Skip to main content

matrix_sdk_ffi/
client.rs

1// Copyright 2025 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for that specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    collections::HashMap,
17    fmt::Debug,
18    path::PathBuf,
19    sync::{Arc, OnceLock},
20    time::Duration,
21};
22
23use anyhow::{anyhow, Context as _};
24use futures_util::pin_mut;
25#[cfg(not(target_family = "wasm"))]
26use matrix_sdk::media::MediaFileHandle as SdkMediaFileHandle;
27#[cfg(feature = "sqlite")]
28use matrix_sdk::STATE_STORE_DATABASE_NAME;
29use matrix_sdk::{
30    authentication::oauth::{ClientId, OAuthAuthorizationData, OAuthError, OAuthSession},
31    deserialized_responses::RawAnySyncOrStrippedTimelineEvent,
32    executor::AbortOnDrop,
33    media::{MediaFormat, MediaRequestParameters, MediaRetentionPolicy, MediaThumbnailSettings},
34    ruma::{
35        api::client::{
36            discovery::{
37                discover_homeserver::RtcFocusInfo,
38                get_authorization_server_metadata::v1::Prompt as RumaOidcPrompt,
39            },
40            push::{EmailPusherData, PusherIds, PusherInit, PusherKind as RumaPusherKind},
41            room::{create_room, Visibility},
42            session::get_login_types,
43            user_directory::search_users,
44        },
45        events::{
46            room::{
47                avatar::RoomAvatarEventContent, encryption::RoomEncryptionEventContent,
48                message::MessageType,
49            },
50            AnyInitialStateEvent, InitialStateEvent,
51        },
52        serde::Raw,
53        EventEncryptionAlgorithm, RoomId, TransactionId, UInt, UserId,
54    },
55    sliding_sync::Version as SdkSlidingSyncVersion,
56    store::RoomLoadSettings as SdkRoomLoadSettings,
57    task_monitor::BackgroundTaskFailureReason,
58    Account, AuthApi, AuthSession, Client as MatrixClient, Error, SessionChange, SessionTokens,
59};
60use matrix_sdk_common::{
61    cross_process_lock::CrossProcessLockConfig, stream::StreamExt, SendOutsideWasm, SyncOutsideWasm,
62};
63use matrix_sdk_ui::{
64    notification_client::{
65        NotificationClient as MatrixNotificationClient,
66        NotificationProcessSetup as MatrixNotificationProcessSetup,
67    },
68    spaces::SpaceService as UISpaceService,
69    unable_to_decrypt_hook::UtdHookManager,
70};
71use mime::Mime;
72use oauth2::Scope;
73use ruma::{
74    api::client::{
75        alias::get_alias,
76        discovery::get_authorization_server_metadata::v1::{
77            AccountManagementActionData, DeviceDeleteData, DeviceViewData,
78        },
79        error::ErrorKind,
80        profile::{AvatarUrl, DisplayName},
81        room::create_room::{v3::CreationContent, RoomPowerLevelsContentOverride},
82        uiaa::{EmailUserIdentifier, UserIdentifier},
83    },
84    events::{
85        direct::DirectEventContent,
86        fully_read::FullyReadEventContent,
87        identity_server::IdentityServerEventContent,
88        ignored_user_list::IgnoredUserListEventContent,
89        key::verification::request::ToDeviceKeyVerificationRequestEvent,
90        marked_unread::{MarkedUnreadEventContent, UnstableMarkedUnreadEventContent},
91        push_rules::PushRulesEventContent,
92        room::{
93            history_visibility::RoomHistoryVisibilityEventContent,
94            join_rules::{
95                AllowRule as RumaAllowRule, JoinRule as RumaJoinRule, RoomJoinRulesEventContent,
96            },
97            message::{OriginalSyncRoomMessageEvent, Relation},
98        },
99        secret_storage::{
100            default_key::SecretStorageDefaultKeyEventContent, key::SecretStorageKeyEventContent,
101        },
102        tag::TagEventContent,
103        AnyMessageLikeEventContent, AnySyncTimelineEvent,
104        GlobalAccountDataEvent as RumaGlobalAccountDataEvent,
105        RoomAccountDataEvent as RumaRoomAccountDataEvent,
106    },
107    push::{HttpPusherData as RumaHttpPusherData, PushFormat as RumaPushFormat},
108    room::RoomType,
109    OwnedDeviceId, OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
110};
111use serde::{Deserialize, Serialize};
112use serde_json::{json, Value};
113use tokio::sync::broadcast::error::RecvError;
114use tracing::{debug, error};
115use url::Url;
116
117use super::{
118    room::{room_info::RoomInfo, Room},
119    session_verification::SessionVerificationController,
120};
121use crate::{
122    authentication::{HomeserverLoginDetails, OidcConfiguration, OidcError, SsoError, SsoHandler},
123    client,
124    encryption::Encryption,
125    notification::{
126        NotificationClient, NotificationEvent, NotificationItem, NotificationRoomInfo,
127        NotificationSenderInfo,
128    },
129    notification_settings::NotificationSettings,
130    qr_code::{GrantLoginWithQrCodeHandler, LoginWithQrCodeHandler},
131    room::{RoomHistoryVisibility, RoomInfoListener, RoomSendQueueUpdate},
132    room_directory_search::RoomDirectorySearch,
133    room_preview::RoomPreview,
134    ruma::{
135        AccountDataEvent, AccountDataEventType, AuthData, InviteAvatars, MediaPreviewConfig,
136        MediaPreviews, MediaSource, RoomAccountDataEvent, RoomAccountDataEventType,
137    },
138    runtime::get_runtime_handle,
139    spaces::SpaceService,
140    sync_service::{SyncService, SyncServiceBuilder},
141    sync_v2::{SyncListenerV2, SyncResponseV2, SyncSettingsV2},
142    task_handle::TaskHandle,
143    utd::{UnableToDecryptDelegate, UtdHook},
144    utils::AsyncRuntimeDropped,
145    ClientError,
146};
147
148#[derive(Clone, uniffi::Record)]
149pub struct PusherIdentifiers {
150    pub pushkey: String,
151    pub app_id: String,
152}
153
154impl From<PusherIdentifiers> for PusherIds {
155    fn from(value: PusherIdentifiers) -> Self {
156        Self::new(value.pushkey, value.app_id)
157    }
158}
159
160#[derive(Clone, uniffi::Record)]
161pub struct HttpPusherData {
162    pub url: String,
163    pub format: Option<PushFormat>,
164    pub default_payload: Option<String>,
165}
166
167#[derive(Clone, uniffi::Enum)]
168pub enum PusherKind {
169    Http { data: HttpPusherData },
170    Email,
171}
172
173impl TryFrom<PusherKind> for RumaPusherKind {
174    type Error = anyhow::Error;
175
176    fn try_from(value: PusherKind) -> anyhow::Result<Self> {
177        match value {
178            PusherKind::Http { data } => {
179                let mut ruma_data = RumaHttpPusherData::new(data.url);
180                if let Some(payload) = data.default_payload {
181                    let json: Value = serde_json::from_str(&payload)?;
182                    ruma_data.data.insert("default_payload".to_owned(), json);
183                }
184                ruma_data.format = data.format.map(Into::into);
185                Ok(Self::Http(ruma_data))
186            }
187            PusherKind::Email => {
188                let ruma_data = EmailPusherData::new();
189                Ok(Self::Email(ruma_data))
190            }
191        }
192    }
193}
194
195#[derive(Clone, uniffi::Enum)]
196pub enum PushFormat {
197    EventIdOnly,
198}
199
200impl From<PushFormat> for RumaPushFormat {
201    fn from(value: PushFormat) -> Self {
202        match value {
203            client::PushFormat::EventIdOnly => Self::EventIdOnly,
204        }
205    }
206}
207
208#[matrix_sdk_ffi_macros::export(callback_interface)]
209pub trait ClientDelegate: SyncOutsideWasm + SendOutsideWasm {
210    /// A callback invoked whenever the SDK runs into an unknown token error.
211    fn did_receive_auth_error(&self, is_soft_logout: bool);
212
213    /// A callback invoked when a background task registered with the client's
214    /// task monitor encounters an error.
215    ///
216    /// Can default to an empty implementation, if the embedder doesn't care
217    /// about handling background jobs errors.
218    fn on_background_task_error_report(
219        &self,
220        task_name: String,
221        error: BackgroundTaskFailureReason,
222    );
223}
224
225#[matrix_sdk_ffi_macros::export(callback_interface)]
226pub trait ClientSessionDelegate: SyncOutsideWasm + SendOutsideWasm {
227    fn retrieve_session_from_keychain(&self, user_id: String) -> Result<Session, ClientError>;
228    fn save_session_in_keychain(&self, session: Session);
229}
230
231#[matrix_sdk_ffi_macros::export(callback_interface)]
232pub trait ProgressWatcher: SyncOutsideWasm + SendOutsideWasm {
233    fn transmission_progress(&self, progress: TransmissionProgress);
234}
235
236/// A listener to the global (client-wide) update reporter of the send queue.
237#[matrix_sdk_ffi_macros::export(callback_interface)]
238pub trait SendQueueRoomUpdateListener: SyncOutsideWasm + SendOutsideWasm {
239    /// Called every time the send queue emits an update for a given room.
240    fn on_update(&self, room_id: String, update: RoomSendQueueUpdate);
241}
242
243/// A listener to the global (client-wide) error reporter of the send queue.
244#[matrix_sdk_ffi_macros::export(callback_interface)]
245pub trait SendQueueRoomErrorListener: SyncOutsideWasm + SendOutsideWasm {
246    /// Called every time the send queue has ran into an error for a given room,
247    /// which will disable the send queue for that particular room.
248    fn on_error(&self, room_id: String, error: ClientError);
249}
250
251/// A listener for changes of global account data events.
252#[matrix_sdk_ffi_macros::export(callback_interface)]
253pub trait AccountDataListener: SyncOutsideWasm + SendOutsideWasm {
254    /// Called when a global account data event has changed.
255    fn on_change(&self, event: AccountDataEvent);
256}
257
258/// A listener for duplicate key upload errors triggered by requests to
259/// /keys/upload.
260#[matrix_sdk_ffi_macros::export(callback_interface)]
261pub trait DuplicateKeyUploadErrorListener: SyncOutsideWasm + SendOutsideWasm {
262    /// Called once when uploading keys fails.
263    fn on_duplicate_key_upload_error(&self, message: Option<DuplicateOneTimeKeyErrorMessage>);
264}
265
266/// Information about the old and new key that caused a duplicate key upload
267/// error in /keys/upload.
268#[derive(uniffi::Record)]
269pub struct DuplicateOneTimeKeyErrorMessage {
270    /// The previously uploaded one-time key, encoded as unpadded base64.
271    pub old_key: String,
272    /// The one-time key we attempted to upload, encoded as unpadded base64
273    pub new_key: String,
274}
275
276impl From<matrix_sdk::encryption::DuplicateOneTimeKeyErrorMessage>
277    for DuplicateOneTimeKeyErrorMessage
278{
279    fn from(value: matrix_sdk::encryption::DuplicateOneTimeKeyErrorMessage) -> Self {
280        Self { old_key: value.old_key.to_base64(), new_key: value.new_key.to_base64() }
281    }
282}
283
284/// A listener for changes of room account data events.
285#[matrix_sdk_ffi_macros::export(callback_interface)]
286pub trait RoomAccountDataListener: SyncOutsideWasm + SendOutsideWasm {
287    /// Called when a room account data event was changed.
288    fn on_change(&self, event: RoomAccountDataEvent, room_id: String);
289}
290
291/// A listener for notifications generated from sync responses.
292///
293/// This is called during sync for each event that triggers a notification
294/// based on the user's push rules.
295#[matrix_sdk_ffi_macros::export(callback_interface)]
296pub trait SyncNotificationListener: SyncOutsideWasm + SendOutsideWasm {
297    /// Called when a notifying event is received during sync.
298    fn on_notification(&self, notification: NotificationItem, room_id: String);
299}
300
301#[derive(Clone, Copy, uniffi::Record)]
302pub struct TransmissionProgress {
303    pub current: u64,
304    pub total: u64,
305}
306
307impl From<matrix_sdk::TransmissionProgress> for TransmissionProgress {
308    fn from(value: matrix_sdk::TransmissionProgress) -> Self {
309        Self {
310            current: value.current.try_into().unwrap_or(u64::MAX),
311            total: value.total.try_into().unwrap_or(u64::MAX),
312        }
313    }
314}
315
316struct ClientDelegateData {
317    /// The delegate itself, that will receive the callbacks.
318    delegate: Arc<dyn ClientDelegate>,
319
320    // The background task error listener task, that will forward errors occurring in background
321    // jobs to the delegate.
322    _background_error_listener_task: Arc<AbortOnDrop<()>>,
323}
324
325#[derive(uniffi::Object)]
326pub struct Client {
327    pub(crate) inner: AsyncRuntimeDropped<MatrixClient>,
328
329    delegate_data: OnceLock<ClientDelegateData>,
330
331    pub(crate) utd_hook_manager: OnceLock<Arc<UtdHookManager>>,
332
333    session_verification_controller:
334        Arc<tokio::sync::RwLock<Option<SessionVerificationController>>>,
335
336    /// The path to the directory where the state store and the crypto store are
337    /// located, if the `Client` instance has been built with a store (either
338    /// SQLite or IndexedDB).
339    #[cfg_attr(not(feature = "sqlite"), allow(unused))]
340    store_path: Option<PathBuf>,
341}
342
343impl Client {
344    pub async fn new(
345        sdk_client: MatrixClient,
346        session_delegate: Option<Arc<dyn ClientSessionDelegate>>,
347        store_path: Option<PathBuf>,
348    ) -> Result<Self, ClientError> {
349        let session_verification_controller: Arc<
350            tokio::sync::RwLock<Option<SessionVerificationController>>,
351        > = Default::default();
352        let controller = session_verification_controller.clone();
353        sdk_client.add_event_handler(
354            move |event: ToDeviceKeyVerificationRequestEvent| async move {
355                if let Some(session_verification_controller) = &*controller.clone().read().await {
356                    session_verification_controller
357                        .process_incoming_verification_request(
358                            &event.sender,
359                            event.content.transaction_id,
360                        )
361                        .await;
362                }
363            },
364        );
365
366        let controller = session_verification_controller.clone();
367        sdk_client.add_event_handler(move |event: OriginalSyncRoomMessageEvent| async move {
368            if let MessageType::VerificationRequest(_) = &event.content.msgtype {
369                if let Some(session_verification_controller) = &*controller.clone().read().await {
370                    session_verification_controller
371                        .process_incoming_verification_request(&event.sender, event.event_id)
372                        .await;
373                }
374            }
375        });
376
377        let store_mode = sdk_client.cross_process_lock_config();
378
379        let client = Client {
380            inner: AsyncRuntimeDropped::new(sdk_client.clone()),
381            delegate_data: OnceLock::new(),
382            utd_hook_manager: OnceLock::new(),
383            session_verification_controller,
384            store_path,
385        };
386
387        match store_mode {
388            CrossProcessLockConfig::MultiProcess { holder_name } => {
389                if session_delegate.is_none() {
390                    return Err(anyhow::anyhow!(
391                        "missing session delegates with multi-process lock configuration"
392                    ))?;
393                }
394                client.inner.oauth().enable_cross_process_refresh_lock(holder_name.clone()).await?;
395            }
396            CrossProcessLockConfig::SingleProcess => {}
397        }
398
399        if let Some(session_delegate) = session_delegate {
400            client.inner.set_session_callbacks(
401                {
402                    let session_delegate = session_delegate.clone();
403                    Box::new(move |client| {
404                        let session_delegate = session_delegate.clone();
405                        let user_id = client.user_id().context("user isn't logged in")?;
406                        Ok(Self::retrieve_session(session_delegate, user_id)?)
407                    })
408                },
409                {
410                    let session_delegate = session_delegate.clone();
411                    Box::new(move |client| {
412                        let session_delegate = session_delegate.clone();
413                        Ok(Self::save_session(session_delegate, client)?)
414                    })
415                },
416            )?;
417        }
418
419        Ok(client)
420    }
421}
422
423#[matrix_sdk_ffi_macros::export]
424impl Client {
425    /// Perform database optimizations if any are available, i.e. vacuuming in
426    /// SQLite.
427    pub async fn optimize_stores(&self) -> Result<(), ClientError> {
428        Ok(self.inner.optimize_stores().await?)
429    }
430
431    /// Returns the sizes of the existing stores, if known.
432    pub async fn get_store_sizes(&self) -> Result<StoreSizes, ClientError> {
433        Ok(self.inner.get_store_sizes().await?.into())
434    }
435
436    /// Information about login options for the client's homeserver.
437    pub async fn homeserver_login_details(&self) -> Arc<HomeserverLoginDetails> {
438        let oauth = self.inner.oauth();
439        let (supports_oidc_login, supported_oidc_prompts) = match oauth.server_metadata().await {
440            Ok(metadata) => {
441                let prompts =
442                    metadata.prompt_values_supported.into_iter().map(Into::into).collect();
443
444                (true, prompts)
445            }
446            Err(error) => {
447                error!("Failed to fetch OIDC provider metadata: {error}");
448                (false, Default::default())
449            }
450        };
451
452        let login_types = self.inner.matrix_auth().get_login_types().await.ok();
453        let supports_password_login = login_types
454            .as_ref()
455            .map(|login_types| {
456                login_types.flows.iter().any(|login_type| {
457                    matches!(login_type, get_login_types::v3::LoginType::Password(_))
458                })
459            })
460            .unwrap_or(false);
461        let supports_sso_login = login_types
462            .as_ref()
463            .map(|login_types| {
464                login_types
465                    .flows
466                    .iter()
467                    .any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Sso(_)))
468            })
469            .unwrap_or(false);
470        let sliding_sync_version = self.sliding_sync_version();
471
472        Arc::new(HomeserverLoginDetails {
473            url: self.homeserver(),
474            sliding_sync_version,
475            supports_oidc_login,
476            supported_oidc_prompts,
477            supports_sso_login,
478            supports_password_login,
479        })
480    }
481
482    /// Login using a username and password.
483    pub async fn login(
484        &self,
485        username: String,
486        password: String,
487        initial_device_name: Option<String>,
488        device_id: Option<String>,
489    ) -> Result<(), ClientError> {
490        let mut builder = self.inner.matrix_auth().login_username(&username, &password);
491        if let Some(initial_device_name) = initial_device_name.as_ref() {
492            builder = builder.initial_device_display_name(initial_device_name);
493        }
494        if let Some(device_id) = device_id.as_ref() {
495            builder = builder.device_id(device_id);
496        }
497        builder.send().await?;
498        Ok(())
499    }
500
501    /// Login using JWT
502    /// This is an implementation of the custom_login https://docs.rs/matrix-sdk/latest/matrix_sdk/matrix_auth/struct.MatrixAuth.html#method.login_custom
503    /// For more information on logging in with JWT: https://element-hq.github.io/synapse/latest/jwt.html
504    pub async fn custom_login_with_jwt(
505        &self,
506        jwt: String,
507        initial_device_name: Option<String>,
508        device_id: Option<String>,
509    ) -> Result<(), ClientError> {
510        let data = json!({ "token": jwt }).as_object().unwrap().clone();
511
512        let mut builder = self.inner.matrix_auth().login_custom("org.matrix.login.jwt", data)?;
513
514        if let Some(initial_device_name) = initial_device_name.as_ref() {
515            builder = builder.initial_device_display_name(initial_device_name);
516        }
517
518        if let Some(device_id) = device_id.as_ref() {
519            builder = builder.device_id(device_id);
520        }
521
522        builder.send().await?;
523        Ok(())
524    }
525
526    /// Login using an email and password.
527    pub async fn login_with_email(
528        &self,
529        email: String,
530        password: String,
531        initial_device_name: Option<String>,
532        device_id: Option<String>,
533    ) -> Result<(), ClientError> {
534        let mut builder = self
535            .inner
536            .matrix_auth()
537            .login_identifier(UserIdentifier::Email(EmailUserIdentifier::new(email)), &password);
538
539        if let Some(initial_device_name) = initial_device_name.as_ref() {
540            builder = builder.initial_device_display_name(initial_device_name);
541        }
542
543        if let Some(device_id) = device_id.as_ref() {
544            builder = builder.device_id(device_id);
545        }
546
547        builder.send().await?;
548
549        Ok(())
550    }
551
552    /// Returns a handler to start the SSO login process.
553    pub(crate) async fn start_sso_login(
554        self: &Arc<Self>,
555        redirect_url: String,
556        idp_id: Option<String>,
557    ) -> Result<Arc<SsoHandler>, SsoError> {
558        let auth = self.inner.matrix_auth();
559        let url = auth
560            .get_sso_login_url(redirect_url.as_str(), idp_id.as_deref())
561            .await
562            .map_err(|e| SsoError::Generic { message: e.to_string() })?;
563        Ok(Arc::new(SsoHandler { client: Arc::clone(self), url }))
564    }
565
566    /// Requests the URL needed for opening a web view using OIDC. Once the web
567    /// view has succeeded, call `login_with_oidc_callback` with the callback it
568    /// returns. If a failure occurs and a callback isn't available, make sure
569    /// to call `abort_oidc_auth` to inform the client of this.
570    ///
571    /// # Arguments
572    ///
573    /// * `oidc_configuration` - The configuration used to load the credentials
574    ///   of the client if it is already registered with the authorization
575    ///   server, or register the client and store its credentials if it isn't.
576    ///
577    /// * `prompt` - The desired user experience in the web UI. No value means
578    ///   that the user wishes to login into an existing account, and a value of
579    ///   `Create` means that the user wishes to register a new account.
580    ///
581    /// * `login_hint` - A generic login hint that an identity provider can use
582    ///   to pre-fill the login form. The format of this hint is not restricted
583    ///   by the spec as external providers all have their own way to handle the hint.
584    ///   However, it should be noted that when providing a user ID as a hint
585    ///   for MAS (with no upstream provider), then the format to use is defined
586    ///   by [MSC4198]: https://github.com/matrix-org/matrix-spec-proposals/pull/4198
587    ///
588    /// * `device_id` - The unique ID that will be associated with the session.
589    ///   If not set, a random one will be generated. It can be an existing
590    ///   device ID from a previous login call. Note that this should be done
591    ///   only if the client also holds the corresponding encryption keys.
592    ///
593    /// * `additional_scopes` - Additional scopes to request from the
594    ///   authorization server, e.g. "urn:matrix:client:com.example.msc9999.foo".
595    ///   The scopes for API access and the device ID according to the
596    ///   [specification](https://spec.matrix.org/v1.15/client-server-api/#allocated-scope-tokens)
597    ///   are always requested.
598    pub async fn url_for_oidc(
599        &self,
600        oidc_configuration: &OidcConfiguration,
601        prompt: Option<OidcPrompt>,
602        login_hint: Option<String>,
603        device_id: Option<String>,
604        additional_scopes: Option<Vec<String>>,
605    ) -> Result<Arc<OAuthAuthorizationData>, OidcError> {
606        let registration_data = oidc_configuration.registration_data()?;
607        let redirect_uri = oidc_configuration.redirect_uri()?;
608
609        let device_id = device_id.map(OwnedDeviceId::from);
610
611        let additional_scopes =
612            additional_scopes.map(|scopes| scopes.into_iter().map(Scope::new).collect::<Vec<_>>());
613
614        let mut url_builder = self.inner.oauth().login(
615            redirect_uri,
616            device_id,
617            Some(registration_data),
618            additional_scopes,
619        );
620
621        if let Some(prompt) = prompt {
622            url_builder = url_builder.prompt(vec![prompt.into()]);
623        }
624        if let Some(login_hint) = login_hint {
625            url_builder = url_builder.login_hint(login_hint);
626        }
627
628        let data = url_builder.build().await?;
629
630        Ok(Arc::new(data))
631    }
632
633    /// Aborts an existing OIDC login operation that might have been cancelled,
634    /// failed etc.
635    pub async fn abort_oidc_auth(&self, authorization_data: Arc<OAuthAuthorizationData>) {
636        self.inner.oauth().abort_login(&authorization_data.state).await;
637    }
638
639    /// Completes the OIDC login process.
640    pub async fn login_with_oidc_callback(&self, callback_url: String) -> Result<(), OidcError> {
641        let url = Url::parse(&callback_url).or(Err(OidcError::CallbackUrlInvalid))?;
642
643        self.inner.oauth().finish_login(url.into()).await?;
644
645        Ok(())
646    }
647
648    /// Create a handler for requesting an existing device to grant login to
649    /// this device by way of a QR code.
650    ///
651    /// # Arguments
652    ///
653    /// * `oidc_configuration` - The data to restore or register the client with
654    ///   the server.
655    pub fn new_login_with_qr_code_handler(
656        self: Arc<Self>,
657        oidc_configuration: OidcConfiguration,
658    ) -> LoginWithQrCodeHandler {
659        LoginWithQrCodeHandler::new(self.inner.oauth(), oidc_configuration)
660    }
661
662    /// Create a handler for granting login from this device to a new device by
663    /// way of a QR code.
664    pub fn new_grant_login_with_qr_code_handler(self: Arc<Self>) -> GrantLoginWithQrCodeHandler {
665        GrantLoginWithQrCodeHandler::new(self.inner.oauth())
666    }
667
668    /// Restores the client from a `Session`.
669    ///
670    /// It reloads the entire set of rooms from the previous session.
671    ///
672    /// If you want to control the amount of rooms to reloads, check
673    /// [`Client::restore_session_with`].
674    pub async fn restore_session(&self, session: Session) -> Result<(), ClientError> {
675        self.restore_session_with(session, RoomLoadSettings::All).await
676    }
677
678    /// Restores the client from a `Session`.
679    ///
680    /// It reloads a set of rooms controlled by [`RoomLoadSettings`].
681    pub async fn restore_session_with(
682        &self,
683        session: Session,
684        room_load_settings: RoomLoadSettings,
685    ) -> Result<(), ClientError> {
686        let sliding_sync_version = session.sliding_sync_version.clone();
687        let auth_session: AuthSession = session.try_into()?;
688
689        self.inner
690            .restore_session_with(
691                auth_session,
692                room_load_settings
693                    .try_into()
694                    .map_err(|error| ClientError::from_str(error, None))?,
695            )
696            .await?;
697        self.inner.set_sliding_sync_version(sliding_sync_version.try_into()?);
698
699        Ok(())
700    }
701
702    /// Enables or disables all the room send queues at once.
703    ///
704    /// When connectivity is lost on a device, it is recommended to disable the
705    /// room sending queues.
706    ///
707    /// This can be controlled for individual rooms, using
708    /// [`Room::enable_send_queue`].
709    pub async fn enable_all_send_queues(&self, enable: bool) {
710        self.inner.send_queue().set_enabled(enable).await;
711    }
712
713    /// Enables or disables progress reporting for media uploads in the send
714    /// queue.
715    pub fn enable_send_queue_upload_progress(&self, enable: bool) {
716        self.inner.send_queue().enable_upload_progress(enable);
717    }
718
719    /// Subscribe to the global send queue update reporter, at the
720    /// client-wide level.
721    ///
722    /// The given listener will be immediately called with
723    /// `RoomSendQueueUpdate::NewLocalEvent` for each local echo existing in
724    /// the queue.
725    pub async fn subscribe_to_send_queue_updates(
726        &self,
727        listener: Box<dyn SendQueueRoomUpdateListener>,
728    ) -> Result<Arc<TaskHandle>, ClientError> {
729        let q = self.inner.send_queue();
730        let local_echoes = q.local_echoes().await?;
731        let mut subscriber = q.subscribe();
732
733        for (room_id, local_echoes) in local_echoes {
734            for local_echo in local_echoes {
735                listener.on_update(
736                    room_id.clone().into(),
737                    RoomSendQueueUpdate::NewLocalEvent {
738                        transaction_id: local_echo.transaction_id.into(),
739                    },
740                );
741            }
742        }
743
744        Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
745            loop {
746                match subscriber.recv().await {
747                    Ok(update) => {
748                        let room_id = update.room_id.to_string();
749                        match update.update.try_into() {
750                            Ok(update) => listener.on_update(room_id, update),
751                            Err(err) => error!("error when converting send queue update: {err}"),
752                        }
753                    }
754                    Err(err) => {
755                        error!("error when listening to the send queue update reporter: {err}");
756                    }
757                }
758            }
759        }))))
760    }
761
762    /// Subscribe to the global enablement status of the send queue, at the
763    /// client-wide level.
764    ///
765    /// The given listener will be immediately called with the initial value of
766    /// the enablement status.
767    pub fn subscribe_to_send_queue_status(
768        &self,
769        listener: Box<dyn SendQueueRoomErrorListener>,
770    ) -> Arc<TaskHandle> {
771        let q = self.inner.send_queue();
772        let mut subscriber = q.subscribe_errors();
773
774        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
775            // Respawn tasks for rooms that had unsent events. At this point we've just
776            // created the subscriber, so it'll be notified about errors.
777            q.respawn_tasks_for_rooms_with_unsent_requests().await;
778
779            loop {
780                match subscriber.recv().await {
781                    Ok(report) => listener
782                        .on_error(report.room_id.to_string(), ClientError::from_err(report.error)),
783                    Err(err) => {
784                        error!("error when listening to the send queue error reporter: {err}");
785                    }
786                }
787            }
788        })))
789    }
790
791    /// Subscribe to duplicate key upload errors triggered by requests to
792    /// /keys/upload.
793    pub fn subscribe_to_duplicate_key_upload_errors(
794        &self,
795        listener: Box<dyn DuplicateKeyUploadErrorListener>,
796    ) -> Arc<TaskHandle> {
797        let mut subscriber = self.inner.subscribe_to_duplicate_key_upload_errors();
798
799        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
800            loop {
801                match subscriber.recv().await {
802                    Ok(message) => {
803                        listener.on_duplicate_key_upload_error(message.map(|m| m.into()))
804                    }
805                    Err(err) => {
806                        error!("error when listening to key upload errors: {err}");
807                    }
808                }
809            }
810        })))
811    }
812
813    /// Subscribe to updates of global account data events.
814    ///
815    /// Be careful that only the most recent value can be observed. Subscribers
816    /// are notified when a new value is sent, but there is no guarantee that
817    /// they will see all values.
818    pub fn observe_account_data_event(
819        &self,
820        event_type: AccountDataEventType,
821        listener: Box<dyn AccountDataListener>,
822    ) -> Arc<TaskHandle> {
823        macro_rules! observe {
824            ($t:ty, $cb: expr) => {{
825                // Using an Arc here is mandatory or else the subscriber will never trigger
826                let observer =
827                    Arc::new(self.inner.observe_events::<RumaGlobalAccountDataEvent<$t>, ()>());
828
829                Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
830                    let mut subscriber = observer.subscribe();
831                    loop {
832                        if let Some(next) = subscriber.next().await {
833                            $cb(next.0);
834                        }
835                    }
836                })))
837            }};
838
839            ($t:ty) => {{
840                observe!($t, |event: RumaGlobalAccountDataEvent<$t>| {
841                    listener.on_change(event.into());
842                })
843            }};
844        }
845
846        match event_type {
847            AccountDataEventType::Direct => {
848                observe!(DirectEventContent)
849            }
850            AccountDataEventType::IdentityServer => {
851                observe!(IdentityServerEventContent)
852            }
853            AccountDataEventType::IgnoredUserList => {
854                observe!(IgnoredUserListEventContent)
855            }
856            AccountDataEventType::PushRules => {
857                observe!(PushRulesEventContent, |event: RumaGlobalAccountDataEvent<
858                    PushRulesEventContent,
859                >| {
860                    if let Ok(event) = event.try_into() {
861                        listener.on_change(event);
862                    }
863                })
864            }
865            AccountDataEventType::SecretStorageDefaultKey => {
866                observe!(SecretStorageDefaultKeyEventContent)
867            }
868            AccountDataEventType::SecretStorageKey { key_id } => {
869                observe!(SecretStorageKeyEventContent, |event: RumaGlobalAccountDataEvent<
870                    SecretStorageKeyEventContent,
871                >| {
872                    if event.content.key_id != key_id {
873                        return;
874                    }
875                    if let Ok(event) = event.try_into() {
876                        listener.on_change(event);
877                    }
878                })
879            }
880        }
881    }
882
883    /// Subscribe to updates of room account data events.
884    ///
885    /// Be careful that only the most recent value can be observed. Subscribers
886    /// are notified when a new value is sent, but there is no guarantee that
887    /// they will see all values.
888    pub fn observe_room_account_data_event(
889        &self,
890        room_id: String,
891        event_type: RoomAccountDataEventType,
892        listener: Box<dyn RoomAccountDataListener>,
893    ) -> Result<Arc<TaskHandle>, ClientError> {
894        macro_rules! observe {
895            ($t:ty, $cb: expr) => {{
896                // Using an Arc here is mandatory or else the subscriber will never trigger
897                let observer =
898                    Arc::new(self.inner.observe_room_events::<RumaRoomAccountDataEvent<$t>, ()>(
899                        &RoomId::parse(&room_id)?,
900                    ));
901
902                Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
903                    let mut subscriber = observer.subscribe();
904                    loop {
905                        if let Some(next) = subscriber.next().await {
906                            $cb(next.0);
907                        }
908                    }
909                }))))
910            }};
911
912            ($t:ty) => {{
913                observe!($t, |event: RumaRoomAccountDataEvent<$t>| {
914                    listener.on_change(event.into(), room_id.clone());
915                })
916            }};
917        }
918
919        match event_type {
920            RoomAccountDataEventType::FullyRead => {
921                observe!(FullyReadEventContent)
922            }
923            RoomAccountDataEventType::MarkedUnread => {
924                observe!(MarkedUnreadEventContent)
925            }
926            RoomAccountDataEventType::Tag => {
927                observe!(TagEventContent, |event: RumaRoomAccountDataEvent<TagEventContent>| {
928                    if let Ok(event) = event.try_into() {
929                        listener.on_change(event, room_id.clone());
930                    }
931                })
932            }
933            RoomAccountDataEventType::UnstableMarkedUnread => {
934                observe!(UnstableMarkedUnreadEventContent)
935            }
936        }
937    }
938
939    /// Register a handler for notifications generated from sync responses.
940    ///
941    /// The handler will be called during sync for each event that triggers
942    /// a notification based on the user's push rules.
943    ///
944    /// The handler receives:
945    /// - The notification with push actions and event data
946    /// - The room ID where the notification occurred
947    ///
948    /// This is useful for implementing custom notification logic, such as
949    /// displaying local notifications or updating notification badges.
950    pub async fn register_notification_handler(&self, listener: Box<dyn SyncNotificationListener>) {
951        let listener = Arc::new(listener);
952        self.inner
953            .register_notification_handler(move |notification, room, _client| {
954                let listener = listener.clone();
955                let room_id = room.room_id().to_string();
956
957                async move {
958                    // Extract information about the actions
959                    let is_noisy = notification.actions.iter().any(|a| a.sound().is_some());
960                    let has_mention = notification.actions.iter().any(|a| a.is_highlight());
961
962                    // Convert SDK actions to FFI type
963                    let actions: Vec<crate::notification_settings::Action> = notification
964                        .actions
965                        .into_iter()
966                        .filter_map(|action| action.try_into().ok())
967                        .collect();
968
969                    // Convert SDK event to FFI type
970                    let (sender, event, thread_id, raw_event) = match notification.event {
971                        RawAnySyncOrStrippedTimelineEvent::Sync(raw) => {
972                            let raw_event = raw.json().get().to_owned();
973                            match raw.deserialize() {
974                                Ok(deserialized) => {
975                                    let sender = deserialized.sender().to_owned();
976                                    let thread_id = match &deserialized {
977                                        AnySyncTimelineEvent::MessageLike(event) => {
978                                            match event.original_content() {
979                                                Some(AnyMessageLikeEventContent::RoomMessage(
980                                                    content,
981                                                )) => match content.relates_to {
982                                                    Some(Relation::Thread(thread)) => {
983                                                        Some(thread.event_id.to_string())
984                                                    }
985                                                    _ => None,
986                                                },
987                                                _ => None,
988                                            }
989                                        }
990                                        _ => None,
991                                    };
992                                    let event = NotificationEvent::Timeline {
993                                        event: Arc::new(crate::event::TimelineEvent(Box::new(
994                                            deserialized,
995                                        ))),
996                                    };
997                                    (sender, event, thread_id, raw_event)
998                                }
999                                Err(err) => {
1000                                    tracing::warn!("Failed to deserialize timeline event: {err}");
1001                                    return;
1002                                }
1003                            }
1004                        }
1005                        RawAnySyncOrStrippedTimelineEvent::Stripped(raw) => {
1006                            let raw_event = raw.json().get().to_owned();
1007                            match raw.deserialize() {
1008                                Ok(deserialized) => {
1009                                    let sender = deserialized.sender().to_owned();
1010                                    let event =
1011                                        NotificationEvent::Invite { sender: sender.to_string() };
1012                                    let thread_id = None;
1013                                    (sender, event, thread_id, raw_event)
1014                                }
1015                                Err(err) => {
1016                                    tracing::warn!(
1017                                        "Failed to deserialize stripped state event: {err}"
1018                                    );
1019                                    return;
1020                                }
1021                            }
1022                        }
1023                    };
1024
1025                    // Compile sender info
1026                    let sender = room.get_member_no_sync(&sender).await.ok().flatten();
1027                    let sender_info = if let Some(sender) = sender.as_ref() {
1028                        NotificationSenderInfo {
1029                            display_name: sender.display_name().map(|name| name.to_owned()),
1030                            avatar_url: sender.avatar_url().map(|uri| uri.to_string()),
1031                            is_name_ambiguous: sender.name_ambiguous(),
1032                        }
1033                    } else {
1034                        NotificationSenderInfo {
1035                            display_name: None,
1036                            avatar_url: None,
1037                            is_name_ambiguous: false,
1038                        }
1039                    };
1040
1041                    // Compile room info
1042                    let display_name = match room.display_name().await {
1043                        Ok(name) => name.to_string(),
1044                        Err(err) => {
1045                            tracing::warn!("Failed to calculate the room's display name: {err}");
1046                            return;
1047                        }
1048                    };
1049                    let is_direct = match room.is_direct().await {
1050                        Ok(is_direct) => is_direct,
1051                        Err(err) => {
1052                            tracing::warn!("Failed to determine if room is direct or not: {err}");
1053                            return;
1054                        }
1055                    };
1056                    let room_info = NotificationRoomInfo {
1057                        display_name,
1058                        avatar_url: room.avatar_url().map(Into::into),
1059                        canonical_alias: room.canonical_alias().map(Into::into),
1060                        topic: room.topic(),
1061                        join_rule: room
1062                            .join_rule()
1063                            .map(TryInto::try_into)
1064                            .transpose()
1065                            .ok()
1066                            .flatten(),
1067                        joined_members_count: room.joined_members_count(),
1068                        is_encrypted: Some(room.encryption_state().is_encrypted()),
1069                        is_direct,
1070                        is_space: room.is_space(),
1071                    };
1072
1073                    listener.on_notification(
1074                        NotificationItem {
1075                            event,
1076                            raw_event,
1077                            sender_info,
1078                            room_info,
1079                            is_noisy: Some(is_noisy),
1080                            has_mention: Some(has_mention),
1081                            thread_id,
1082                            actions: Some(actions),
1083                        },
1084                        room_id,
1085                    );
1086                }
1087            })
1088            .await;
1089    }
1090
1091    /// Allows generic GET requests to be made through the SDK's internal HTTP
1092    /// client. This is useful when the caller's native HTTP client wouldn't
1093    /// have the same configuration (such as certificates, proxies, etc.) This
1094    /// method returns the raw bytes of the response, so that any kind of
1095    /// resource can be fetched including images, files, etc.
1096    ///
1097    /// Note: When an HTTP error occurs, the error response can be found in the
1098    /// `ClientError::Generic`'s `details` field.
1099    pub async fn get_url(&self, url: String) -> Result<Vec<u8>, ClientError> {
1100        let response = self.inner.http_client().get(url).send().await?;
1101        if response.status().is_success() {
1102            Ok(response.bytes().await?.into())
1103        } else {
1104            Err(ClientError::Generic {
1105                msg: response.status().to_string(),
1106                details: response.text().await.ok(),
1107            })
1108        }
1109    }
1110
1111    /// Empty the server version and unstable features cache.
1112    ///
1113    /// Since the SDK caches the supported versions, it's possible to have a
1114    /// stale entry in the cache. This functions makes it possible to force
1115    /// reset it.
1116    pub async fn reset_supported_versions(&self) -> Result<(), ClientError> {
1117        Ok(self.inner.reset_supported_versions().await?)
1118    }
1119
1120    /// Empty the well-known cache.
1121    ///
1122    /// Since the SDK caches the well-known, it's possible to have a stale
1123    /// entry in the cache. This functions makes it possible to force reset
1124    /// it.
1125    pub async fn reset_well_known(&self) -> Result<(), ClientError> {
1126        Ok(self.inner.reset_well_known().await?)
1127    }
1128}
1129
1130#[matrix_sdk_ffi_macros::export]
1131impl Client {
1132    /// Retrieves a media file from the media source
1133    ///
1134    /// Not available on Wasm platforms, due to lack of accessible file system.
1135    pub async fn get_media_file(
1136        &self,
1137        media_source: Arc<MediaSource>,
1138        filename: Option<String>,
1139        mime_type: String,
1140        use_cache: bool,
1141        temp_dir: Option<String>,
1142    ) -> Result<Arc<MediaFileHandle>, ClientError> {
1143        #[cfg(not(target_family = "wasm"))]
1144        {
1145            let source = (*media_source).clone();
1146            let mime_type: mime::Mime = mime_type.parse()?;
1147
1148            let handle = self
1149                .inner
1150                .media()
1151                .get_media_file(
1152                    &MediaRequestParameters {
1153                        source: source.media_source,
1154                        format: MediaFormat::File,
1155                    },
1156                    filename,
1157                    &mime_type,
1158                    use_cache,
1159                    temp_dir,
1160                )
1161                .await?;
1162
1163            Ok(Arc::new(MediaFileHandle::new(handle)))
1164        }
1165
1166        /// MediaFileHandle uses SdkMediaFileHandle which requires an
1167        /// intermediate TempFile which is not available on wasm
1168        /// platforms due to lack of an accessible file system.
1169        #[cfg(target_family = "wasm")]
1170        Err(ClientError::Generic {
1171            msg: "get_media_file is not supported on wasm platforms".to_owned(),
1172            details: None,
1173        })
1174    }
1175
1176    pub async fn set_display_name(&self, name: String) -> Result<(), ClientError> {
1177        #[cfg(not(target_family = "wasm"))]
1178        {
1179            self.inner
1180                .account()
1181                .set_display_name(Some(name.as_str()))
1182                .await
1183                .context("Unable to set display name")?;
1184        }
1185
1186        #[cfg(target_family = "wasm")]
1187        {
1188            self.inner.account().set_display_name(Some(name.as_str())).await.map_err(|e| {
1189                ClientError::Generic {
1190                    msg: "Unable to set display name".to_owned(),
1191                    details: Some(e.to_string()),
1192                }
1193            })?;
1194        }
1195
1196        Ok(())
1197    }
1198}
1199
1200#[matrix_sdk_ffi_macros::export]
1201impl Client {
1202    /// The sliding sync version.
1203    pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
1204        self.inner.sliding_sync_version().into()
1205    }
1206
1207    /// Find all sliding sync versions that are available.
1208    ///
1209    /// Be careful: This method may hit the store and will send new requests for
1210    /// each call. It can be costly to call it repeatedly.
1211    ///
1212    /// If `.well-known` or `/versions` is unreachable, it will simply move
1213    /// potential sliding sync versions aside. No error will be reported.
1214    pub async fn available_sliding_sync_versions(&self) -> Vec<SlidingSyncVersion> {
1215        self.inner.available_sliding_sync_versions().await.into_iter().map(Into::into).collect()
1216    }
1217
1218    /// Sets the [ClientDelegate] which will inform about authentication errors.
1219    /// Returns an error if the delegate was already set.
1220    pub fn set_delegate(
1221        self: Arc<Self>,
1222        delegate: Option<Box<dyn ClientDelegate>>,
1223    ) -> Result<Option<Arc<TaskHandle>>, ClientError> {
1224        if self.delegate_data.get().is_some() {
1225            return Err(ClientError::Generic {
1226                msg: "Delegate already initialized".to_owned(),
1227                details: None,
1228            });
1229        }
1230
1231        let handle = delegate.map(|delegate| {
1232            let mut session_change_receiver = self.inner.subscribe_to_session_changes();
1233            let client_clone = self.clone();
1234            let session_change_task = get_runtime_handle().spawn(async move {
1235                loop {
1236                    match session_change_receiver.recv().await {
1237                        Ok(session_change) => client_clone.process_session_change(session_change),
1238                        Err(receive_error) => {
1239                            if let RecvError::Closed = receive_error {
1240                                break;
1241                            }
1242                        }
1243                    }
1244                }
1245            });
1246
1247            let delegate: Arc<dyn ClientDelegate> = delegate.into();
1248
1249            let client = self.inner.clone();
1250            let delegate_clone = delegate.clone();
1251            let task = Arc::new(AbortOnDrop::new(get_runtime_handle().spawn(async move {
1252                let mut receiver = client.task_monitor().subscribe();
1253                while let Ok(error) = receiver.recv().await {
1254                    delegate_clone.on_background_task_error_report(error.task.name, error.reason);
1255                }
1256            })));
1257
1258            let delegate_data =
1259                ClientDelegateData { delegate, _background_error_listener_task: task };
1260
1261            self.delegate_data.get_or_init(|| delegate_data);
1262
1263            Arc::new(TaskHandle::new(session_change_task))
1264        });
1265
1266        Ok(handle)
1267    }
1268
1269    /// Sets the [`UnableToDecryptDelegate`] which will inform about UTDs.
1270    /// Returns an error if the delegate was already set.
1271    pub async fn set_utd_delegate(
1272        self: Arc<Self>,
1273        utd_delegate: Box<dyn UnableToDecryptDelegate>,
1274    ) -> Result<(), ClientError> {
1275        if self.utd_hook_manager.get().is_some() {
1276            return Err(ClientError::Generic {
1277                msg: "UTD delegate already initialized".to_owned(),
1278                details: None,
1279            });
1280        }
1281
1282        // UTDs detected before this duration may be reclassified as "late decryption"
1283        // events (or discarded, if they get decrypted fast enough).
1284        const UTD_HOOK_GRACE_PERIOD: Duration = Duration::from_secs(60);
1285
1286        let mut utd_hook_manager = UtdHookManager::new(
1287            Arc::new(UtdHook { delegate: utd_delegate.into() }),
1288            (*self.inner).clone(),
1289        )
1290        .with_max_delay(UTD_HOOK_GRACE_PERIOD);
1291
1292        if let Err(e) = utd_hook_manager.reload_from_store().await {
1293            error!("Unable to reload UTD hook data from data store: {e}");
1294            // Carry on with the setup anyway; we shouldn't fail setup just
1295            // because the UTD hook failed to load its data.
1296        }
1297
1298        self.utd_hook_manager.get_or_init(|| Arc::new(utd_hook_manager));
1299
1300        Ok(())
1301    }
1302
1303    pub fn session(&self) -> Result<Session, ClientError> {
1304        Self::session_inner((*self.inner).clone())
1305    }
1306
1307    pub async fn account_url(
1308        &self,
1309        action: Option<AccountManagementAction>,
1310    ) -> Result<Option<String>, ClientError> {
1311        if !matches!(self.inner.auth_api(), Some(AuthApi::OAuth(..))) {
1312            return Ok(None);
1313        }
1314
1315        let server_metadata = match self.inner.oauth().cached_server_metadata().await {
1316            Ok(server_metadata) => server_metadata,
1317            Err(e) => {
1318                error!("Failed retrieving cached server metadata: {e}");
1319                return Err(OAuthError::from(e).into());
1320            }
1321        };
1322
1323        Ok(if let Some(action) = &action {
1324            server_metadata.account_management_url_with_action(action.into())
1325        } else {
1326            server_metadata.account_management_uri
1327        }
1328        .map(Into::into))
1329    }
1330
1331    pub fn user_id(&self) -> Result<String, ClientError> {
1332        let user_id = self.inner.user_id().context("No User ID found")?;
1333        Ok(user_id.to_string())
1334    }
1335
1336    /// The server name part of the current user ID
1337    pub fn user_id_server_name(&self) -> Result<String, ClientError> {
1338        let user_id = self.inner.user_id().context("No User ID found")?;
1339        Ok(user_id.server_name().to_string())
1340    }
1341
1342    pub async fn display_name(&self) -> Result<String, ClientError> {
1343        let display_name =
1344            self.inner.account().get_display_name().await?.context("No User ID found")?;
1345        Ok(display_name)
1346    }
1347
1348    pub async fn upload_avatar(&self, mime_type: String, data: Vec<u8>) -> Result<(), ClientError> {
1349        let mime: Mime = mime_type.parse()?;
1350        self.inner.account().upload_avatar(&mime, data).await?;
1351        Ok(())
1352    }
1353
1354    pub async fn remove_avatar(&self) -> Result<(), ClientError> {
1355        self.inner.account().set_avatar_url(None).await?;
1356        Ok(())
1357    }
1358
1359    /// Sends a request to retrieve the avatar URL. Will fill the cache used by
1360    /// [`Self::cached_avatar_url`] on success.
1361    pub async fn avatar_url(&self) -> Result<Option<String>, ClientError> {
1362        let avatar_url = self.inner.account().get_avatar_url().await?;
1363
1364        Ok(avatar_url.map(|u| u.to_string()))
1365    }
1366
1367    /// Retrieves an avatar cached from a previous call to [`Self::avatar_url`].
1368    pub async fn cached_avatar_url(&self) -> Result<Option<String>, ClientError> {
1369        Ok(self.inner.account().get_cached_avatar_url().await?.map(Into::into))
1370    }
1371
1372    pub fn device_id(&self) -> Result<String, ClientError> {
1373        let device_id = self.inner.device_id().context("No Device ID found")?;
1374        Ok(device_id.to_string())
1375    }
1376
1377    pub async fn create_room(&self, request: CreateRoomParameters) -> Result<String, ClientError> {
1378        let response = self.inner.create_room(request.try_into()?).await?;
1379        Ok(String::from(response.room_id()))
1380    }
1381
1382    /// Get the content of the event of the given type out of the account data
1383    /// store.
1384    ///
1385    /// It will be returned as a JSON string.
1386    pub async fn account_data(&self, event_type: String) -> Result<Option<String>, ClientError> {
1387        let event = self.inner.account().account_data_raw(event_type.into()).await?;
1388        Ok(event.map(|e| e.json().get().to_owned()))
1389    }
1390
1391    /// Set the given account data content for the given event type.
1392    ///
1393    /// It should be supplied as a JSON string.
1394    pub async fn set_account_data(
1395        &self,
1396        event_type: String,
1397        content: String,
1398    ) -> Result<(), ClientError> {
1399        let raw_content = Raw::from_json_string(content)?;
1400        self.inner.account().set_account_data_raw(event_type.into(), raw_content).await?;
1401        Ok(())
1402    }
1403
1404    pub async fn upload_media(
1405        &self,
1406        mime_type: String,
1407        data: Vec<u8>,
1408        progress_watcher: Option<Box<dyn ProgressWatcher>>,
1409    ) -> Result<String, ClientError> {
1410        let mime_type: mime::Mime = mime_type.parse().context("Parsing mime type")?;
1411        let request = self.inner.media().upload(&mime_type, data, None);
1412
1413        if let Some(progress_watcher) = progress_watcher {
1414            let mut subscriber = request.subscribe_to_send_progress();
1415            get_runtime_handle().spawn(async move {
1416                while let Some(progress) = subscriber.next().await {
1417                    progress_watcher.transmission_progress(progress.into());
1418                }
1419            });
1420        }
1421
1422        let response = request.await?;
1423
1424        Ok(String::from(response.content_uri))
1425    }
1426
1427    pub async fn get_media_content(
1428        &self,
1429        media_source: Arc<MediaSource>,
1430    ) -> Result<Vec<u8>, ClientError> {
1431        let source = (*media_source).clone().media_source;
1432
1433        debug!(?source, "requesting media file");
1434        Ok(self
1435            .inner
1436            .media()
1437            .get_media_content(&MediaRequestParameters { source, format: MediaFormat::File }, true)
1438            .await?)
1439    }
1440
1441    pub async fn get_media_thumbnail(
1442        &self,
1443        media_source: Arc<MediaSource>,
1444        width: u64,
1445        height: u64,
1446    ) -> Result<Vec<u8>, ClientError> {
1447        let source = (*media_source).clone().media_source;
1448
1449        debug!(?source, width, height, "requesting media thumbnail");
1450        Ok(self
1451            .inner
1452            .media()
1453            .get_media_content(
1454                &MediaRequestParameters {
1455                    source,
1456                    format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(
1457                        UInt::new(width).unwrap(),
1458                        UInt::new(height).unwrap(),
1459                    )),
1460                },
1461                true,
1462            )
1463            .await?)
1464    }
1465
1466    pub async fn get_session_verification_controller(
1467        &self,
1468    ) -> Result<Arc<SessionVerificationController>, ClientError> {
1469        if let Some(session_verification_controller) =
1470            &*self.session_verification_controller.read().await
1471        {
1472            return Ok(Arc::new(session_verification_controller.clone()));
1473        }
1474        let user_id = self.inner.user_id().context("Failed retrieving current user_id")?;
1475        let user_identity = self
1476            .inner
1477            .encryption()
1478            .get_user_identity(user_id)
1479            .await?
1480            .context("Failed retrieving user identity")?;
1481
1482        let session_verification_controller = SessionVerificationController::new(
1483            self.inner.encryption(),
1484            user_identity,
1485            self.inner.account(),
1486        );
1487
1488        *self.session_verification_controller.write().await =
1489            Some(session_verification_controller.clone());
1490
1491        Ok(Arc::new(session_verification_controller))
1492    }
1493
1494    /// Log the current user out.
1495    pub async fn logout(&self) -> Result<(), ClientError> {
1496        Ok(self.inner.logout().await?)
1497    }
1498
1499    /// Registers a pusher with given parameters
1500    pub async fn set_pusher(
1501        &self,
1502        identifiers: PusherIdentifiers,
1503        kind: PusherKind,
1504        app_display_name: String,
1505        device_display_name: String,
1506        profile_tag: Option<String>,
1507        lang: String,
1508    ) -> Result<(), ClientError> {
1509        let ids = identifiers.into();
1510
1511        let pusher_init = PusherInit {
1512            ids,
1513            kind: kind.try_into()?,
1514            app_display_name,
1515            device_display_name,
1516            profile_tag,
1517            lang,
1518        };
1519        self.inner.pusher().set(pusher_init.into()).await?;
1520        Ok(())
1521    }
1522
1523    /// Deletes a pusher of given pusher ids
1524    pub async fn delete_pusher(&self, identifiers: PusherIdentifiers) -> Result<(), ClientError> {
1525        self.inner.pusher().delete(identifiers.into()).await?;
1526        Ok(())
1527    }
1528
1529    /// The homeserver this client is configured to use.
1530    pub fn homeserver(&self) -> String {
1531        self.inner.homeserver().to_string()
1532    }
1533
1534    /// The URL of the server.
1535    ///
1536    /// Not to be confused with the `Self::homeserver`. `server` is usually
1537    /// the server part in a user ID, e.g. with `@mnt_io:matrix.org`, here
1538    /// `matrix.org` is the server, whilst `matrix-client.matrix.org` is the
1539    /// homeserver (at the time of writing — 2024-08-28).
1540    ///
1541    /// This value is optional depending on how the `Client` has been built.
1542    /// If it's been built from a homeserver URL directly, we don't know the
1543    /// server. However, if the `Client` has been built from a server URL or
1544    /// name, then the homeserver has been discovered, and we know both.
1545    pub fn server(&self) -> Option<String> {
1546        self.inner.server().map(ToString::to_string)
1547    }
1548
1549    pub fn rooms(&self) -> Vec<Arc<Room>> {
1550        self.inner
1551            .rooms()
1552            .into_iter()
1553            .map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1554            .collect()
1555    }
1556
1557    /// Get a room by its ID.
1558    ///
1559    /// # Arguments
1560    ///
1561    /// * `room_id` - The ID of the room to get.
1562    ///
1563    /// # Returns
1564    ///
1565    /// A `Result` containing an optional room, or a `ClientError`.
1566    /// This method will not initialize the room's timeline or populate it with
1567    /// events.
1568    pub fn get_room(&self, room_id: String) -> Result<Option<Arc<Room>>, ClientError> {
1569        let room_id = RoomId::parse(room_id)?;
1570        let sdk_room = self.inner.get_room(&room_id);
1571
1572        let room =
1573            sdk_room.map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())));
1574        Ok(room)
1575    }
1576
1577    pub fn get_dm_room(&self, user_id: String) -> Result<Option<Arc<Room>>, ClientError> {
1578        let user_id = UserId::parse(user_id)?;
1579        let sdk_room = self.inner.get_dm_room(&user_id);
1580        let dm =
1581            sdk_room.map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())));
1582        Ok(dm)
1583    }
1584
1585    pub async fn search_users(
1586        &self,
1587        search_term: String,
1588        limit: u64,
1589    ) -> Result<SearchUsersResults, ClientError> {
1590        let response = self.inner.search_users(&search_term, limit).await?;
1591        Ok(SearchUsersResults::from(response))
1592    }
1593
1594    pub async fn get_profile(&self, user_id: String) -> Result<UserProfile, ClientError> {
1595        let user_id = <&UserId>::try_from(user_id.as_str())?;
1596        UserProfile::fetch(&self.inner.account(), user_id).await
1597    }
1598
1599    pub async fn notification_client(
1600        self: Arc<Self>,
1601        process_setup: NotificationProcessSetup,
1602    ) -> Result<Arc<NotificationClient>, ClientError> {
1603        Ok(Arc::new(NotificationClient {
1604            inner: MatrixNotificationClient::new((*self.inner).clone(), process_setup.into())
1605                .await?,
1606            client: self.clone(),
1607        }))
1608    }
1609
1610    pub fn sync_service(&self) -> Arc<SyncServiceBuilder> {
1611        SyncServiceBuilder::new((*self.inner).clone(), self.utd_hook_manager.get().cloned())
1612    }
1613
1614    /// Start a sync v2 loop.
1615    ///
1616    /// This is an alternative to [`Client::sync_service`] (which uses Sliding
1617    /// Sync / MSC4186). It works with any homeserver, including older
1618    /// Synapse versions that do not support Sliding Sync.
1619    ///
1620    /// Returns a `TaskHandle` that can be used to cancel the sync loop.
1621    /// The listener is called after each successful sync response.
1622    pub fn sync_v2(
1623        &self,
1624        settings: SyncSettingsV2,
1625        listener: Box<dyn SyncListenerV2>,
1626    ) -> Arc<TaskHandle> {
1627        let client = (*self.inner).clone();
1628        let sdk_settings: matrix_sdk::config::SyncSettings = settings.into();
1629        let listener: Arc<dyn SyncListenerV2> = Arc::from(listener);
1630
1631        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
1632            let result = client
1633                .sync_with_result_callback(sdk_settings, |result| {
1634                    let listener = listener.clone();
1635                    async move {
1636                        let response = result?;
1637                        let ffi_response: SyncResponseV2 = response.into();
1638                        listener.on_update(ffi_response);
1639                        Ok(matrix_sdk::LoopCtrl::Continue)
1640                    }
1641                })
1642                .await;
1643
1644            if let Err(e) = result {
1645                tracing::error!("Sync loop ended with error: {e}");
1646            }
1647        })))
1648    }
1649
1650    /// Perform a single sync v2 call.
1651    ///
1652    /// This is useful for performing an initial sync or a one-shot sync
1653    /// without entering a continuous loop.
1654    pub async fn sync_once_v2(
1655        &self,
1656        settings: SyncSettingsV2,
1657    ) -> Result<SyncResponseV2, ClientError> {
1658        let sdk_settings: matrix_sdk::config::SyncSettings = settings.into();
1659        let response = self.inner.sync_once(sdk_settings).await?;
1660        Ok(response.into())
1661    }
1662
1663    pub async fn space_service(&self) -> Arc<SpaceService> {
1664        let inner = UISpaceService::new((*self.inner).clone()).await;
1665        Arc::new(SpaceService::new(inner))
1666    }
1667
1668    pub async fn get_notification_settings(&self) -> Arc<NotificationSettings> {
1669        let inner = self.inner.notification_settings().await;
1670
1671        Arc::new(NotificationSettings::new((*self.inner).clone(), inner))
1672    }
1673
1674    pub fn encryption(self: Arc<Self>) -> Arc<Encryption> {
1675        Arc::new(Encryption { inner: self.inner.encryption(), _client: self.clone() })
1676    }
1677
1678    // Ignored users
1679
1680    pub async fn ignored_users(&self) -> Result<Vec<String>, ClientError> {
1681        if let Some(raw_content) =
1682            self.inner.account().fetch_account_data_static::<IgnoredUserListEventContent>().await?
1683        {
1684            let content = raw_content.deserialize()?;
1685            let user_ids: Vec<String> =
1686                content.ignored_users.keys().map(|id| id.to_string()).collect();
1687
1688            return Ok(user_ids);
1689        }
1690
1691        Ok(vec![])
1692    }
1693
1694    pub async fn ignore_user(&self, user_id: String) -> Result<(), ClientError> {
1695        let user_id = UserId::parse(user_id)?;
1696        self.inner.account().ignore_user(&user_id).await?;
1697        Ok(())
1698    }
1699
1700    pub async fn unignore_user(&self, user_id: String) -> Result<(), ClientError> {
1701        let user_id = UserId::parse(user_id)?;
1702        self.inner.account().unignore_user(&user_id).await?;
1703        Ok(())
1704    }
1705
1706    pub fn subscribe_to_ignored_users(
1707        &self,
1708        listener: Box<dyn IgnoredUsersListener>,
1709    ) -> Arc<TaskHandle> {
1710        let mut subscriber = self.inner.subscribe_to_ignore_user_list_changes();
1711        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
1712            while let Some(user_ids) = subscriber.next().await {
1713                listener.call(user_ids);
1714            }
1715        })))
1716    }
1717
1718    pub fn room_directory_search(&self) -> Arc<RoomDirectorySearch> {
1719        Arc::new(RoomDirectorySearch::new(
1720            matrix_sdk::room_directory_search::RoomDirectorySearch::new((*self.inner).clone()),
1721        ))
1722    }
1723
1724    /// Join a room by its ID.
1725    ///
1726    /// Use this method when the homeserver already knows of the given room ID.
1727    /// Otherwise use `join_room_by_id_or_alias` so you can pass a list of
1728    /// server names for the homeserver to find the room.
1729    pub async fn join_room_by_id(&self, room_id: String) -> Result<Arc<Room>, ClientError> {
1730        let room_id = RoomId::parse(room_id)?;
1731        let room = self.inner.join_room_by_id(room_id.as_ref()).await?;
1732        Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1733    }
1734
1735    /// Join a room by its ID or alias.
1736    ///
1737    /// When supplying the room's ID, you can also supply a list of server names
1738    /// for the homeserver to find the room. Typically these server names
1739    /// come from a permalink's `via` parameters, or from resolving a room's
1740    /// alias into an ID.
1741    pub async fn join_room_by_id_or_alias(
1742        &self,
1743        room_id_or_alias: String,
1744        server_names: Vec<String>,
1745    ) -> Result<Arc<Room>, ClientError> {
1746        let room_id = RoomOrAliasId::parse(&room_id_or_alias)?;
1747        let server_names = server_names
1748            .iter()
1749            .map(|name| OwnedServerName::try_from(name.as_str()))
1750            .collect::<Result<Vec<_>, _>>()?;
1751        let room =
1752            self.inner.join_room_by_id_or_alias(room_id.as_ref(), server_names.as_ref()).await?;
1753        Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1754    }
1755
1756    /// Knock on a room to join it using its ID or alias.
1757    pub async fn knock(
1758        &self,
1759        room_id_or_alias: String,
1760        reason: Option<String>,
1761        server_names: Vec<String>,
1762    ) -> Result<Arc<Room>, ClientError> {
1763        let room_id = RoomOrAliasId::parse(&room_id_or_alias)?;
1764        let server_names =
1765            server_names.iter().map(ServerName::parse).collect::<Result<Vec<_>, _>>()?;
1766        let room = self.inner.knock(room_id, reason, server_names).await?;
1767        Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1768    }
1769
1770    pub async fn get_recently_visited_rooms(&self) -> Result<Vec<String>, ClientError> {
1771        Ok(self
1772            .inner
1773            .account()
1774            .get_recently_visited_rooms()
1775            .await?
1776            .into_iter()
1777            .map(Into::into)
1778            .collect())
1779    }
1780
1781    pub async fn track_recently_visited_room(&self, room: String) -> Result<(), ClientError> {
1782        let room_id = RoomId::parse(room)?;
1783        self.inner.account().track_recently_visited_room(room_id).await?;
1784        Ok(())
1785    }
1786
1787    /// Resolves the given room alias to a room ID (and a list of servers), if
1788    /// possible.
1789    pub async fn resolve_room_alias(
1790        &self,
1791        room_alias: String,
1792    ) -> Result<Option<ResolvedRoomAlias>, ClientError> {
1793        let room_alias = RoomAliasId::parse(&room_alias)?;
1794        match self.inner.resolve_room_alias(&room_alias).await {
1795            Ok(response) => Ok(Some(response.into())),
1796            Err(error) => match error.client_api_error_kind() {
1797                // The room alias wasn't found, so we return None.
1798                Some(ErrorKind::NotFound) => Ok(None),
1799                // In any other case we just return the error, mapped.
1800                _ => Err(error.into()),
1801            },
1802        }
1803    }
1804
1805    /// Checks if a room alias exists in the current homeserver.
1806    pub async fn room_alias_exists(&self, room_alias: String) -> Result<bool, ClientError> {
1807        self.resolve_room_alias(room_alias).await.map(|ret| ret.is_some())
1808    }
1809
1810    /// Given a room id, get the preview of a room, to interact with it.
1811    ///
1812    /// The list of `via_servers` must be a list of servers that know
1813    /// about the room and can resolve it, and that may appear as a `via`
1814    /// parameter in e.g. a permalink URL. This list can be empty.
1815    pub async fn get_room_preview_from_room_id(
1816        &self,
1817        room_id: String,
1818        via_servers: Vec<String>,
1819    ) -> Result<Arc<RoomPreview>, ClientError> {
1820        let room_id = RoomId::parse(&room_id).context("room_id is not a valid room id")?;
1821
1822        let via_servers = via_servers
1823            .into_iter()
1824            .map(ServerName::parse)
1825            .collect::<Result<Vec<_>, _>>()
1826            .context("at least one `via` server name is invalid")?;
1827
1828        // The `into()` call below doesn't work if I do `(&room_id).into()`, so I let
1829        // rustc win that one fight.
1830        let room_id: &RoomId = &room_id;
1831
1832        let room_preview = self.inner.get_room_preview(room_id.into(), via_servers).await?;
1833
1834        Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
1835    }
1836
1837    /// Given a room alias, get the preview of a room, to interact with it.
1838    pub async fn get_room_preview_from_room_alias(
1839        &self,
1840        room_alias: String,
1841    ) -> Result<Arc<RoomPreview>, ClientError> {
1842        let room_alias =
1843            RoomAliasId::parse(&room_alias).context("room_alias is not a valid room alias")?;
1844
1845        // The `into()` call below doesn't work if I do `(&room_id).into()`, so I let
1846        // rustc win that one fight.
1847        let room_alias: &RoomAliasId = &room_alias;
1848
1849        let room_preview = self.inner.get_room_preview(room_alias.into(), Vec::new()).await?;
1850
1851        Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
1852    }
1853
1854    /// Waits until an at least partially synced room is received, and returns
1855    /// it.
1856    ///
1857    /// **Note: this function will loop endlessly until either it finds the room
1858    /// or an externally set timeout happens.**
1859    pub async fn await_room_remote_echo(&self, room_id: String) -> Result<Arc<Room>, ClientError> {
1860        let room_id = RoomId::parse(room_id)?;
1861        Ok(Arc::new(Room::new(
1862            self.inner.await_room_remote_echo(&room_id).await,
1863            self.utd_hook_manager.get().cloned(),
1864        )))
1865    }
1866
1867    /// Lets the user know whether this is an `m.login.password` based
1868    /// auth and if the account can actually be deactivated
1869    pub fn can_deactivate_account(&self) -> bool {
1870        matches!(self.inner.auth_api(), Some(AuthApi::Matrix(_)))
1871    }
1872
1873    /// Deactivate this account definitively.
1874    /// Similarly to `encryption::reset_identity` this
1875    /// will only work with password-based authentication (`m.login.password`)
1876    ///
1877    /// # Arguments
1878    ///
1879    /// * `auth_data` - This request uses the [User-Interactive Authentication
1880    ///   API][uiaa]. The first request needs to set this to `None` and will
1881    ///   always fail and the same request needs to be made but this time with
1882    ///   some `auth_data` provided.
1883    pub async fn deactivate_account(
1884        &self,
1885        auth_data: Option<AuthData>,
1886        erase_data: bool,
1887    ) -> Result<(), ClientError> {
1888        if let Some(auth_data) = auth_data {
1889            _ = self.inner.account().deactivate(None, Some(auth_data.into()), erase_data).await?;
1890        } else {
1891            _ = self.inner.account().deactivate(None, None, erase_data).await?;
1892        }
1893
1894        Ok(())
1895    }
1896
1897    /// Checks if a room alias is not in use yet.
1898    ///
1899    /// Returns:
1900    /// - `Ok(true)` if the room alias is available.
1901    /// - `Ok(false)` if it's not (the resolve alias request returned a `404`
1902    ///   status code).
1903    /// - An `Err` otherwise.
1904    pub async fn is_room_alias_available(&self, alias: String) -> Result<bool, ClientError> {
1905        let alias = RoomAliasId::parse(alias)?;
1906        self.inner.is_room_alias_available(&alias).await.map_err(Into::into)
1907    }
1908
1909    /// Set the media retention policy.
1910    pub async fn set_media_retention_policy(
1911        &self,
1912        policy: MediaRetentionPolicy,
1913    ) -> Result<(), ClientError> {
1914        let closure = async || -> Result<_, Error> {
1915            let store = self.inner.media_store().lock().await?;
1916            Ok(store.set_media_retention_policy(policy).await?)
1917        };
1918
1919        Ok(closure().await?)
1920    }
1921
1922    /// Clear all the non-critical caches for this Client instance.
1923    ///
1924    /// WARNING: This will clear all the caches, including the base store (state
1925    /// store), so callers must make sure that the Client is at rest before
1926    /// calling it.
1927    ///
1928    /// In particular, if a [`SyncService`] is running, it must be passed here
1929    /// as a parameter, or stopped before calling this method. Ideally, the
1930    /// send queues should have been disabled and must all be inactive (i.e.
1931    /// not sending events); this method will disable them, but it might not
1932    /// be enough if the queues are still processing events.
1933    ///
1934    /// After the method returns, the Client will be in an unstable
1935    /// state, and it is required that the caller reinstantiates a new
1936    /// Client instance, be it via dropping the previous and re-creating it,
1937    /// restarting their application, or any other similar means.
1938    ///
1939    /// - This will get rid of the backing state store file, if provided.
1940    /// - This will empty all the room's persisted event caches, so all rooms
1941    ///   will start as if they were empty.
1942    /// - This will empty the media cache according to the current media
1943    ///   retention policy.
1944    pub async fn clear_caches(
1945        &self,
1946        sync_service: Option<Arc<SyncService>>,
1947    ) -> Result<(), ClientError> {
1948        let closure = async || -> Result<_, ClientError> {
1949            // First, make sure to expire sessions in the sync service.
1950            if let Some(sync_service) = sync_service {
1951                sync_service.inner.expire_sessions().await;
1952            }
1953
1954            // Disable the send queues, as they might read and write to the state store.
1955            // Events being send might still be active, and cause errors if
1956            // processing finishes, so this will only minimize damage. Since
1957            // this method should only be called in exceptional cases, this has
1958            // been deemed acceptable.
1959            self.inner.send_queue().set_enabled(false).await;
1960
1961            // Clean up the media cache according to the current media retention policy.
1962            self.inner
1963                .media_store()
1964                .lock()
1965                .await
1966                .map_err(Error::from)?
1967                .clean()
1968                .await
1969                .map_err(Error::from)?;
1970
1971            // Clear all the room chunks. It's important to *not* call
1972            // `EventCacheStore::clear_all_linked_chunks` here, because there might be live
1973            // observers of the linked chunks, and that would cause some very bad state
1974            // mismatch.
1975            self.inner.event_cache().clear_all_rooms().await?;
1976
1977            // Delete the state store file, if it exists.
1978            #[cfg(feature = "sqlite")]
1979            if let Some(store_path) = &self.store_path {
1980                debug!("Removing the state store: {}", store_path.display());
1981
1982                // The state store and the crypto store both live in the same store path, so we
1983                // can't blindly delete the directory.
1984                //
1985                // Delete the state store SQLite file, as well as the write-ahead log (WAL) and
1986                // shared-memory (SHM) files, if they exist.
1987
1988                for file_name in [
1989                    PathBuf::from(STATE_STORE_DATABASE_NAME),
1990                    PathBuf::from(format!("{STATE_STORE_DATABASE_NAME}.wal")),
1991                    PathBuf::from(format!("{STATE_STORE_DATABASE_NAME}.shm")),
1992                ] {
1993                    let file_path = store_path.join(file_name);
1994                    if file_path.exists() {
1995                        debug!("Removing file: {}", file_path.display());
1996                        std::fs::remove_file(&file_path).map_err(|err| ClientError::Generic {
1997                            msg: format!(
1998                                "couldn't delete the state store file {}: {err}",
1999                                file_path.display()
2000                            ),
2001                            details: None,
2002                        })?;
2003                    }
2004                }
2005            }
2006
2007            Ok(())
2008        };
2009
2010        closure().await
2011    }
2012
2013    /// Checks if the server supports the report room API.
2014    pub async fn is_report_room_api_supported(&self) -> Result<bool, ClientError> {
2015        Ok(self.inner.server_versions().await?.contains(&ruma::api::MatrixVersion::V1_13))
2016    }
2017
2018    /// Checks if the server supports the LiveKit RTC focus for placing calls.
2019    pub async fn is_livekit_rtc_supported(&self) -> Result<bool, ClientError> {
2020        Ok(self
2021            .inner
2022            .rtc_foci()
2023            .await?
2024            .iter()
2025            .any(|focus| matches!(focus, RtcFocusInfo::LiveKit(_))))
2026    }
2027
2028    /// Checks if the server supports login using a QR code.
2029    pub async fn is_login_with_qr_code_supported(&self) -> Result<bool, ClientError> {
2030        Ok(matches!(self.inner.auth_api(), Some(AuthApi::OAuth(_)))
2031            && self.inner.unstable_features().await?.contains(&ruma::api::FeatureFlag::Msc4108))
2032    }
2033
2034    /// Get server vendor information from the federation API.
2035    ///
2036    /// This method retrieves information about the server's name and version
2037    /// by calling the `/_matrix/federation/v1/version` endpoint.
2038    pub async fn server_vendor_info(&self) -> Result<matrix_sdk::ServerVendorInfo, ClientError> {
2039        Ok(self.inner.server_vendor_info(None).await?)
2040    }
2041
2042    /// Subscribe to changes in the media preview configuration.
2043    pub async fn subscribe_to_media_preview_config(
2044        &self,
2045        listener: Box<dyn MediaPreviewConfigListener>,
2046    ) -> Result<Arc<TaskHandle>, ClientError> {
2047        let (initial_value, stream) = self.inner.account().observe_media_preview_config().await?;
2048        Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
2049            // Send the initial value to the listener.
2050            listener.on_change(initial_value.map(|config| config.into()));
2051            // Listen for changes and notify the listener.
2052            pin_mut!(stream);
2053            while let Some(media_preview_config) = stream.next().await {
2054                listener.on_change(Some(media_preview_config.into()));
2055            }
2056        }))))
2057    }
2058
2059    /// Set the media previews timeline display policy
2060    pub async fn set_media_preview_display_policy(
2061        &self,
2062        policy: MediaPreviews,
2063    ) -> Result<(), ClientError> {
2064        self.inner.account().set_media_previews_display_policy(policy.into()).await?;
2065        Ok(())
2066    }
2067
2068    /// Get the media previews timeline display policy
2069    /// currently stored in the cache.
2070    pub async fn get_media_preview_display_policy(
2071        &self,
2072    ) -> Result<Option<MediaPreviews>, ClientError> {
2073        let configuration = self.inner.account().get_media_preview_config_event_content().await?;
2074        match configuration {
2075            Some(configuration) => Ok(configuration.media_previews.map(Into::into)),
2076            None => Ok(None),
2077        }
2078    }
2079
2080    /// Set the invite request avatars display policy
2081    pub async fn set_invite_avatars_display_policy(
2082        &self,
2083        policy: InviteAvatars,
2084    ) -> Result<(), ClientError> {
2085        self.inner.account().set_invite_avatars_display_policy(policy.into()).await?;
2086        Ok(())
2087    }
2088
2089    /// Get the invite request avatars display policy
2090    /// currently stored in the cache.
2091    pub async fn get_invite_avatars_display_policy(
2092        &self,
2093    ) -> Result<Option<InviteAvatars>, ClientError> {
2094        let configuration = self.inner.account().get_media_preview_config_event_content().await?;
2095        match configuration {
2096            Some(configuration) => Ok(configuration.invite_avatars.map(Into::into)),
2097            None => Ok(None),
2098        }
2099    }
2100
2101    /// Fetch the media preview configuration from the server.
2102    pub async fn fetch_media_preview_config(
2103        &self,
2104    ) -> Result<Option<MediaPreviewConfig>, ClientError> {
2105        Ok(self.inner.account().fetch_media_preview_config_event_content().await?.map(Into::into))
2106    }
2107
2108    /// Gets the `max_upload_size` value from the homeserver, which controls the
2109    /// max size a media upload request can have.
2110    pub async fn get_max_media_upload_size(&self) -> Result<u64, ClientError> {
2111        let max_upload_size = self.inner.load_or_fetch_max_upload_size().await?;
2112        Ok(max_upload_size.into())
2113    }
2114
2115    /// Subscribe to [`RoomInfo`] updates given a provided [`RoomId`].
2116    ///
2117    /// This works even for rooms we haven't received yet, so we can subscribe
2118    /// to this and wait until we receive updates from them when sync responses
2119    /// are processed.
2120    ///
2121    /// Note this method should be used sparingly since using callback
2122    /// interfaces is expensive, as well as keeping them alive for a long
2123    /// time. Usages of this method should be short-lived and dropped as
2124    /// soon as possible.
2125    pub async fn subscribe_to_room_info(
2126        &self,
2127        room_id: String,
2128        listener: Box<dyn RoomInfoListener>,
2129    ) -> Result<Arc<TaskHandle>, ClientError> {
2130        let room_id = RoomId::parse(room_id)?;
2131
2132        // Emit the initial event, if present
2133        if let Some(room) = self.inner.get_room(&room_id) {
2134            if let Ok(room_info) = RoomInfo::new(&room).await {
2135                listener.call(room_info);
2136            }
2137        }
2138
2139        Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn({
2140            let client = self.inner.clone();
2141            let mut receiver = client.room_info_notable_update_receiver();
2142            async move {
2143                while let Ok(room_update) = receiver.recv().await {
2144                    if room_update.room_id != room_id {
2145                        continue;
2146                    }
2147
2148                    if let Some(room) = client.get_room(&room_id) {
2149                        if let Ok(room_info) = RoomInfo::new(&room).await {
2150                            listener.call(room_info);
2151                        }
2152                    }
2153                }
2154            }
2155        }))))
2156    }
2157
2158    /// Whether to enable automatic backpagination under certain conditions
2159    /// (e.g. when processing read receipts).
2160    ///
2161    /// This is an experimental feature, and might cause performance issues on
2162    /// large accounts. Use with caution.
2163    pub fn enable_automatic_backpagination(&self) {
2164        self.inner.event_cache().config_mut().experimental_auto_backpagination = true;
2165    }
2166
2167    pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
2168        HomeserverCapabilities::new(self.inner.homeserver_capabilities())
2169    }
2170}
2171
2172#[cfg(feature = "experimental-element-recent-emojis")]
2173mod recent_emoji {
2174    use crate::{client::Client, error::ClientError};
2175
2176    /// Represents an emoji recently used for reactions.
2177    #[derive(Debug, uniffi::Record)]
2178    pub struct RecentEmoji {
2179        /// The actual emoji text representation.
2180        pub emoji: String,
2181        /// The number of times this emoji has been used for reactions.
2182        pub count: u64,
2183    }
2184
2185    #[matrix_sdk_ffi_macros::export]
2186    impl Client {
2187        /// Adds a recently used emoji to the list and uploads the updated
2188        /// `io.element.recent_emoji` content to the global account data.
2189        pub async fn add_recent_emoji(&self, emoji: String) -> Result<(), ClientError> {
2190            Ok(self.inner.account().add_recent_emoji(&emoji).await?)
2191        }
2192
2193        /// Gets the list of recently used emojis from the
2194        /// `io.element.recent_emoji` global account data.
2195        pub async fn get_recent_emojis(&self) -> Result<Vec<RecentEmoji>, ClientError> {
2196            Ok(self
2197                .inner
2198                .account()
2199                .get_recent_emojis(false)
2200                .await?
2201                .into_iter()
2202                .map(|(emoji, count)| RecentEmoji { emoji, count: count.into() })
2203                .collect::<Vec<RecentEmoji>>())
2204        }
2205    }
2206}
2207
2208#[matrix_sdk_ffi_macros::export(callback_interface)]
2209pub trait MediaPreviewConfigListener: SyncOutsideWasm + SendOutsideWasm {
2210    fn on_change(&self, media_preview_config: Option<MediaPreviewConfig>);
2211}
2212
2213#[matrix_sdk_ffi_macros::export(callback_interface)]
2214pub trait IgnoredUsersListener: SyncOutsideWasm + SendOutsideWasm {
2215    fn call(&self, ignored_user_ids: Vec<String>);
2216}
2217
2218#[derive(uniffi::Enum)]
2219pub enum NotificationProcessSetup {
2220    MultipleProcesses,
2221    SingleProcess { sync_service: Arc<SyncService> },
2222}
2223
2224impl From<NotificationProcessSetup> for MatrixNotificationProcessSetup {
2225    fn from(value: NotificationProcessSetup) -> Self {
2226        match value {
2227            NotificationProcessSetup::MultipleProcesses => {
2228                MatrixNotificationProcessSetup::MultipleProcesses
2229            }
2230            NotificationProcessSetup::SingleProcess { sync_service } => {
2231                MatrixNotificationProcessSetup::SingleProcess {
2232                    sync_service: sync_service.inner.clone(),
2233                }
2234            }
2235        }
2236    }
2237}
2238
2239/// Information about a room, that was resolved from a room alias.
2240#[derive(uniffi::Record)]
2241pub struct ResolvedRoomAlias {
2242    /// The room ID that the alias resolved to.
2243    pub room_id: String,
2244    /// A list of servers that can be used to find the room by its room ID.
2245    pub servers: Vec<String>,
2246}
2247
2248impl From<get_alias::v3::Response> for ResolvedRoomAlias {
2249    fn from(value: get_alias::v3::Response) -> Self {
2250        Self {
2251            room_id: value.room_id.to_string(),
2252            servers: value.servers.iter().map(ToString::to_string).collect(),
2253        }
2254    }
2255}
2256
2257#[derive(uniffi::Record)]
2258pub struct SearchUsersResults {
2259    pub results: Vec<UserProfile>,
2260    pub limited: bool,
2261}
2262
2263impl From<search_users::v3::Response> for SearchUsersResults {
2264    fn from(value: search_users::v3::Response) -> Self {
2265        let results: Vec<UserProfile> = value.results.iter().map(UserProfile::from).collect();
2266        SearchUsersResults { results, limited: value.limited }
2267    }
2268}
2269
2270#[derive(uniffi::Record)]
2271pub struct UserProfile {
2272    pub user_id: String,
2273    pub display_name: Option<String>,
2274    pub avatar_url: Option<String>,
2275}
2276
2277impl UserProfile {
2278    /// Fetch the profile for the given user ID, using the given [`Account`]
2279    /// API.
2280    pub(crate) async fn fetch(account: &Account, user_id: &UserId) -> Result<Self, ClientError> {
2281        let response = account.fetch_user_profile_of(user_id).await?;
2282        let display_name = response.get_static::<DisplayName>()?;
2283        let avatar_url = response.get_static::<AvatarUrl>()?.map(|url| url.to_string());
2284
2285        Ok(UserProfile { user_id: user_id.to_string(), display_name, avatar_url })
2286    }
2287}
2288
2289impl From<&search_users::v3::User> for UserProfile {
2290    fn from(value: &search_users::v3::User) -> Self {
2291        UserProfile {
2292            user_id: value.user_id.to_string(),
2293            display_name: value.display_name.clone(),
2294            avatar_url: value.avatar_url.as_ref().map(|url| url.to_string()),
2295        }
2296    }
2297}
2298
2299impl Client {
2300    fn process_session_change(&self, session_change: SessionChange) {
2301        if let Some(delegate_data) = self.delegate_data.get() {
2302            debug!("Applying session change: {session_change:?}");
2303            let delegate = delegate_data.delegate.clone();
2304            get_runtime_handle().spawn_blocking(move || match session_change {
2305                SessionChange::UnknownToken(unknown_token) => {
2306                    delegate.did_receive_auth_error(unknown_token.soft_logout);
2307                }
2308                SessionChange::TokensRefreshed => {}
2309            });
2310        } else {
2311            debug!(
2312                "No client delegate found, session change couldn't be applied: {session_change:?}"
2313            );
2314        }
2315    }
2316
2317    fn retrieve_session(
2318        session_delegate: Arc<dyn ClientSessionDelegate>,
2319        user_id: &UserId,
2320    ) -> anyhow::Result<SessionTokens> {
2321        Ok(session_delegate.retrieve_session_from_keychain(user_id.to_string())?.into_tokens())
2322    }
2323
2324    fn session_inner(client: matrix_sdk::Client) -> Result<Session, ClientError> {
2325        let auth_api = client.auth_api().context("Missing authentication API")?;
2326
2327        let homeserver_url = client.homeserver().into();
2328        let sliding_sync_version = client.sliding_sync_version();
2329
2330        Session::new(auth_api, homeserver_url, sliding_sync_version.into())
2331    }
2332
2333    fn save_session(
2334        session_delegate: Arc<dyn ClientSessionDelegate>,
2335        client: matrix_sdk::Client,
2336    ) -> anyhow::Result<()> {
2337        let session = Self::session_inner(client)?;
2338        session_delegate.save_session_in_keychain(session);
2339        Ok(())
2340    }
2341}
2342
2343/// Configure how many rooms will be restored when restoring the session with
2344/// [`Client::restore_session_with`].
2345///
2346/// Please, see the documentation of [`matrix_sdk::store::RoomLoadSettings`] to
2347/// learn more.
2348#[derive(uniffi::Enum)]
2349pub enum RoomLoadSettings {
2350    /// Load all rooms from the `StateStore` into the in-memory state store
2351    /// `BaseStateStore`.
2352    All,
2353
2354    /// Load a single room from the `StateStore` into the in-memory state
2355    /// store `BaseStateStore`.
2356    ///
2357    /// Please, be careful with this option. Read the documentation of
2358    /// [`RoomLoadSettings`].
2359    One { room_id: String },
2360}
2361
2362impl TryInto<SdkRoomLoadSettings> for RoomLoadSettings {
2363    type Error = String;
2364
2365    fn try_into(self) -> Result<SdkRoomLoadSettings, Self::Error> {
2366        Ok(match self {
2367            Self::All => SdkRoomLoadSettings::All,
2368            Self::One { room_id } => {
2369                SdkRoomLoadSettings::One(RoomId::parse(room_id).map_err(|error| error.to_string())?)
2370            }
2371        })
2372    }
2373}
2374
2375#[derive(uniffi::Record)]
2376pub struct NotificationPowerLevels {
2377    pub room: i32,
2378}
2379
2380impl From<NotificationPowerLevels> for ruma::power_levels::NotificationPowerLevels {
2381    fn from(value: NotificationPowerLevels) -> Self {
2382        let mut notification_power_levels = Self::new();
2383        notification_power_levels.room = value.room.into();
2384        notification_power_levels
2385    }
2386}
2387
2388#[derive(uniffi::Record)]
2389pub struct PowerLevels {
2390    pub users_default: Option<i32>,
2391    pub events_default: Option<i32>,
2392    pub state_default: Option<i32>,
2393    pub ban: Option<i32>,
2394    pub kick: Option<i32>,
2395    pub redact: Option<i32>,
2396    pub invite: Option<i32>,
2397    pub notifications: Option<NotificationPowerLevels>,
2398    pub users: HashMap<String, i32>,
2399    pub events: HashMap<String, i32>,
2400}
2401
2402impl From<PowerLevels> for RoomPowerLevelsContentOverride {
2403    fn from(value: PowerLevels) -> Self {
2404        let mut power_levels = RoomPowerLevelsContentOverride::default();
2405        power_levels.users_default = value.users_default.map(Into::into);
2406        power_levels.state_default = value.state_default.map(Into::into);
2407        power_levels.events_default = value.events_default.map(Into::into);
2408        power_levels.ban = value.ban.map(Into::into);
2409        power_levels.kick = value.kick.map(Into::into);
2410        power_levels.redact = value.redact.map(Into::into);
2411        power_levels.invite = value.invite.map(Into::into);
2412        power_levels.notifications = value.notifications.map(Into::into).unwrap_or_default();
2413        power_levels.users = value
2414            .users
2415            .iter()
2416            .filter_map(|(user_id, power_level)| match UserId::parse(user_id) {
2417                Ok(id) => Some((id, (*power_level).into())),
2418                Err(e) => {
2419                    error!(user_id, "Skipping invalid user ID, error: {e}");
2420                    None
2421                }
2422            })
2423            .collect();
2424        power_levels.events = value
2425            .events
2426            .iter()
2427            .map(|(event_type, power_level)| {
2428                let event_type: ruma::events::TimelineEventType = event_type.as_str().into();
2429                (event_type, (*power_level).into())
2430            })
2431            .collect();
2432        power_levels
2433    }
2434}
2435
2436#[derive(uniffi::Record)]
2437pub struct CreateRoomParameters {
2438    pub name: Option<String>,
2439    #[uniffi(default = None)]
2440    pub topic: Option<String>,
2441    pub is_encrypted: bool,
2442    #[uniffi(default = false)]
2443    pub is_direct: bool,
2444    pub visibility: RoomVisibility,
2445    pub preset: RoomPreset,
2446    #[uniffi(default = None)]
2447    pub invite: Option<Vec<String>>,
2448    #[uniffi(default = None)]
2449    pub avatar: Option<String>,
2450    #[uniffi(default = None)]
2451    pub power_level_content_override: Option<PowerLevels>,
2452    #[uniffi(default = None)]
2453    pub join_rule_override: Option<JoinRule>,
2454    #[uniffi(default = None)]
2455    pub history_visibility_override: Option<RoomHistoryVisibility>,
2456    #[uniffi(default = None)]
2457    pub canonical_alias: Option<String>,
2458    #[uniffi(default = false)]
2459    pub is_space: bool,
2460}
2461
2462impl TryFrom<CreateRoomParameters> for create_room::v3::Request {
2463    type Error = ClientError;
2464
2465    fn try_from(value: CreateRoomParameters) -> Result<create_room::v3::Request, Self::Error> {
2466        let mut request = create_room::v3::Request::new();
2467        request.name = value.name;
2468        request.topic = value.topic;
2469        request.is_direct = value.is_direct;
2470        request.visibility = value.visibility.into();
2471        request.preset = Some(value.preset.into());
2472        request.room_alias_name = value.canonical_alias;
2473        request.invite = match value.invite {
2474            Some(invite) => invite
2475                .iter()
2476                .filter_map(|user_id| match UserId::parse(user_id) {
2477                    Ok(id) => Some(id),
2478                    Err(e) => {
2479                        error!(user_id, "Skipping invalid user ID, error: {e}");
2480                        None
2481                    }
2482                })
2483                .collect(),
2484            None => vec![],
2485        };
2486
2487        let mut initial_state: Vec<Raw<AnyInitialStateEvent>> = vec![];
2488
2489        if value.is_encrypted {
2490            let content =
2491                RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
2492            initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2493        }
2494
2495        if let Some(url) = value.avatar {
2496            let mut content = RoomAvatarEventContent::new();
2497            content.url = Some(url.into());
2498            initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2499        }
2500
2501        if let Some(join_rule_override) = value.join_rule_override {
2502            let content = RoomJoinRulesEventContent::new(join_rule_override.try_into()?);
2503            initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2504        }
2505
2506        if let Some(history_visibility_override) = value.history_visibility_override {
2507            let content =
2508                RoomHistoryVisibilityEventContent::new(history_visibility_override.try_into()?);
2509            initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2510        }
2511
2512        request.initial_state = initial_state;
2513
2514        if value.is_space {
2515            let mut creation_content = CreationContent::new();
2516            creation_content.room_type = Some(RoomType::Space);
2517            request.creation_content = Some(Raw::new(&creation_content)?);
2518        }
2519
2520        if let Some(power_levels) = value.power_level_content_override {
2521            match Raw::<RoomPowerLevelsContentOverride>::new(&power_levels.into()) {
2522                Ok(power_levels) => {
2523                    request.power_level_content_override = Some(power_levels);
2524                }
2525                Err(e) => {
2526                    return Err(ClientError::Generic {
2527                        msg: format!("Failed to serialize power levels, error: {e}"),
2528                        details: Some(format!("{e:?}")),
2529                    });
2530                }
2531            }
2532        }
2533
2534        Ok(request)
2535    }
2536}
2537
2538#[derive(uniffi::Enum)]
2539pub enum RoomVisibility {
2540    /// Indicates that the room will be shown in the published room list.
2541    Public,
2542
2543    /// Indicates that the room will not be shown in the published room list.
2544    Private,
2545
2546    /// A custom value that's not present in the spec.
2547    Custom { value: String },
2548}
2549
2550impl From<RoomVisibility> for Visibility {
2551    fn from(value: RoomVisibility) -> Self {
2552        match value {
2553            RoomVisibility::Public => Self::Public,
2554            RoomVisibility::Private => Self::Private,
2555            RoomVisibility::Custom { value } => value.as_str().into(),
2556        }
2557    }
2558}
2559
2560impl From<Visibility> for RoomVisibility {
2561    fn from(value: Visibility) -> Self {
2562        match value {
2563            Visibility::Public => Self::Public,
2564            Visibility::Private => Self::Private,
2565            _ => Self::Custom { value: value.as_str().to_owned() },
2566        }
2567    }
2568}
2569
2570#[derive(uniffi::Enum)]
2571#[allow(clippy::enum_variant_names)]
2572pub enum RoomPreset {
2573    /// `join_rules` is set to `invite` and `history_visibility` is set to
2574    /// `shared`.
2575    PrivateChat,
2576
2577    /// `join_rules` is set to `public` and `history_visibility` is set to
2578    /// `shared`.
2579    PublicChat,
2580
2581    /// Same as `PrivateChat`, but all initial invitees get the same power level
2582    /// as the creator.
2583    TrustedPrivateChat,
2584}
2585
2586impl From<RoomPreset> for create_room::v3::RoomPreset {
2587    fn from(value: RoomPreset) -> Self {
2588        match value {
2589            RoomPreset::PrivateChat => Self::PrivateChat,
2590            RoomPreset::PublicChat => Self::PublicChat,
2591            RoomPreset::TrustedPrivateChat => Self::TrustedPrivateChat,
2592        }
2593    }
2594}
2595
2596#[derive(uniffi::Record)]
2597pub struct Session {
2598    // Same fields as the Session type in matrix-sdk, just simpler types
2599    /// The access token used for this session.
2600    pub access_token: String,
2601    /// The token used for [refreshing the access token], if any.
2602    ///
2603    /// [refreshing the access token]: https://spec.matrix.org/v1.3/client-server-api/#refreshing-access-tokens
2604    pub refresh_token: Option<String>,
2605    /// The user the access token was issued for.
2606    pub user_id: String,
2607    /// The ID of the client device.
2608    pub device_id: String,
2609
2610    // FFI-only fields (for now)
2611    /// The URL for the homeserver used for this session.
2612    pub homeserver_url: String,
2613    /// Additional data for this session if OpenID Connect was used for
2614    /// authentication.
2615    pub oidc_data: Option<String>,
2616    /// The sliding sync version used for this session.
2617    pub sliding_sync_version: SlidingSyncVersion,
2618}
2619
2620impl Session {
2621    fn new(
2622        auth_api: AuthApi,
2623        homeserver_url: String,
2624        sliding_sync_version: SlidingSyncVersion,
2625    ) -> Result<Session, ClientError> {
2626        match auth_api {
2627            // Build the session from the regular Matrix Auth Session.
2628            AuthApi::Matrix(a) => {
2629                let matrix_sdk::authentication::matrix::MatrixSession {
2630                    meta: matrix_sdk::SessionMeta { user_id, device_id },
2631                    tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2632                } = a.session().context("Missing session")?;
2633
2634                Ok(Session {
2635                    access_token,
2636                    refresh_token,
2637                    user_id: user_id.to_string(),
2638                    device_id: device_id.to_string(),
2639                    homeserver_url,
2640                    oidc_data: None,
2641                    sliding_sync_version,
2642                })
2643            }
2644            // Build the session from the OIDC UserSession.
2645            AuthApi::OAuth(api) => {
2646                let matrix_sdk::authentication::oauth::UserSession {
2647                    meta: matrix_sdk::SessionMeta { user_id, device_id },
2648                    tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2649                } = api.user_session().context("Missing session")?;
2650                let client_id = api.client_id().context("OIDC client ID is missing.")?.clone();
2651                let oidc_data = OidcSessionData { client_id };
2652
2653                let oidc_data = serde_json::to_string(&oidc_data).ok();
2654                Ok(Session {
2655                    access_token,
2656                    refresh_token,
2657                    user_id: user_id.to_string(),
2658                    device_id: device_id.to_string(),
2659                    homeserver_url,
2660                    oidc_data,
2661                    sliding_sync_version,
2662                })
2663            }
2664            _ => Err(anyhow!("Unknown authentication API").into()),
2665        }
2666    }
2667
2668    fn into_tokens(self) -> matrix_sdk::SessionTokens {
2669        SessionTokens { access_token: self.access_token, refresh_token: self.refresh_token }
2670    }
2671}
2672
2673impl TryFrom<Session> for AuthSession {
2674    type Error = ClientError;
2675    fn try_from(value: Session) -> Result<Self, Self::Error> {
2676        let Session {
2677            access_token,
2678            refresh_token,
2679            user_id,
2680            device_id,
2681            homeserver_url: _,
2682            oidc_data,
2683            sliding_sync_version: _,
2684        } = value;
2685
2686        if let Some(oidc_data) = oidc_data {
2687            // Create an OidcSession.
2688            let oidc_data = serde_json::from_str::<OidcSessionData>(&oidc_data)?;
2689
2690            let user_session = matrix_sdk::authentication::oauth::UserSession {
2691                meta: matrix_sdk::SessionMeta {
2692                    user_id: user_id.try_into()?,
2693                    device_id: device_id.into(),
2694                },
2695                tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2696            };
2697
2698            let session = OAuthSession { client_id: oidc_data.client_id, user: user_session };
2699
2700            Ok(AuthSession::OAuth(session.into()))
2701        } else {
2702            // Create a regular Matrix Session.
2703            let session = matrix_sdk::authentication::matrix::MatrixSession {
2704                meta: matrix_sdk::SessionMeta {
2705                    user_id: user_id.try_into()?,
2706                    device_id: device_id.into(),
2707                },
2708                tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2709            };
2710
2711            Ok(AuthSession::Matrix(session))
2712        }
2713    }
2714}
2715
2716/// Represents a client registration against an OpenID Connect authentication
2717/// issuer.
2718#[derive(Serialize, Deserialize)]
2719pub(crate) struct OidcSessionData {
2720    client_id: ClientId,
2721}
2722
2723#[derive(uniffi::Enum)]
2724pub enum AccountManagementAction {
2725    Profile,
2726    DevicesList,
2727    DeviceView { device_id: String },
2728    DeviceDelete { device_id: String },
2729    AccountDeactivate,
2730    CrossSigningReset,
2731}
2732
2733impl<'a> From<&'a AccountManagementAction> for AccountManagementActionData<'a> {
2734    fn from(value: &'a AccountManagementAction) -> Self {
2735        match value {
2736            AccountManagementAction::Profile => Self::Profile,
2737            AccountManagementAction::DevicesList => Self::DevicesList,
2738            AccountManagementAction::DeviceView { device_id } => {
2739                Self::DeviceView(DeviceViewData::new(device_id.as_str().into()))
2740            }
2741            AccountManagementAction::DeviceDelete { device_id } => {
2742                Self::DeviceDelete(DeviceDeleteData::new(device_id.as_str().into()))
2743            }
2744            AccountManagementAction::AccountDeactivate => Self::AccountDeactivate,
2745            AccountManagementAction::CrossSigningReset => Self::CrossSigningReset,
2746        }
2747    }
2748}
2749
2750#[matrix_sdk_ffi_macros::export]
2751fn gen_transaction_id() -> String {
2752    TransactionId::new().to_string()
2753}
2754
2755/// A file handle that takes ownership of a media file on disk. When the handle
2756/// is dropped, the file will be removed from the disk.
2757#[derive(uniffi::Object)]
2758pub struct MediaFileHandle {
2759    #[cfg(not(target_family = "wasm"))]
2760    inner: std::sync::RwLock<Option<SdkMediaFileHandle>>,
2761}
2762
2763impl MediaFileHandle {
2764    #[cfg(not(target_family = "wasm"))]
2765    fn new(handle: SdkMediaFileHandle) -> Self {
2766        Self { inner: std::sync::RwLock::new(Some(handle)) }
2767    }
2768}
2769
2770#[matrix_sdk_ffi_macros::export]
2771impl MediaFileHandle {
2772    /// Get the media file's path.
2773    pub fn path(&self) -> Result<String, ClientError> {
2774        #[cfg(not(target_family = "wasm"))]
2775        return Ok(self
2776            .inner
2777            .read()
2778            .unwrap()
2779            .as_ref()
2780            .context("MediaFileHandle must not be used after calling persist")?
2781            .path()
2782            .to_str()
2783            .unwrap()
2784            .to_owned());
2785        #[cfg(target_family = "wasm")]
2786        Err(ClientError::Generic {
2787            msg: "MediaFileHandle.path() is not supported on WASM targets".to_string(),
2788            details: None,
2789        })
2790    }
2791
2792    pub fn persist(&self, path: String) -> Result<bool, ClientError> {
2793        #[cfg(not(target_family = "wasm"))]
2794        {
2795            let mut guard = self.inner.write().unwrap();
2796            Ok(
2797                match guard
2798                    .take()
2799                    .context("MediaFileHandle was already persisted")?
2800                    .persist(path.as_ref())
2801                {
2802                    Ok(_) => true,
2803                    Err(e) => {
2804                        *guard = Some(e.file);
2805                        false
2806                    }
2807                },
2808            )
2809        }
2810        #[cfg(target_family = "wasm")]
2811        Err(ClientError::Generic {
2812            msg: "MediaFileHandle.persist() is not supported on WASM targets".to_string(),
2813            details: None,
2814        })
2815    }
2816}
2817
2818#[derive(Clone, uniffi::Enum)]
2819pub enum SlidingSyncVersion {
2820    None,
2821    Native,
2822}
2823
2824impl From<SdkSlidingSyncVersion> for SlidingSyncVersion {
2825    fn from(value: SdkSlidingSyncVersion) -> Self {
2826        match value {
2827            SdkSlidingSyncVersion::None => Self::None,
2828            SdkSlidingSyncVersion::Native => Self::Native,
2829        }
2830    }
2831}
2832
2833impl TryFrom<SlidingSyncVersion> for SdkSlidingSyncVersion {
2834    type Error = ClientError;
2835
2836    fn try_from(value: SlidingSyncVersion) -> Result<Self, Self::Error> {
2837        Ok(match value {
2838            SlidingSyncVersion::None => Self::None,
2839            SlidingSyncVersion::Native => Self::Native,
2840        })
2841    }
2842}
2843
2844#[derive(Clone, uniffi::Enum)]
2845pub enum OidcPrompt {
2846    /// The Authorization Server should prompt the End-User to create a user
2847    /// account.
2848    ///
2849    /// Defined in [Initiating User Registration via OpenID Connect](https://openid.net/specs/openid-connect-prompt-create-1_0.html).
2850    Create,
2851
2852    /// The Authorization Server should prompt the End-User for
2853    /// reauthentication.
2854    Login,
2855
2856    /// The Authorization Server should prompt the End-User for consent before
2857    /// returning information to the Client.
2858    Consent,
2859
2860    /// An unknown value.
2861    Unknown { value: String },
2862}
2863
2864impl From<RumaOidcPrompt> for OidcPrompt {
2865    fn from(value: RumaOidcPrompt) -> Self {
2866        match value {
2867            RumaOidcPrompt::Create => Self::Create,
2868            value => match value.as_str() {
2869                "consent" => Self::Consent,
2870                "login" => Self::Login,
2871                _ => Self::Unknown { value: value.to_string() },
2872            },
2873        }
2874    }
2875}
2876
2877impl From<OidcPrompt> for RumaOidcPrompt {
2878    fn from(value: OidcPrompt) -> Self {
2879        match value {
2880            OidcPrompt::Create => Self::Create,
2881            OidcPrompt::Consent => Self::from("consent"),
2882            OidcPrompt::Login => Self::from("login"),
2883            OidcPrompt::Unknown { value } => value.into(),
2884        }
2885    }
2886}
2887
2888/// The rule used for users wishing to join this room.
2889#[derive(Debug, Clone, uniffi::Enum)]
2890pub enum JoinRule {
2891    /// Anyone can join the room without any prior action.
2892    Public,
2893
2894    /// A user who wishes to join the room must first receive an invite to the
2895    /// room from someone already inside of the room.
2896    Invite,
2897
2898    /// Users can join the room if they are invited, or they can request an
2899    /// invite to the room.
2900    ///
2901    /// They can be allowed (invited) or denied (kicked/banned) access.
2902    Knock,
2903
2904    /// Reserved but not yet implemented by the Matrix specification.
2905    Private,
2906
2907    /// Users can join the room if they are invited, or if they meet any of the
2908    /// conditions described in a set of [`AllowRule`]s.
2909    Restricted { rules: Vec<AllowRule> },
2910
2911    /// Users can join the room if they are invited, or if they meet any of the
2912    /// conditions described in a set of [`AllowRule`]s, or they can request
2913    /// an invite to the room.
2914    KnockRestricted { rules: Vec<AllowRule> },
2915
2916    /// A custom join rule, up for interpretation by the consumer.
2917    Custom {
2918        /// The string representation for this custom rule.
2919        repr: String,
2920    },
2921}
2922
2923/// An allow rule which defines a condition that allows joining a room.
2924#[derive(Debug, Clone, uniffi::Enum)]
2925pub enum AllowRule {
2926    /// Only a member of the `room_id` Room can join the one this rule is used
2927    /// in.
2928    RoomMembership { room_id: String },
2929
2930    /// A custom allow rule implementation, containing its JSON representation
2931    /// as a `String`.
2932    Custom { json: String },
2933}
2934
2935impl TryFrom<JoinRule> for RumaJoinRule {
2936    type Error = ClientError;
2937
2938    fn try_from(value: JoinRule) -> Result<Self, Self::Error> {
2939        match value {
2940            JoinRule::Public => Ok(Self::Public),
2941            JoinRule::Invite => Ok(Self::Invite),
2942            JoinRule::Knock => Ok(Self::Knock),
2943            JoinRule::Private => Ok(Self::Private),
2944            JoinRule::Restricted { rules } => {
2945                let rules = ruma_allow_rules_from_ffi(rules)?;
2946                Ok(Self::Restricted(ruma::events::room::join_rules::Restricted::new(rules)))
2947            }
2948            JoinRule::KnockRestricted { rules } => {
2949                let rules = ruma_allow_rules_from_ffi(rules)?;
2950                Ok(Self::KnockRestricted(ruma::events::room::join_rules::Restricted::new(rules)))
2951            }
2952            JoinRule::Custom { repr } => Ok(serde_json::from_str(&repr)?),
2953        }
2954    }
2955}
2956
2957fn ruma_allow_rules_from_ffi(value: Vec<AllowRule>) -> Result<Vec<RumaAllowRule>, ClientError> {
2958    let mut ret = Vec::with_capacity(value.len());
2959    for rule in value {
2960        let rule: Result<RumaAllowRule, ClientError> = rule.try_into();
2961        match rule {
2962            Ok(rule) => ret.push(rule),
2963            Err(error) => return Err(error),
2964        }
2965    }
2966    Ok(ret)
2967}
2968
2969impl TryFrom<AllowRule> for RumaAllowRule {
2970    type Error = ClientError;
2971
2972    fn try_from(value: AllowRule) -> Result<Self, Self::Error> {
2973        match value {
2974            AllowRule::RoomMembership { room_id } => {
2975                let room_id = RoomId::parse(room_id)?;
2976                Ok(Self::RoomMembership(ruma::room::RoomMembership::new(room_id)))
2977            }
2978            AllowRule::Custom { json } => Ok(Self::_Custom(Box::new(serde_json::from_str(&json)?))),
2979        }
2980    }
2981}
2982
2983impl TryFrom<RumaJoinRule> for JoinRule {
2984    type Error = String;
2985    fn try_from(value: RumaJoinRule) -> Result<Self, Self::Error> {
2986        match value {
2987            RumaJoinRule::Knock => Ok(JoinRule::Knock),
2988            RumaJoinRule::Public => Ok(JoinRule::Public),
2989            RumaJoinRule::Private => Ok(JoinRule::Private),
2990            RumaJoinRule::KnockRestricted(restricted) => {
2991                let rules = restricted.allow.into_iter().map(TryInto::try_into).collect::<Result<
2992                    Vec<_>,
2993                    Self::Error,
2994                >>(
2995                )?;
2996                Ok(JoinRule::KnockRestricted { rules })
2997            }
2998            RumaJoinRule::Restricted(restricted) => {
2999                let rules = restricted.allow.into_iter().map(TryInto::try_into).collect::<Result<
3000                    Vec<_>,
3001                    Self::Error,
3002                >>(
3003                )?;
3004                Ok(JoinRule::Restricted { rules })
3005            }
3006            RumaJoinRule::Invite => Ok(JoinRule::Invite),
3007            RumaJoinRule::_Custom(_) => Ok(JoinRule::Custom { repr: value.as_str().to_owned() }),
3008            _ => Err(format!("Unknown JoinRule: {value:?}")),
3009        }
3010    }
3011}
3012
3013impl TryFrom<RumaAllowRule> for AllowRule {
3014    type Error = String;
3015    fn try_from(value: RumaAllowRule) -> Result<Self, Self::Error> {
3016        match value {
3017            RumaAllowRule::RoomMembership(membership) => {
3018                Ok(AllowRule::RoomMembership { room_id: membership.room_id.to_string() })
3019            }
3020            RumaAllowRule::_Custom(repr) => {
3021                let json = serde_json::to_string(&repr)
3022                    .map_err(|e| format!("Couldn't serialize custom AllowRule: {e:?}"))?;
3023                Ok(Self::Custom { json })
3024            }
3025            _ => Err(format!("Invalid AllowRule: {value:?}")),
3026        }
3027    }
3028}
3029
3030/// Contains the disk size of the different stores, if known. It won't be
3031/// available for in-memory stores.
3032#[derive(Debug, Clone, uniffi::Record)]
3033pub struct StoreSizes {
3034    /// The size of the CryptoStore.
3035    crypto_store: Option<u64>,
3036    /// The size of the StateStore.
3037    state_store: Option<u64>,
3038    /// The size of the EventCacheStore.
3039    event_cache_store: Option<u64>,
3040    /// The size of the MediaStore.
3041    media_store: Option<u64>,
3042}
3043
3044impl From<matrix_sdk::StoreSizes> for StoreSizes {
3045    fn from(value: matrix_sdk::StoreSizes) -> Self {
3046        Self {
3047            crypto_store: value.crypto_store.map(|v| v as u64),
3048            state_store: value.state_store.map(|v| v as u64),
3049            event_cache_store: value.event_cache_store.map(|v| v as u64),
3050            media_store: value.media_store.map(|v| v as u64),
3051        }
3052    }
3053}
3054
3055#[derive(uniffi::Object)]
3056pub struct HomeserverCapabilities {
3057    inner: matrix_sdk::HomeserverCapabilities,
3058}
3059
3060impl HomeserverCapabilities {
3061    pub(crate) fn new(capabilities: matrix_sdk::HomeserverCapabilities) -> Self {
3062        Self { inner: capabilities }
3063    }
3064}
3065
3066#[matrix_sdk_ffi_macros::export]
3067impl HomeserverCapabilities {
3068    pub async fn refresh(&self) -> Result<(), ClientError> {
3069        Ok(self.inner.refresh().await?)
3070    }
3071
3072    pub async fn can_change_password(&self) -> Result<bool, ClientError> {
3073        Ok(self.inner.can_change_password().await?)
3074    }
3075
3076    pub async fn can_change_displayname(&self) -> Result<bool, ClientError> {
3077        Ok(self.inner.can_change_displayname().await?)
3078    }
3079
3080    pub async fn can_change_avatar(&self) -> Result<bool, ClientError> {
3081        Ok(self.inner.can_change_avatar().await?)
3082    }
3083
3084    pub async fn can_change_thirdparty_ids(&self) -> Result<bool, ClientError> {
3085        Ok(self.inner.can_change_thirdparty_ids().await?)
3086    }
3087
3088    pub async fn can_get_login_token(&self) -> Result<bool, ClientError> {
3089        Ok(self.inner.can_get_login_token().await?)
3090    }
3091
3092    pub async fn extended_profile_fields(&self) -> Result<ExtendedProfileFields, ClientError> {
3093        let profile_fields = self.inner.extended_profile_fields().await?;
3094        Ok(ExtendedProfileFields {
3095            enabled: profile_fields.enabled,
3096            allowed: profile_fields
3097                .allowed
3098                .unwrap_or_default()
3099                .iter()
3100                .map(ToString::to_string)
3101                .collect(),
3102            disallowed: profile_fields
3103                .disallowed
3104                .unwrap_or_default()
3105                .iter()
3106                .map(ToString::to_string)
3107                .collect(),
3108        })
3109    }
3110
3111    pub async fn forgets_room_when_leaving(&self) -> Result<bool, ClientError> {
3112        Ok(self.inner.forgets_room_when_leaving().await?)
3113    }
3114}
3115
3116#[derive(uniffi::Record)]
3117pub struct ExtendedProfileFields {
3118    pub enabled: bool,
3119    pub allowed: Vec<String>,
3120    pub disallowed: Vec<String>,
3121}
3122
3123#[cfg(test)]
3124mod tests {
3125    use ruma::{
3126        api::client::room::{create_room, Visibility},
3127        events::StateEventType,
3128        room::RoomType,
3129    };
3130
3131    use crate::{
3132        client::{CreateRoomParameters, JoinRule, RoomPreset, RoomVisibility},
3133        room::RoomHistoryVisibility,
3134    };
3135
3136    #[test]
3137    fn test_create_room_parameters_mapping() {
3138        let params = CreateRoomParameters {
3139            name: Some("A room".to_owned()),
3140            topic: Some("A topic".to_owned()),
3141            is_encrypted: true,
3142            is_direct: true,
3143            visibility: RoomVisibility::Public,
3144            preset: RoomPreset::PublicChat,
3145            invite: Some(vec!["@user:example.com".to_owned()]),
3146            avatar: Some("http://example.com/avatar.jpg".to_owned()),
3147            power_level_content_override: None,
3148            join_rule_override: Some(JoinRule::Knock),
3149            history_visibility_override: Some(RoomHistoryVisibility::Shared),
3150            canonical_alias: Some("#a-room:example.com".to_owned()),
3151            is_space: true,
3152        };
3153
3154        let request: create_room::v3::Request =
3155            params.try_into().expect("CreateRoomParameters couldn't be transformed into a Request");
3156        let initial_state = request
3157            .initial_state
3158            .iter()
3159            .map(|raw| raw.deserialize().expect("Initial state event failed to deserialize"))
3160            .collect::<Vec<_>>();
3161
3162        assert_eq!(request.name, Some("A room".to_owned()));
3163        assert_eq!(request.topic, Some("A topic".to_owned()));
3164        assert!(initial_state.iter().any(|e| e.event_type() == StateEventType::RoomEncryption));
3165        assert!(request.is_direct);
3166        assert_eq!(request.visibility, Visibility::Public);
3167        assert_eq!(request.preset, Some(create_room::v3::RoomPreset::PublicChat));
3168        assert_eq!(request.invite.len(), 1);
3169        assert!(initial_state.iter().any(|e| e.event_type() == StateEventType::RoomAvatar));
3170        assert!(initial_state.iter().any(|e| e.event_type() == StateEventType::RoomJoinRules));
3171        assert!(initial_state
3172            .iter()
3173            .any(|e| e.event_type() == StateEventType::RoomHistoryVisibility));
3174        assert_eq!(request.room_alias_name, Some("#a-room:example.com".to_owned()));
3175
3176        let room_type = request
3177            .creation_content
3178            .expect("Creation content is missing")
3179            .deserialize()
3180            .expect("Creation content can't be deserialized")
3181            .room_type;
3182        assert_eq!(room_type, Some(RoomType::Space));
3183    }
3184}