matrix_sdk_ffi/
client.rs

1use std::{
2    collections::HashMap,
3    fmt::Debug,
4    sync::{Arc, RwLock},
5};
6
7use anyhow::{anyhow, Context as _};
8use async_compat::get_runtime_handle;
9use matrix_sdk::{
10    authentication::oauth::{
11        AccountManagementActionFull, ClientId, OAuthAuthorizationData, OAuthSession,
12    },
13    event_cache::EventCacheError,
14    media::{
15        MediaFileHandle as SdkMediaFileHandle, MediaFormat, MediaRequestParameters,
16        MediaRetentionPolicy, MediaThumbnailSettings,
17    },
18    ruma::{
19        api::client::{
20            discovery::get_authorization_server_metadata::msc2965::Prompt as RumaOidcPrompt,
21            push::{EmailPusherData, PusherIds, PusherInit, PusherKind as RumaPusherKind},
22            room::{create_room, Visibility},
23            session::get_login_types,
24            user_directory::search_users,
25        },
26        events::{
27            room::{
28                avatar::RoomAvatarEventContent, encryption::RoomEncryptionEventContent,
29                message::MessageType,
30            },
31            AnyInitialStateEvent, InitialStateEvent,
32        },
33        serde::Raw,
34        EventEncryptionAlgorithm, RoomId, TransactionId, UInt, UserId,
35    },
36    sliding_sync::Version as SdkSlidingSyncVersion,
37    AuthApi, AuthSession, Client as MatrixClient, SessionChange, SessionTokens,
38};
39use matrix_sdk_ui::notification_client::{
40    NotificationClient as MatrixNotificationClient,
41    NotificationProcessSetup as MatrixNotificationProcessSetup,
42};
43use mime::Mime;
44use ruma::{
45    api::client::{
46        alias::get_alias, discovery::discover_homeserver::AuthenticationServerInfo,
47        error::ErrorKind, uiaa::UserIdentifier,
48    },
49    events::{
50        ignored_user_list::IgnoredUserListEventContent,
51        key::verification::request::ToDeviceKeyVerificationRequestEvent,
52        room::{
53            history_visibility::RoomHistoryVisibilityEventContent,
54            join_rules::{
55                AllowRule as RumaAllowRule, JoinRule as RumaJoinRule, RoomJoinRulesEventContent,
56            },
57            message::OriginalSyncRoomMessageEvent,
58            power_levels::RoomPowerLevelsEventContent,
59        },
60        GlobalAccountDataEventType,
61    },
62    push::{HttpPusherData as RumaHttpPusherData, PushFormat as RumaPushFormat},
63    OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
64};
65use serde::{Deserialize, Serialize};
66use serde_json::{json, Value};
67use tokio::sync::broadcast::error::RecvError;
68use tracing::{debug, error};
69use url::Url;
70
71use super::{room::Room, session_verification::SessionVerificationController};
72use crate::{
73    authentication::{HomeserverLoginDetails, OidcConfiguration, OidcError, SsoError, SsoHandler},
74    client,
75    encryption::Encryption,
76    notification::NotificationClient,
77    notification_settings::NotificationSettings,
78    room::RoomHistoryVisibility,
79    room_directory_search::RoomDirectorySearch,
80    room_preview::RoomPreview,
81    ruma::{AuthData, MediaSource},
82    sync_service::{SyncService, SyncServiceBuilder},
83    task_handle::TaskHandle,
84    utils::AsyncRuntimeDropped,
85    ClientError,
86};
87
88#[derive(Clone, uniffi::Record)]
89pub struct PusherIdentifiers {
90    pub pushkey: String,
91    pub app_id: String,
92}
93
94impl From<PusherIdentifiers> for PusherIds {
95    fn from(value: PusherIdentifiers) -> Self {
96        Self::new(value.pushkey, value.app_id)
97    }
98}
99
100#[derive(Clone, uniffi::Record)]
101pub struct HttpPusherData {
102    pub url: String,
103    pub format: Option<PushFormat>,
104    pub default_payload: Option<String>,
105}
106
107#[derive(Clone, uniffi::Enum)]
108pub enum PusherKind {
109    Http { data: HttpPusherData },
110    Email,
111}
112
113impl TryFrom<PusherKind> for RumaPusherKind {
114    type Error = anyhow::Error;
115
116    fn try_from(value: PusherKind) -> anyhow::Result<Self> {
117        match value {
118            PusherKind::Http { data } => {
119                let mut ruma_data = RumaHttpPusherData::new(data.url);
120                if let Some(payload) = data.default_payload {
121                    let json: Value = serde_json::from_str(&payload)?;
122                    ruma_data.data.insert("default_payload".to_owned(), json);
123                }
124                ruma_data.format = data.format.map(Into::into);
125                Ok(Self::Http(ruma_data))
126            }
127            PusherKind::Email => {
128                let ruma_data = EmailPusherData::new();
129                Ok(Self::Email(ruma_data))
130            }
131        }
132    }
133}
134
135#[derive(Clone, uniffi::Enum)]
136pub enum PushFormat {
137    EventIdOnly,
138}
139
140impl From<PushFormat> for RumaPushFormat {
141    fn from(value: PushFormat) -> Self {
142        match value {
143            client::PushFormat::EventIdOnly => Self::EventIdOnly,
144        }
145    }
146}
147
148#[matrix_sdk_ffi_macros::export(callback_interface)]
149pub trait ClientDelegate: Sync + Send {
150    fn did_receive_auth_error(&self, is_soft_logout: bool);
151    fn did_refresh_tokens(&self);
152}
153
154#[matrix_sdk_ffi_macros::export(callback_interface)]
155pub trait ClientSessionDelegate: Sync + Send {
156    fn retrieve_session_from_keychain(&self, user_id: String) -> Result<Session, ClientError>;
157    fn save_session_in_keychain(&self, session: Session);
158}
159
160#[matrix_sdk_ffi_macros::export(callback_interface)]
161pub trait ProgressWatcher: Send + Sync {
162    fn transmission_progress(&self, progress: TransmissionProgress);
163}
164
165/// A listener to the global (client-wide) error reporter of the send queue.
166#[matrix_sdk_ffi_macros::export(callback_interface)]
167pub trait SendQueueRoomErrorListener: Sync + Send {
168    /// Called every time the send queue has ran into an error for a given room,
169    /// which will disable the send queue for that particular room.
170    fn on_error(&self, room_id: String, error: ClientError);
171}
172
173#[derive(Clone, Copy, uniffi::Record)]
174pub struct TransmissionProgress {
175    pub current: u64,
176    pub total: u64,
177}
178
179impl From<matrix_sdk::TransmissionProgress> for TransmissionProgress {
180    fn from(value: matrix_sdk::TransmissionProgress) -> Self {
181        Self {
182            current: value.current.try_into().unwrap_or(u64::MAX),
183            total: value.total.try_into().unwrap_or(u64::MAX),
184        }
185    }
186}
187
188#[derive(uniffi::Object)]
189pub struct Client {
190    pub(crate) inner: AsyncRuntimeDropped<MatrixClient>,
191    delegate: RwLock<Option<Arc<dyn ClientDelegate>>>,
192    session_verification_controller:
193        Arc<tokio::sync::RwLock<Option<SessionVerificationController>>>,
194}
195
196impl Client {
197    pub async fn new(
198        sdk_client: MatrixClient,
199        enable_oidc_refresh_lock: bool,
200        session_delegate: Option<Arc<dyn ClientSessionDelegate>>,
201    ) -> Result<Self, ClientError> {
202        let session_verification_controller: Arc<
203            tokio::sync::RwLock<Option<SessionVerificationController>>,
204        > = Default::default();
205        let controller = session_verification_controller.clone();
206        sdk_client.add_event_handler(
207            move |event: ToDeviceKeyVerificationRequestEvent| async move {
208                if let Some(session_verification_controller) = &*controller.clone().read().await {
209                    session_verification_controller
210                        .process_incoming_verification_request(
211                            &event.sender,
212                            event.content.transaction_id,
213                        )
214                        .await;
215                }
216            },
217        );
218
219        let controller = session_verification_controller.clone();
220        sdk_client.add_event_handler(move |event: OriginalSyncRoomMessageEvent| async move {
221            if let MessageType::VerificationRequest(_) = &event.content.msgtype {
222                if let Some(session_verification_controller) = &*controller.clone().read().await {
223                    session_verification_controller
224                        .process_incoming_verification_request(&event.sender, event.event_id)
225                        .await;
226                }
227            }
228        });
229
230        let cross_process_store_locks_holder_name =
231            sdk_client.cross_process_store_locks_holder_name().to_owned();
232
233        let client = Client {
234            inner: AsyncRuntimeDropped::new(sdk_client),
235            delegate: RwLock::new(None),
236            session_verification_controller,
237        };
238
239        if enable_oidc_refresh_lock {
240            if session_delegate.is_none() {
241                return Err(anyhow::anyhow!(
242                    "missing session delegates when enabling the cross-process lock"
243                ))?;
244            }
245
246            client
247                .inner
248                .oauth()
249                .enable_cross_process_refresh_lock(cross_process_store_locks_holder_name)
250                .await?;
251        }
252
253        if let Some(session_delegate) = session_delegate {
254            client.inner.set_session_callbacks(
255                {
256                    let session_delegate = session_delegate.clone();
257                    Box::new(move |client| {
258                        let session_delegate = session_delegate.clone();
259                        let user_id = client.user_id().context("user isn't logged in")?;
260                        Ok(Self::retrieve_session(session_delegate, user_id)?)
261                    })
262                },
263                {
264                    let session_delegate = session_delegate.clone();
265                    Box::new(move |client| {
266                        let session_delegate = session_delegate.clone();
267                        Ok(Self::save_session(session_delegate, client)?)
268                    })
269                },
270            )?;
271        }
272
273        Ok(client)
274    }
275}
276
277#[matrix_sdk_ffi_macros::export]
278impl Client {
279    /// Information about login options for the client's homeserver.
280    pub async fn homeserver_login_details(&self) -> Arc<HomeserverLoginDetails> {
281        let oauth = self.inner.oauth();
282        let (supports_oidc_login, supported_oidc_prompts) = match oauth.server_metadata().await {
283            Ok(metadata) => {
284                let prompts =
285                    metadata.prompt_values_supported.into_iter().map(Into::into).collect();
286
287                (true, prompts)
288            }
289            Err(error) => {
290                error!("Failed to fetch OIDC provider metadata: {error}");
291                (false, Default::default())
292            }
293        };
294
295        let supports_password_login = self.supports_password_login().await.ok().unwrap_or(false);
296        let sliding_sync_version = self.sliding_sync_version();
297
298        Arc::new(HomeserverLoginDetails {
299            url: self.homeserver(),
300            sliding_sync_version,
301            supports_oidc_login,
302            supported_oidc_prompts,
303            supports_password_login,
304        })
305    }
306
307    /// Login using a username and password.
308    pub async fn login(
309        &self,
310        username: String,
311        password: String,
312        initial_device_name: Option<String>,
313        device_id: Option<String>,
314    ) -> Result<(), ClientError> {
315        let mut builder = self.inner.matrix_auth().login_username(&username, &password);
316        if let Some(initial_device_name) = initial_device_name.as_ref() {
317            builder = builder.initial_device_display_name(initial_device_name);
318        }
319        if let Some(device_id) = device_id.as_ref() {
320            builder = builder.device_id(device_id);
321        }
322        builder.send().await?;
323        Ok(())
324    }
325
326    /// Login using JWT
327    /// This is an implementation of the custom_login https://docs.rs/matrix-sdk/latest/matrix_sdk/matrix_auth/struct.MatrixAuth.html#method.login_custom
328    /// For more information on logging in with JWT: https://element-hq.github.io/synapse/latest/jwt.html
329    pub async fn custom_login_with_jwt(
330        &self,
331        jwt: String,
332        initial_device_name: Option<String>,
333        device_id: Option<String>,
334    ) -> Result<(), ClientError> {
335        let data = json!({ "token": jwt }).as_object().unwrap().clone();
336
337        let mut builder = self.inner.matrix_auth().login_custom("org.matrix.login.jwt", data)?;
338
339        if let Some(initial_device_name) = initial_device_name.as_ref() {
340            builder = builder.initial_device_display_name(initial_device_name);
341        }
342
343        if let Some(device_id) = device_id.as_ref() {
344            builder = builder.device_id(device_id);
345        }
346
347        builder.send().await?;
348        Ok(())
349    }
350
351    /// Login using an email and password.
352    pub async fn login_with_email(
353        &self,
354        email: String,
355        password: String,
356        initial_device_name: Option<String>,
357        device_id: Option<String>,
358    ) -> Result<(), ClientError> {
359        let mut builder = self
360            .inner
361            .matrix_auth()
362            .login_identifier(UserIdentifier::Email { address: email }, &password);
363
364        if let Some(initial_device_name) = initial_device_name.as_ref() {
365            builder = builder.initial_device_display_name(initial_device_name);
366        }
367
368        if let Some(device_id) = device_id.as_ref() {
369            builder = builder.device_id(device_id);
370        }
371
372        builder.send().await?;
373
374        Ok(())
375    }
376
377    /// Returns a handler to start the SSO login process.
378    pub(crate) async fn start_sso_login(
379        self: &Arc<Self>,
380        redirect_url: String,
381        idp_id: Option<String>,
382    ) -> Result<Arc<SsoHandler>, SsoError> {
383        let auth = self.inner.matrix_auth();
384        let url = auth
385            .get_sso_login_url(redirect_url.as_str(), idp_id.as_deref())
386            .await
387            .map_err(|e| SsoError::Generic { message: e.to_string() })?;
388        Ok(Arc::new(SsoHandler { client: Arc::clone(self), url }))
389    }
390
391    /// Requests the URL needed for opening a web view using OIDC. Once the web
392    /// view has succeeded, call `login_with_oidc_callback` with the callback it
393    /// returns. If a failure occurs and a callback isn't available, make sure
394    /// to call `abort_oidc_auth` to inform the client of this.
395    ///
396    /// # Arguments
397    ///
398    /// * `oidc_configuration` - The configuration used to load the credentials
399    ///   of the client if it is already registered with the authorization
400    ///   server, or register the client and store its credentials if it isn't.
401    ///
402    /// * `prompt` - The desired user experience in the web UI. No value means
403    ///   that the user wishes to login into an existing account, and a value of
404    ///   `Create` means that the user wishes to register a new account.
405    pub async fn url_for_oidc(
406        &self,
407        oidc_configuration: &OidcConfiguration,
408        prompt: Option<OidcPrompt>,
409    ) -> Result<Arc<OAuthAuthorizationData>, OidcError> {
410        let registrations = oidc_configuration.registrations().await?;
411        let redirect_uri = oidc_configuration.redirect_uri()?;
412
413        let mut url_builder = self.inner.oauth().login(registrations.into(), redirect_uri, None);
414
415        if let Some(prompt) = prompt {
416            url_builder = url_builder.prompt(vec![prompt.into()]);
417        }
418
419        let data = url_builder.build().await?;
420
421        Ok(Arc::new(data))
422    }
423
424    /// Aborts an existing OIDC login operation that might have been cancelled,
425    /// failed etc.
426    pub async fn abort_oidc_auth(&self, authorization_data: Arc<OAuthAuthorizationData>) {
427        self.inner.oauth().abort_login(&authorization_data.state).await;
428    }
429
430    /// Completes the OIDC login process.
431    pub async fn login_with_oidc_callback(&self, callback_url: String) -> Result<(), OidcError> {
432        let url = Url::parse(&callback_url).or(Err(OidcError::CallbackUrlInvalid))?;
433
434        self.inner.oauth().finish_login(url.into()).await?;
435
436        Ok(())
437    }
438
439    pub async fn get_media_file(
440        &self,
441        media_source: Arc<MediaSource>,
442        filename: Option<String>,
443        mime_type: String,
444        use_cache: bool,
445        temp_dir: Option<String>,
446    ) -> Result<Arc<MediaFileHandle>, ClientError> {
447        let source = (*media_source).clone();
448        let mime_type: mime::Mime = mime_type.parse()?;
449
450        let handle = self
451            .inner
452            .media()
453            .get_media_file(
454                &MediaRequestParameters { source: source.media_source, format: MediaFormat::File },
455                filename,
456                &mime_type,
457                use_cache,
458                temp_dir,
459            )
460            .await?;
461
462        Ok(Arc::new(MediaFileHandle::new(handle)))
463    }
464
465    /// Restores the client from a `Session`.
466    pub async fn restore_session(&self, session: Session) -> Result<(), ClientError> {
467        let sliding_sync_version = session.sliding_sync_version.clone();
468        let auth_session: AuthSession = session.try_into()?;
469
470        self.restore_session_inner(auth_session).await?;
471        self.inner.set_sliding_sync_version(sliding_sync_version.try_into()?);
472
473        Ok(())
474    }
475
476    /// Enables or disables all the room send queues at once.
477    ///
478    /// When connectivity is lost on a device, it is recommended to disable the
479    /// room sending queues.
480    ///
481    /// This can be controlled for individual rooms, using
482    /// [`Room::enable_send_queue`].
483    pub async fn enable_all_send_queues(&self, enable: bool) {
484        self.inner.send_queue().set_enabled(enable).await;
485    }
486
487    /// Subscribe to the global enablement status of the send queue, at the
488    /// client-wide level.
489    ///
490    /// The given listener will be immediately called with the initial value of
491    /// the enablement status.
492    pub fn subscribe_to_send_queue_status(
493        &self,
494        listener: Box<dyn SendQueueRoomErrorListener>,
495    ) -> Arc<TaskHandle> {
496        let q = self.inner.send_queue();
497        let mut subscriber = q.subscribe_errors();
498
499        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
500            // Respawn tasks for rooms that had unsent events. At this point we've just
501            // created the subscriber, so it'll be notified about errors.
502            q.respawn_tasks_for_rooms_with_unsent_requests().await;
503
504            loop {
505                match subscriber.recv().await {
506                    Ok(report) => listener
507                        .on_error(report.room_id.to_string(), ClientError::new(report.error)),
508                    Err(err) => {
509                        error!("error when listening to the send queue error reporter: {err}");
510                    }
511                }
512            }
513        })))
514    }
515
516    /// Allows generic GET requests to be made through the SDKs internal HTTP
517    /// client
518    pub async fn get_url(&self, url: String) -> Result<String, ClientError> {
519        let http_client = self.inner.http_client();
520        Ok(http_client.get(url).send().await?.text().await?)
521    }
522
523    /// Empty the server version and unstable features cache.
524    ///
525    /// Since the SDK caches server capabilities (versions and unstable
526    /// features), it's possible to have a stale entry in the cache. This
527    /// functions makes it possible to force reset it.
528    pub async fn reset_server_capabilities(&self) -> Result<(), ClientError> {
529        Ok(self.inner.reset_server_capabilities().await?)
530    }
531}
532
533impl Client {
534    /// Restores the client from an `AuthSession`.
535    pub(crate) async fn restore_session_inner(
536        &self,
537        session: impl Into<AuthSession>,
538    ) -> anyhow::Result<()> {
539        self.inner.restore_session(session).await?;
540        Ok(())
541    }
542
543    /// Whether or not the client's homeserver supports the password login flow.
544    pub(crate) async fn supports_password_login(&self) -> anyhow::Result<bool> {
545        let login_types = self.inner.matrix_auth().get_login_types().await?;
546        let supports_password = login_types
547            .flows
548            .iter()
549            .any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Password(_)));
550        Ok(supports_password)
551    }
552}
553
554#[matrix_sdk_ffi_macros::export]
555impl Client {
556    /// The sliding sync version.
557    pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
558        self.inner.sliding_sync_version().into()
559    }
560
561    /// Find all sliding sync versions that are available.
562    ///
563    /// Be careful: This method may hit the store and will send new requests for
564    /// each call. It can be costly to call it repeatedly.
565    ///
566    /// If `.well-known` or `/versions` is unreachable, it will simply move
567    /// potential sliding sync versions aside. No error will be reported.
568    pub async fn available_sliding_sync_versions(&self) -> Vec<SlidingSyncVersion> {
569        self.inner.available_sliding_sync_versions().await.into_iter().map(Into::into).collect()
570    }
571
572    pub fn set_delegate(
573        self: Arc<Self>,
574        delegate: Option<Box<dyn ClientDelegate>>,
575    ) -> Option<Arc<TaskHandle>> {
576        delegate.map(|delegate| {
577            let mut session_change_receiver = self.inner.subscribe_to_session_changes();
578            let client_clone = self.clone();
579            let session_change_task = get_runtime_handle().spawn(async move {
580                loop {
581                    match session_change_receiver.recv().await {
582                        Ok(session_change) => client_clone.process_session_change(session_change),
583                        Err(receive_error) => {
584                            if let RecvError::Closed = receive_error {
585                                break;
586                            }
587                        }
588                    }
589                }
590            });
591
592            *self.delegate.write().unwrap() = Some(Arc::from(delegate));
593            Arc::new(TaskHandle::new(session_change_task))
594        })
595    }
596
597    pub fn session(&self) -> Result<Session, ClientError> {
598        Self::session_inner((*self.inner).clone())
599    }
600
601    pub async fn account_url(
602        &self,
603        action: Option<AccountManagementAction>,
604    ) -> Result<Option<String>, ClientError> {
605        if !matches!(self.inner.auth_api(), Some(AuthApi::OAuth(..))) {
606            return Ok(None);
607        }
608
609        let mut url_builder = match self.inner.oauth().account_management_url().await {
610            Ok(Some(url_builder)) => url_builder,
611            Ok(None) => return Ok(None),
612            Err(e) => {
613                error!("Failed retrieving account management URL: {e}");
614                return Err(e.into());
615            }
616        };
617
618        if let Some(action) = action {
619            url_builder = url_builder.action(action.into());
620        }
621
622        Ok(Some(url_builder.build().to_string()))
623    }
624
625    pub fn user_id(&self) -> Result<String, ClientError> {
626        let user_id = self.inner.user_id().context("No User ID found")?;
627        Ok(user_id.to_string())
628    }
629
630    /// The server name part of the current user ID
631    pub fn user_id_server_name(&self) -> Result<String, ClientError> {
632        let user_id = self.inner.user_id().context("No User ID found")?;
633        Ok(user_id.server_name().to_string())
634    }
635
636    pub async fn display_name(&self) -> Result<String, ClientError> {
637        let display_name =
638            self.inner.account().get_display_name().await?.context("No User ID found")?;
639        Ok(display_name)
640    }
641
642    pub async fn set_display_name(&self, name: String) -> Result<(), ClientError> {
643        self.inner
644            .account()
645            .set_display_name(Some(name.as_str()))
646            .await
647            .context("Unable to set display name")?;
648        Ok(())
649    }
650
651    pub async fn upload_avatar(&self, mime_type: String, data: Vec<u8>) -> Result<(), ClientError> {
652        let mime: Mime = mime_type.parse()?;
653        self.inner.account().upload_avatar(&mime, data).await?;
654        Ok(())
655    }
656
657    pub async fn remove_avatar(&self) -> Result<(), ClientError> {
658        self.inner.account().set_avatar_url(None).await?;
659        Ok(())
660    }
661
662    /// Sends a request to retrieve the avatar URL. Will fill the cache used by
663    /// [`Self::cached_avatar_url`] on success.
664    pub async fn avatar_url(&self) -> Result<Option<String>, ClientError> {
665        let avatar_url = self.inner.account().get_avatar_url().await?;
666
667        Ok(avatar_url.map(|u| u.to_string()))
668    }
669
670    /// Retrieves an avatar cached from a previous call to [`Self::avatar_url`].
671    pub async fn cached_avatar_url(&self) -> Result<Option<String>, ClientError> {
672        Ok(self.inner.account().get_cached_avatar_url().await?.map(Into::into))
673    }
674
675    pub fn device_id(&self) -> Result<String, ClientError> {
676        let device_id = self.inner.device_id().context("No Device ID found")?;
677        Ok(device_id.to_string())
678    }
679
680    pub async fn create_room(&self, request: CreateRoomParameters) -> Result<String, ClientError> {
681        let response = self.inner.create_room(request.try_into()?).await?;
682        Ok(String::from(response.room_id()))
683    }
684
685    /// Get the content of the event of the given type out of the account data
686    /// store.
687    ///
688    /// It will be returned as a JSON string.
689    pub async fn account_data(&self, event_type: String) -> Result<Option<String>, ClientError> {
690        let event = self.inner.account().account_data_raw(event_type.into()).await?;
691        Ok(event.map(|e| e.json().get().to_owned()))
692    }
693
694    /// Set the given account data content for the given event type.
695    ///
696    /// It should be supplied as a JSON string.
697    pub async fn set_account_data(
698        &self,
699        event_type: String,
700        content: String,
701    ) -> Result<(), ClientError> {
702        let raw_content = Raw::from_json_string(content)?;
703        self.inner.account().set_account_data_raw(event_type.into(), raw_content).await?;
704        Ok(())
705    }
706
707    pub async fn upload_media(
708        &self,
709        mime_type: String,
710        data: Vec<u8>,
711        progress_watcher: Option<Box<dyn ProgressWatcher>>,
712    ) -> Result<String, ClientError> {
713        let mime_type: mime::Mime = mime_type.parse().context("Parsing mime type")?;
714        let request = self.inner.media().upload(&mime_type, data, None);
715
716        if let Some(progress_watcher) = progress_watcher {
717            let mut subscriber = request.subscribe_to_send_progress();
718            get_runtime_handle().spawn(async move {
719                while let Some(progress) = subscriber.next().await {
720                    progress_watcher.transmission_progress(progress.into());
721                }
722            });
723        }
724
725        let response = request.await?;
726
727        Ok(String::from(response.content_uri))
728    }
729
730    pub async fn get_media_content(
731        &self,
732        media_source: Arc<MediaSource>,
733    ) -> Result<Vec<u8>, ClientError> {
734        let source = (*media_source).clone().media_source;
735
736        debug!(?source, "requesting media file");
737        Ok(self
738            .inner
739            .media()
740            .get_media_content(&MediaRequestParameters { source, format: MediaFormat::File }, true)
741            .await?)
742    }
743
744    pub async fn get_media_thumbnail(
745        &self,
746        media_source: Arc<MediaSource>,
747        width: u64,
748        height: u64,
749    ) -> Result<Vec<u8>, ClientError> {
750        let source = (*media_source).clone().media_source;
751
752        debug!(?source, width, height, "requesting media thumbnail");
753        Ok(self
754            .inner
755            .media()
756            .get_media_content(
757                &MediaRequestParameters {
758                    source,
759                    format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(
760                        UInt::new(width).unwrap(),
761                        UInt::new(height).unwrap(),
762                    )),
763                },
764                true,
765            )
766            .await?)
767    }
768
769    pub async fn get_session_verification_controller(
770        &self,
771    ) -> Result<Arc<SessionVerificationController>, ClientError> {
772        if let Some(session_verification_controller) =
773            &*self.session_verification_controller.read().await
774        {
775            return Ok(Arc::new(session_verification_controller.clone()));
776        }
777        let user_id = self.inner.user_id().context("Failed retrieving current user_id")?;
778        let user_identity = self
779            .inner
780            .encryption()
781            .get_user_identity(user_id)
782            .await?
783            .context("Failed retrieving user identity")?;
784
785        let session_verification_controller = SessionVerificationController::new(
786            self.inner.encryption(),
787            user_identity,
788            self.inner.account(),
789        );
790
791        *self.session_verification_controller.write().await =
792            Some(session_verification_controller.clone());
793
794        Ok(Arc::new(session_verification_controller))
795    }
796
797    /// Log the current user out.
798    pub async fn logout(&self) -> Result<(), ClientError> {
799        let Some(auth_api) = self.inner.auth_api() else {
800            return Err(anyhow!("Missing authentication API").into());
801        };
802
803        match auth_api {
804            AuthApi::Matrix(a) => {
805                tracing::info!("Logging out via the homeserver.");
806                a.logout().await?;
807                Ok(())
808            }
809
810            AuthApi::OAuth(api) => {
811                tracing::info!("Logging out via OAuth 2.0.");
812                api.logout().await?;
813                Ok(())
814            }
815            _ => Err(anyhow!("Unknown authentication API").into()),
816        }
817    }
818
819    /// Registers a pusher with given parameters
820    pub async fn set_pusher(
821        &self,
822        identifiers: PusherIdentifiers,
823        kind: PusherKind,
824        app_display_name: String,
825        device_display_name: String,
826        profile_tag: Option<String>,
827        lang: String,
828    ) -> Result<(), ClientError> {
829        let ids = identifiers.into();
830
831        let pusher_init = PusherInit {
832            ids,
833            kind: kind.try_into()?,
834            app_display_name,
835            device_display_name,
836            profile_tag,
837            lang,
838        };
839        self.inner.pusher().set(pusher_init.into()).await?;
840        Ok(())
841    }
842
843    /// Deletes a pusher of given pusher ids
844    pub async fn delete_pusher(&self, identifiers: PusherIdentifiers) -> Result<(), ClientError> {
845        self.inner.pusher().delete(identifiers.into()).await?;
846        Ok(())
847    }
848
849    /// The homeserver this client is configured to use.
850    pub fn homeserver(&self) -> String {
851        self.inner.homeserver().to_string()
852    }
853
854    /// The URL of the server.
855    ///
856    /// Not to be confused with the `Self::homeserver`. `server` is usually
857    /// the server part in a user ID, e.g. with `@mnt_io:matrix.org`, here
858    /// `matrix.org` is the server, whilst `matrix-client.matrix.org` is the
859    /// homeserver (at the time of writing — 2024-08-28).
860    ///
861    /// This value is optional depending on how the `Client` has been built.
862    /// If it's been built from a homeserver URL directly, we don't know the
863    /// server. However, if the `Client` has been built from a server URL or
864    /// name, then the homeserver has been discovered, and we know both.
865    pub fn server(&self) -> Option<String> {
866        self.inner.server().map(ToString::to_string)
867    }
868
869    pub fn rooms(&self) -> Vec<Arc<Room>> {
870        self.inner.rooms().into_iter().map(|room| Arc::new(Room::new(room))).collect()
871    }
872
873    /// Get a room by its ID.
874    ///
875    /// # Arguments
876    ///
877    /// * `room_id` - The ID of the room to get.
878    ///
879    /// # Returns
880    ///
881    /// A `Result` containing an optional room, or a `ClientError`.
882    /// This method will not initialize the room's timeline or populate it with
883    /// events.
884    pub fn get_room(&self, room_id: String) -> Result<Option<Arc<Room>>, ClientError> {
885        let room_id = RoomId::parse(room_id)?;
886        let sdk_room = self.inner.get_room(&room_id);
887        let room = sdk_room.map(|room| Arc::new(Room::new(room)));
888        Ok(room)
889    }
890
891    pub fn get_dm_room(&self, user_id: String) -> Result<Option<Arc<Room>>, ClientError> {
892        let user_id = UserId::parse(user_id)?;
893        let sdk_room = self.inner.get_dm_room(&user_id);
894        let dm = sdk_room.map(|room| Arc::new(Room::new(room)));
895        Ok(dm)
896    }
897
898    pub async fn search_users(
899        &self,
900        search_term: String,
901        limit: u64,
902    ) -> Result<SearchUsersResults, ClientError> {
903        let response = self.inner.search_users(&search_term, limit).await?;
904        Ok(SearchUsersResults::from(response))
905    }
906
907    pub async fn get_profile(&self, user_id: String) -> Result<UserProfile, ClientError> {
908        let owned_user_id = UserId::parse(user_id.clone())?;
909
910        let response = self.inner.account().fetch_user_profile_of(&owned_user_id).await?;
911
912        Ok(UserProfile {
913            user_id,
914            display_name: response.displayname.clone(),
915            avatar_url: response.avatar_url.as_ref().map(|url| url.to_string()),
916        })
917    }
918
919    pub async fn notification_client(
920        self: Arc<Self>,
921        process_setup: NotificationProcessSetup,
922    ) -> Result<Arc<NotificationClient>, ClientError> {
923        Ok(Arc::new(NotificationClient {
924            inner: MatrixNotificationClient::new((*self.inner).clone(), process_setup.into())
925                .await?,
926            _client: self.clone(),
927        }))
928    }
929
930    pub fn sync_service(&self) -> Arc<SyncServiceBuilder> {
931        SyncServiceBuilder::new((*self.inner).clone())
932    }
933
934    pub async fn get_notification_settings(&self) -> Arc<NotificationSettings> {
935        let inner = self.inner.notification_settings().await;
936
937        Arc::new(NotificationSettings::new((*self.inner).clone(), inner))
938    }
939
940    pub fn encryption(self: Arc<Self>) -> Arc<Encryption> {
941        Arc::new(Encryption { inner: self.inner.encryption(), _client: self.clone() })
942    }
943
944    // Ignored users
945
946    pub async fn ignored_users(&self) -> Result<Vec<String>, ClientError> {
947        if let Some(raw_content) = self
948            .inner
949            .account()
950            .fetch_account_data(GlobalAccountDataEventType::IgnoredUserList)
951            .await?
952        {
953            let content = raw_content.deserialize_as::<IgnoredUserListEventContent>()?;
954            let user_ids: Vec<String> =
955                content.ignored_users.keys().map(|id| id.to_string()).collect();
956
957            return Ok(user_ids);
958        }
959
960        Ok(vec![])
961    }
962
963    pub async fn ignore_user(&self, user_id: String) -> Result<(), ClientError> {
964        let user_id = UserId::parse(user_id)?;
965        self.inner.account().ignore_user(&user_id).await?;
966        Ok(())
967    }
968
969    pub async fn unignore_user(&self, user_id: String) -> Result<(), ClientError> {
970        let user_id = UserId::parse(user_id)?;
971        self.inner.account().unignore_user(&user_id).await?;
972        Ok(())
973    }
974
975    pub fn subscribe_to_ignored_users(
976        &self,
977        listener: Box<dyn IgnoredUsersListener>,
978    ) -> Arc<TaskHandle> {
979        let mut subscriber = self.inner.subscribe_to_ignore_user_list_changes();
980        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
981            while let Some(user_ids) = subscriber.next().await {
982                listener.call(user_ids);
983            }
984        })))
985    }
986
987    pub fn room_directory_search(&self) -> Arc<RoomDirectorySearch> {
988        Arc::new(RoomDirectorySearch::new(
989            matrix_sdk::room_directory_search::RoomDirectorySearch::new((*self.inner).clone()),
990        ))
991    }
992
993    /// Join a room by its ID.
994    ///
995    /// Use this method when the homeserver already knows of the given room ID.
996    /// Otherwise use `join_room_by_id_or_alias` so you can pass a list of
997    /// server names for the homeserver to find the room.
998    pub async fn join_room_by_id(&self, room_id: String) -> Result<Arc<Room>, ClientError> {
999        let room_id = RoomId::parse(room_id)?;
1000        let room = self.inner.join_room_by_id(room_id.as_ref()).await?;
1001        Ok(Arc::new(Room::new(room)))
1002    }
1003
1004    /// Join a room by its ID or alias.
1005    ///
1006    /// When supplying the room's ID, you can also supply a list of server names
1007    /// for the homeserver to find the room. Typically these server names
1008    /// come from a permalink's `via` parameters, or from resolving a room's
1009    /// alias into an ID.
1010    pub async fn join_room_by_id_or_alias(
1011        &self,
1012        room_id_or_alias: String,
1013        server_names: Vec<String>,
1014    ) -> Result<Arc<Room>, ClientError> {
1015        let room_id = RoomOrAliasId::parse(&room_id_or_alias)?;
1016        let server_names = server_names
1017            .iter()
1018            .map(|name| OwnedServerName::try_from(name.as_str()))
1019            .collect::<Result<Vec<_>, _>>()?;
1020        let room =
1021            self.inner.join_room_by_id_or_alias(room_id.as_ref(), server_names.as_ref()).await?;
1022        Ok(Arc::new(Room::new(room)))
1023    }
1024
1025    /// Knock on a room to join it using its ID or alias.
1026    pub async fn knock(
1027        &self,
1028        room_id_or_alias: String,
1029        reason: Option<String>,
1030        server_names: Vec<String>,
1031    ) -> Result<Arc<Room>, ClientError> {
1032        let room_id = RoomOrAliasId::parse(&room_id_or_alias)?;
1033        let server_names =
1034            server_names.iter().map(ServerName::parse).collect::<Result<Vec<_>, _>>()?;
1035        let room = self.inner.knock(room_id, reason, server_names).await?;
1036        Ok(Arc::new(Room::new(room)))
1037    }
1038
1039    pub async fn get_recently_visited_rooms(&self) -> Result<Vec<String>, ClientError> {
1040        Ok(self
1041            .inner
1042            .account()
1043            .get_recently_visited_rooms()
1044            .await?
1045            .into_iter()
1046            .map(Into::into)
1047            .collect())
1048    }
1049
1050    pub async fn track_recently_visited_room(&self, room: String) -> Result<(), ClientError> {
1051        let room_id = RoomId::parse(room)?;
1052        self.inner.account().track_recently_visited_room(room_id).await?;
1053        Ok(())
1054    }
1055
1056    /// Resolves the given room alias to a room ID (and a list of servers), if
1057    /// possible.
1058    pub async fn resolve_room_alias(
1059        &self,
1060        room_alias: String,
1061    ) -> Result<Option<ResolvedRoomAlias>, ClientError> {
1062        let room_alias = RoomAliasId::parse(&room_alias)?;
1063        match self.inner.resolve_room_alias(&room_alias).await {
1064            Ok(response) => Ok(Some(response.into())),
1065            Err(error) => match error.client_api_error_kind() {
1066                // The room alias wasn't found, so we return None.
1067                Some(ErrorKind::NotFound) => Ok(None),
1068                // In any other case we just return the error, mapped.
1069                _ => Err(error.into()),
1070            },
1071        }
1072    }
1073
1074    /// Checks if a room alias exists in the current homeserver.
1075    pub async fn room_alias_exists(&self, room_alias: String) -> Result<bool, ClientError> {
1076        self.resolve_room_alias(room_alias).await.map(|ret| ret.is_some())
1077    }
1078
1079    /// Given a room id, get the preview of a room, to interact with it.
1080    ///
1081    /// The list of `via_servers` must be a list of servers that know
1082    /// about the room and can resolve it, and that may appear as a `via`
1083    /// parameter in e.g. a permalink URL. This list can be empty.
1084    pub async fn get_room_preview_from_room_id(
1085        &self,
1086        room_id: String,
1087        via_servers: Vec<String>,
1088    ) -> Result<Arc<RoomPreview>, ClientError> {
1089        let room_id = RoomId::parse(&room_id).context("room_id is not a valid room id")?;
1090
1091        let via_servers = via_servers
1092            .into_iter()
1093            .map(ServerName::parse)
1094            .collect::<Result<Vec<_>, _>>()
1095            .context("at least one `via` server name is invalid")?;
1096
1097        // The `into()` call below doesn't work if I do `(&room_id).into()`, so I let
1098        // rustc win that one fight.
1099        let room_id: &RoomId = &room_id;
1100
1101        let room_preview = self.inner.get_room_preview(room_id.into(), via_servers).await?;
1102
1103        Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
1104    }
1105
1106    /// Given a room alias, get the preview of a room, to interact with it.
1107    pub async fn get_room_preview_from_room_alias(
1108        &self,
1109        room_alias: String,
1110    ) -> Result<Arc<RoomPreview>, ClientError> {
1111        let room_alias =
1112            RoomAliasId::parse(&room_alias).context("room_alias is not a valid room alias")?;
1113
1114        // The `into()` call below doesn't work if I do `(&room_id).into()`, so I let
1115        // rustc win that one fight.
1116        let room_alias: &RoomAliasId = &room_alias;
1117
1118        let room_preview = self.inner.get_room_preview(room_alias.into(), Vec::new()).await?;
1119
1120        Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
1121    }
1122
1123    /// Waits until an at least partially synced room is received, and returns
1124    /// it.
1125    ///
1126    /// **Note: this function will loop endlessly until either it finds the room
1127    /// or an externally set timeout happens.**
1128    pub async fn await_room_remote_echo(&self, room_id: String) -> Result<Arc<Room>, ClientError> {
1129        let room_id = RoomId::parse(room_id)?;
1130        Ok(Arc::new(Room::new(self.inner.await_room_remote_echo(&room_id).await)))
1131    }
1132
1133    /// Lets the user know whether this is an `m.login.password` based
1134    /// auth and if the account can actually be deactivated
1135    pub fn can_deactivate_account(&self) -> bool {
1136        matches!(self.inner.auth_api(), Some(AuthApi::Matrix(_)))
1137    }
1138
1139    /// Deactivate this account definitively.
1140    /// Similarly to `encryption::reset_identity` this
1141    /// will only work with password-based authentication (`m.login.password`)
1142    ///
1143    /// # Arguments
1144    ///
1145    /// * `auth_data` - This request uses the [User-Interactive Authentication
1146    ///   API][uiaa]. The first request needs to set this to `None` and will
1147    ///   always fail and the same request needs to be made but this time with
1148    ///   some `auth_data` provided.
1149    pub async fn deactivate_account(
1150        &self,
1151        auth_data: Option<AuthData>,
1152        erase_data: bool,
1153    ) -> Result<(), ClientError> {
1154        if let Some(auth_data) = auth_data {
1155            _ = self.inner.account().deactivate(None, Some(auth_data.into()), erase_data).await?;
1156        } else {
1157            _ = self.inner.account().deactivate(None, None, erase_data).await?;
1158        }
1159
1160        Ok(())
1161    }
1162
1163    /// Checks if a room alias is not in use yet.
1164    ///
1165    /// Returns:
1166    /// - `Ok(true)` if the room alias is available.
1167    /// - `Ok(false)` if it's not (the resolve alias request returned a `404`
1168    ///   status code).
1169    /// - An `Err` otherwise.
1170    pub async fn is_room_alias_available(&self, alias: String) -> Result<bool, ClientError> {
1171        let alias = RoomAliasId::parse(alias)?;
1172        self.inner.is_room_alias_available(&alias).await.map_err(Into::into)
1173    }
1174
1175    /// Set the media retention policy.
1176    pub async fn set_media_retention_policy(
1177        &self,
1178        policy: MediaRetentionPolicy,
1179    ) -> Result<(), ClientError> {
1180        let closure = async || -> Result<_, EventCacheError> {
1181            let store = self.inner.event_cache_store().lock().await?;
1182            Ok(store.set_media_retention_policy(policy).await?)
1183        };
1184
1185        Ok(closure().await?)
1186    }
1187
1188    /// Clear all the non-critical caches for this Client instance.
1189    ///
1190    /// - This will empty all the room's persisted event caches, so all rooms
1191    ///   will start as if they were empty.
1192    /// - This will empty the media cache according to the current media
1193    ///   retention policy.
1194    pub async fn clear_caches(&self) -> Result<(), ClientError> {
1195        let closure = async || -> Result<_, EventCacheError> {
1196            let store = self.inner.event_cache_store().lock().await?;
1197
1198            // Clear all the room chunks.
1199            store.clear_all_rooms_chunks().await?;
1200
1201            // Clean up the media cache according to the current media retention policy.
1202            store.clean_up_media_cache().await?;
1203
1204            Ok(())
1205        };
1206
1207        Ok(closure().await?)
1208    }
1209}
1210
1211#[matrix_sdk_ffi_macros::export(callback_interface)]
1212pub trait IgnoredUsersListener: Sync + Send {
1213    fn call(&self, ignored_user_ids: Vec<String>);
1214}
1215
1216#[derive(uniffi::Enum)]
1217pub enum NotificationProcessSetup {
1218    MultipleProcesses,
1219    SingleProcess { sync_service: Arc<SyncService> },
1220}
1221
1222impl From<NotificationProcessSetup> for MatrixNotificationProcessSetup {
1223    fn from(value: NotificationProcessSetup) -> Self {
1224        match value {
1225            NotificationProcessSetup::MultipleProcesses => {
1226                MatrixNotificationProcessSetup::MultipleProcesses
1227            }
1228            NotificationProcessSetup::SingleProcess { sync_service } => {
1229                MatrixNotificationProcessSetup::SingleProcess {
1230                    sync_service: sync_service.inner.clone(),
1231                }
1232            }
1233        }
1234    }
1235}
1236
1237/// Information about a room, that was resolved from a room alias.
1238#[derive(uniffi::Record)]
1239pub struct ResolvedRoomAlias {
1240    /// The room ID that the alias resolved to.
1241    pub room_id: String,
1242    /// A list of servers that can be used to find the room by its room ID.
1243    pub servers: Vec<String>,
1244}
1245
1246impl From<get_alias::v3::Response> for ResolvedRoomAlias {
1247    fn from(value: get_alias::v3::Response) -> Self {
1248        Self {
1249            room_id: value.room_id.to_string(),
1250            servers: value.servers.iter().map(ToString::to_string).collect(),
1251        }
1252    }
1253}
1254
1255#[derive(uniffi::Record)]
1256pub struct SearchUsersResults {
1257    pub results: Vec<UserProfile>,
1258    pub limited: bool,
1259}
1260
1261impl From<search_users::v3::Response> for SearchUsersResults {
1262    fn from(value: search_users::v3::Response) -> Self {
1263        let results: Vec<UserProfile> = value.results.iter().map(UserProfile::from).collect();
1264        SearchUsersResults { results, limited: value.limited }
1265    }
1266}
1267
1268#[derive(uniffi::Record)]
1269pub struct UserProfile {
1270    pub user_id: String,
1271    pub display_name: Option<String>,
1272    pub avatar_url: Option<String>,
1273}
1274
1275impl From<&search_users::v3::User> for UserProfile {
1276    fn from(value: &search_users::v3::User) -> Self {
1277        UserProfile {
1278            user_id: value.user_id.to_string(),
1279            display_name: value.display_name.clone(),
1280            avatar_url: value.avatar_url.as_ref().map(|url| url.to_string()),
1281        }
1282    }
1283}
1284
1285impl Client {
1286    fn process_session_change(&self, session_change: SessionChange) {
1287        if let Some(delegate) = self.delegate.read().unwrap().clone() {
1288            debug!("Applying session change: {session_change:?}");
1289            get_runtime_handle().spawn_blocking(move || match session_change {
1290                SessionChange::UnknownToken { soft_logout } => {
1291                    delegate.did_receive_auth_error(soft_logout);
1292                }
1293                SessionChange::TokensRefreshed => {
1294                    delegate.did_refresh_tokens();
1295                }
1296            });
1297        } else {
1298            debug!(
1299                "No client delegate found, session change couldn't be applied: {session_change:?}"
1300            );
1301        }
1302    }
1303
1304    fn retrieve_session(
1305        session_delegate: Arc<dyn ClientSessionDelegate>,
1306        user_id: &UserId,
1307    ) -> anyhow::Result<SessionTokens> {
1308        Ok(session_delegate.retrieve_session_from_keychain(user_id.to_string())?.into_tokens())
1309    }
1310
1311    fn session_inner(client: matrix_sdk::Client) -> Result<Session, ClientError> {
1312        let auth_api = client.auth_api().context("Missing authentication API")?;
1313
1314        let homeserver_url = client.homeserver().into();
1315        let sliding_sync_version = client.sliding_sync_version();
1316
1317        Session::new(auth_api, homeserver_url, sliding_sync_version.into())
1318    }
1319
1320    fn save_session(
1321        session_delegate: Arc<dyn ClientSessionDelegate>,
1322        client: matrix_sdk::Client,
1323    ) -> anyhow::Result<()> {
1324        let session = Self::session_inner(client)?;
1325        session_delegate.save_session_in_keychain(session);
1326        Ok(())
1327    }
1328}
1329
1330#[derive(uniffi::Record)]
1331pub struct NotificationPowerLevels {
1332    pub room: i32,
1333}
1334
1335impl From<NotificationPowerLevels> for ruma::power_levels::NotificationPowerLevels {
1336    fn from(value: NotificationPowerLevels) -> Self {
1337        let mut notification_power_levels = Self::new();
1338        notification_power_levels.room = value.room.into();
1339        notification_power_levels
1340    }
1341}
1342
1343#[derive(uniffi::Record)]
1344pub struct PowerLevels {
1345    pub users_default: Option<i32>,
1346    pub events_default: Option<i32>,
1347    pub state_default: Option<i32>,
1348    pub ban: Option<i32>,
1349    pub kick: Option<i32>,
1350    pub redact: Option<i32>,
1351    pub invite: Option<i32>,
1352    pub notifications: Option<NotificationPowerLevels>,
1353    pub users: HashMap<String, i32>,
1354    pub events: HashMap<String, i32>,
1355}
1356
1357impl From<PowerLevels> for RoomPowerLevelsEventContent {
1358    fn from(value: PowerLevels) -> Self {
1359        let mut power_levels = RoomPowerLevelsEventContent::new();
1360
1361        if let Some(users_default) = value.users_default {
1362            power_levels.users_default = users_default.into();
1363        }
1364        if let Some(state_default) = value.state_default {
1365            power_levels.state_default = state_default.into();
1366        }
1367        if let Some(events_default) = value.events_default {
1368            power_levels.events_default = events_default.into();
1369        }
1370        if let Some(ban) = value.ban {
1371            power_levels.ban = ban.into();
1372        }
1373        if let Some(kick) = value.kick {
1374            power_levels.kick = kick.into();
1375        }
1376        if let Some(redact) = value.redact {
1377            power_levels.redact = redact.into();
1378        }
1379        if let Some(invite) = value.invite {
1380            power_levels.invite = invite.into();
1381        }
1382        if let Some(notifications) = value.notifications {
1383            power_levels.notifications = notifications.into()
1384        }
1385        power_levels.users = value
1386            .users
1387            .iter()
1388            .filter_map(|(user_id, power_level)| match UserId::parse(user_id) {
1389                Ok(id) => Some((id, (*power_level).into())),
1390                Err(e) => {
1391                    error!(user_id, "Skipping invalid user ID, error: {e}");
1392                    None
1393                }
1394            })
1395            .collect();
1396
1397        power_levels.events = value
1398            .events
1399            .iter()
1400            .map(|(event_type, power_level)| {
1401                let event_type: ruma::events::TimelineEventType = event_type.as_str().into();
1402                (event_type, (*power_level).into())
1403            })
1404            .collect();
1405
1406        power_levels
1407    }
1408}
1409
1410#[derive(uniffi::Record)]
1411pub struct CreateRoomParameters {
1412    pub name: Option<String>,
1413    #[uniffi(default = None)]
1414    pub topic: Option<String>,
1415    pub is_encrypted: bool,
1416    #[uniffi(default = false)]
1417    pub is_direct: bool,
1418    pub visibility: RoomVisibility,
1419    pub preset: RoomPreset,
1420    #[uniffi(default = None)]
1421    pub invite: Option<Vec<String>>,
1422    #[uniffi(default = None)]
1423    pub avatar: Option<String>,
1424    #[uniffi(default = None)]
1425    pub power_level_content_override: Option<PowerLevels>,
1426    #[uniffi(default = None)]
1427    pub join_rule_override: Option<JoinRule>,
1428    #[uniffi(default = None)]
1429    pub history_visibility_override: Option<RoomHistoryVisibility>,
1430    #[uniffi(default = None)]
1431    pub canonical_alias: Option<String>,
1432}
1433
1434impl TryFrom<CreateRoomParameters> for create_room::v3::Request {
1435    type Error = ClientError;
1436
1437    fn try_from(value: CreateRoomParameters) -> Result<create_room::v3::Request, Self::Error> {
1438        let mut request = create_room::v3::Request::new();
1439        request.name = value.name;
1440        request.topic = value.topic;
1441        request.is_direct = value.is_direct;
1442        request.visibility = value.visibility.into();
1443        request.preset = Some(value.preset.into());
1444        request.room_alias_name = value.canonical_alias;
1445        request.invite = match value.invite {
1446            Some(invite) => invite
1447                .iter()
1448                .filter_map(|user_id| match UserId::parse(user_id) {
1449                    Ok(id) => Some(id),
1450                    Err(e) => {
1451                        error!(user_id, "Skipping invalid user ID, error: {e}");
1452                        None
1453                    }
1454                })
1455                .collect(),
1456            None => vec![],
1457        };
1458
1459        let mut initial_state: Vec<Raw<AnyInitialStateEvent>> = vec![];
1460
1461        if value.is_encrypted {
1462            let content =
1463                RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
1464            initial_state.push(InitialStateEvent::new(content).to_raw_any());
1465        }
1466
1467        if let Some(url) = value.avatar {
1468            let mut content = RoomAvatarEventContent::new();
1469            content.url = Some(url.into());
1470            initial_state.push(InitialStateEvent::new(content).to_raw_any());
1471        }
1472
1473        if let Some(join_rule_override) = value.join_rule_override {
1474            let content = RoomJoinRulesEventContent::new(join_rule_override.try_into()?);
1475            initial_state.push(InitialStateEvent::new(content).to_raw_any());
1476        }
1477
1478        if let Some(history_visibility_override) = value.history_visibility_override {
1479            let content =
1480                RoomHistoryVisibilityEventContent::new(history_visibility_override.try_into()?);
1481            initial_state.push(InitialStateEvent::new(content).to_raw_any());
1482        }
1483
1484        request.initial_state = initial_state;
1485
1486        if let Some(power_levels) = value.power_level_content_override {
1487            match Raw::new(&power_levels.into()) {
1488                Ok(power_levels) => {
1489                    request.power_level_content_override = Some(power_levels);
1490                }
1491                Err(e) => {
1492                    return Err(ClientError::Generic {
1493                        msg: format!("Failed to serialize power levels, error: {e}"),
1494                    })
1495                }
1496            }
1497        }
1498
1499        Ok(request)
1500    }
1501}
1502
1503#[derive(uniffi::Enum)]
1504pub enum RoomVisibility {
1505    /// Indicates that the room will be shown in the published room list.
1506    Public,
1507
1508    /// Indicates that the room will not be shown in the published room list.
1509    Private,
1510
1511    /// A custom value that's not present in the spec.
1512    Custom { value: String },
1513}
1514
1515impl From<RoomVisibility> for Visibility {
1516    fn from(value: RoomVisibility) -> Self {
1517        match value {
1518            RoomVisibility::Public => Self::Public,
1519            RoomVisibility::Private => Self::Private,
1520            RoomVisibility::Custom { value } => value.as_str().into(),
1521        }
1522    }
1523}
1524
1525impl From<Visibility> for RoomVisibility {
1526    fn from(value: Visibility) -> Self {
1527        match value {
1528            Visibility::Public => Self::Public,
1529            Visibility::Private => Self::Private,
1530            _ => Self::Custom { value: value.as_str().to_owned() },
1531        }
1532    }
1533}
1534
1535#[derive(uniffi::Enum)]
1536#[allow(clippy::enum_variant_names)]
1537pub enum RoomPreset {
1538    /// `join_rules` is set to `invite` and `history_visibility` is set to
1539    /// `shared`.
1540    PrivateChat,
1541
1542    /// `join_rules` is set to `public` and `history_visibility` is set to
1543    /// `shared`.
1544    PublicChat,
1545
1546    /// Same as `PrivateChat`, but all initial invitees get the same power level
1547    /// as the creator.
1548    TrustedPrivateChat,
1549}
1550
1551impl From<RoomPreset> for create_room::v3::RoomPreset {
1552    fn from(value: RoomPreset) -> Self {
1553        match value {
1554            RoomPreset::PrivateChat => Self::PrivateChat,
1555            RoomPreset::PublicChat => Self::PublicChat,
1556            RoomPreset::TrustedPrivateChat => Self::TrustedPrivateChat,
1557        }
1558    }
1559}
1560
1561#[derive(uniffi::Record)]
1562pub struct Session {
1563    // Same fields as the Session type in matrix-sdk, just simpler types
1564    /// The access token used for this session.
1565    pub access_token: String,
1566    /// The token used for [refreshing the access token], if any.
1567    ///
1568    /// [refreshing the access token]: https://spec.matrix.org/v1.3/client-server-api/#refreshing-access-tokens
1569    pub refresh_token: Option<String>,
1570    /// The user the access token was issued for.
1571    pub user_id: String,
1572    /// The ID of the client device.
1573    pub device_id: String,
1574
1575    // FFI-only fields (for now)
1576    /// The URL for the homeserver used for this session.
1577    pub homeserver_url: String,
1578    /// Additional data for this session if OpenID Connect was used for
1579    /// authentication.
1580    pub oidc_data: Option<String>,
1581    /// The sliding sync version used for this session.
1582    pub sliding_sync_version: SlidingSyncVersion,
1583}
1584
1585impl Session {
1586    fn new(
1587        auth_api: AuthApi,
1588        homeserver_url: String,
1589        sliding_sync_version: SlidingSyncVersion,
1590    ) -> Result<Session, ClientError> {
1591        match auth_api {
1592            // Build the session from the regular Matrix Auth Session.
1593            AuthApi::Matrix(a) => {
1594                let matrix_sdk::authentication::matrix::MatrixSession {
1595                    meta: matrix_sdk::SessionMeta { user_id, device_id },
1596                    tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
1597                } = a.session().context("Missing session")?;
1598
1599                Ok(Session {
1600                    access_token,
1601                    refresh_token,
1602                    user_id: user_id.to_string(),
1603                    device_id: device_id.to_string(),
1604                    homeserver_url,
1605                    oidc_data: None,
1606                    sliding_sync_version,
1607                })
1608            }
1609            // Build the session from the OIDC UserSession.
1610            AuthApi::OAuth(api) => {
1611                let matrix_sdk::authentication::oauth::UserSession {
1612                    meta: matrix_sdk::SessionMeta { user_id, device_id },
1613                    tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
1614                    issuer,
1615                } = api.user_session().context("Missing session")?;
1616                let client_id = api.client_id().context("OIDC client ID is missing.")?.clone();
1617                let oidc_data = OidcSessionData { client_id, issuer };
1618
1619                let oidc_data = serde_json::to_string(&oidc_data).ok();
1620                Ok(Session {
1621                    access_token,
1622                    refresh_token,
1623                    user_id: user_id.to_string(),
1624                    device_id: device_id.to_string(),
1625                    homeserver_url,
1626                    oidc_data,
1627                    sliding_sync_version,
1628                })
1629            }
1630            _ => Err(anyhow!("Unknown authentication API").into()),
1631        }
1632    }
1633
1634    fn into_tokens(self) -> matrix_sdk::SessionTokens {
1635        SessionTokens { access_token: self.access_token, refresh_token: self.refresh_token }
1636    }
1637}
1638
1639impl TryFrom<Session> for AuthSession {
1640    type Error = ClientError;
1641    fn try_from(value: Session) -> Result<Self, Self::Error> {
1642        let Session {
1643            access_token,
1644            refresh_token,
1645            user_id,
1646            device_id,
1647            homeserver_url: _,
1648            oidc_data,
1649            sliding_sync_version: _,
1650        } = value;
1651
1652        if let Some(oidc_data) = oidc_data {
1653            // Create an OidcSession.
1654            let oidc_data = serde_json::from_str::<OidcSessionData>(&oidc_data)?;
1655
1656            let user_session = matrix_sdk::authentication::oauth::UserSession {
1657                meta: matrix_sdk::SessionMeta {
1658                    user_id: user_id.try_into()?,
1659                    device_id: device_id.into(),
1660                },
1661                tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
1662                issuer: oidc_data.issuer,
1663            };
1664
1665            let session = OAuthSession { client_id: oidc_data.client_id, user: user_session };
1666
1667            Ok(AuthSession::OAuth(session.into()))
1668        } else {
1669            // Create a regular Matrix Session.
1670            let session = matrix_sdk::authentication::matrix::MatrixSession {
1671                meta: matrix_sdk::SessionMeta {
1672                    user_id: user_id.try_into()?,
1673                    device_id: device_id.into(),
1674                },
1675                tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
1676            };
1677
1678            Ok(AuthSession::Matrix(session))
1679        }
1680    }
1681}
1682
1683/// Represents a client registration against an OpenID Connect authentication
1684/// issuer.
1685#[derive(Serialize, Deserialize)]
1686#[serde(try_from = "OidcSessionDataDeHelper")]
1687pub(crate) struct OidcSessionData {
1688    client_id: ClientId,
1689    issuer: Url,
1690}
1691
1692#[derive(Deserialize)]
1693struct OidcSessionDataDeHelper {
1694    client_id: ClientId,
1695    issuer_info: Option<AuthenticationServerInfo>,
1696    issuer: Option<Url>,
1697}
1698
1699impl TryFrom<OidcSessionDataDeHelper> for OidcSessionData {
1700    type Error = String;
1701
1702    fn try_from(value: OidcSessionDataDeHelper) -> Result<Self, Self::Error> {
1703        let OidcSessionDataDeHelper { client_id, issuer_info, issuer } = value;
1704
1705        let issuer = issuer
1706            .or(issuer_info.and_then(|info| Url::parse(&info.issuer).ok()))
1707            .ok_or_else(|| "missing field `issuer`".to_owned())?;
1708
1709        Ok(Self { client_id, issuer })
1710    }
1711}
1712
1713#[derive(uniffi::Enum)]
1714pub enum AccountManagementAction {
1715    Profile,
1716    SessionsList,
1717    SessionView { device_id: String },
1718    SessionEnd { device_id: String },
1719    AccountDeactivate,
1720    CrossSigningReset,
1721}
1722
1723impl From<AccountManagementAction> for AccountManagementActionFull {
1724    fn from(value: AccountManagementAction) -> Self {
1725        match value {
1726            AccountManagementAction::Profile => Self::Profile,
1727            AccountManagementAction::SessionsList => Self::SessionsList,
1728            AccountManagementAction::SessionView { device_id } => {
1729                Self::SessionView { device_id: device_id.into() }
1730            }
1731            AccountManagementAction::SessionEnd { device_id } => {
1732                Self::SessionEnd { device_id: device_id.into() }
1733            }
1734            AccountManagementAction::AccountDeactivate => Self::AccountDeactivate,
1735            AccountManagementAction::CrossSigningReset => Self::CrossSigningReset,
1736        }
1737    }
1738}
1739
1740#[matrix_sdk_ffi_macros::export]
1741fn gen_transaction_id() -> String {
1742    TransactionId::new().to_string()
1743}
1744
1745/// A file handle that takes ownership of a media file on disk. When the handle
1746/// is dropped, the file will be removed from the disk.
1747#[derive(uniffi::Object)]
1748pub struct MediaFileHandle {
1749    inner: RwLock<Option<SdkMediaFileHandle>>,
1750}
1751
1752impl MediaFileHandle {
1753    fn new(handle: SdkMediaFileHandle) -> Self {
1754        Self { inner: RwLock::new(Some(handle)) }
1755    }
1756}
1757
1758#[matrix_sdk_ffi_macros::export]
1759impl MediaFileHandle {
1760    /// Get the media file's path.
1761    pub fn path(&self) -> Result<String, ClientError> {
1762        Ok(self
1763            .inner
1764            .read()
1765            .unwrap()
1766            .as_ref()
1767            .context("MediaFileHandle must not be used after calling persist")?
1768            .path()
1769            .to_str()
1770            .unwrap()
1771            .to_owned())
1772    }
1773
1774    pub fn persist(&self, path: String) -> Result<bool, ClientError> {
1775        let mut guard = self.inner.write().unwrap();
1776        Ok(
1777            match guard
1778                .take()
1779                .context("MediaFileHandle was already persisted")?
1780                .persist(path.as_ref())
1781            {
1782                Ok(_) => true,
1783                Err(e) => {
1784                    *guard = Some(e.file);
1785                    false
1786                }
1787            },
1788        )
1789    }
1790}
1791
1792#[derive(Clone, uniffi::Enum)]
1793pub enum SlidingSyncVersion {
1794    None,
1795    Native,
1796}
1797
1798impl From<SdkSlidingSyncVersion> for SlidingSyncVersion {
1799    fn from(value: SdkSlidingSyncVersion) -> Self {
1800        match value {
1801            SdkSlidingSyncVersion::None => Self::None,
1802            SdkSlidingSyncVersion::Native => Self::Native,
1803        }
1804    }
1805}
1806
1807impl TryFrom<SlidingSyncVersion> for SdkSlidingSyncVersion {
1808    type Error = ClientError;
1809
1810    fn try_from(value: SlidingSyncVersion) -> Result<Self, Self::Error> {
1811        Ok(match value {
1812            SlidingSyncVersion::None => Self::None,
1813            SlidingSyncVersion::Native => Self::Native,
1814        })
1815    }
1816}
1817
1818#[derive(Clone, uniffi::Enum)]
1819pub enum OidcPrompt {
1820    /// The Authorization Server should prompt the End-User to create a user
1821    /// account.
1822    ///
1823    /// Defined in [Initiating User Registration via OpenID Connect](https://openid.net/specs/openid-connect-prompt-create-1_0.html).
1824    Create,
1825
1826    /// The Authorization Server should prompt the End-User for
1827    /// reauthentication.
1828    Login,
1829
1830    /// The Authorization Server should prompt the End-User for consent before
1831    /// returning information to the Client.
1832    Consent,
1833
1834    /// An unknown value.
1835    Unknown { value: String },
1836}
1837
1838impl From<RumaOidcPrompt> for OidcPrompt {
1839    fn from(value: RumaOidcPrompt) -> Self {
1840        match value {
1841            RumaOidcPrompt::Create => Self::Create,
1842            value => match value.as_str() {
1843                "consent" => Self::Consent,
1844                "login" => Self::Login,
1845                _ => Self::Unknown { value: value.to_string() },
1846            },
1847        }
1848    }
1849}
1850
1851impl From<OidcPrompt> for RumaOidcPrompt {
1852    fn from(value: OidcPrompt) -> Self {
1853        match value {
1854            OidcPrompt::Create => Self::Create,
1855            OidcPrompt::Consent => Self::from("consent"),
1856            OidcPrompt::Login => Self::from("login"),
1857            OidcPrompt::Unknown { value } => value.into(),
1858        }
1859    }
1860}
1861
1862/// The rule used for users wishing to join this room.
1863#[derive(Debug, Clone, uniffi::Enum)]
1864pub enum JoinRule {
1865    /// Anyone can join the room without any prior action.
1866    Public,
1867
1868    /// A user who wishes to join the room must first receive an invite to the
1869    /// room from someone already inside of the room.
1870    Invite,
1871
1872    /// Users can join the room if they are invited, or they can request an
1873    /// invite to the room.
1874    ///
1875    /// They can be allowed (invited) or denied (kicked/banned) access.
1876    Knock,
1877
1878    /// Reserved but not yet implemented by the Matrix specification.
1879    Private,
1880
1881    /// Users can join the room if they are invited, or if they meet any of the
1882    /// conditions described in a set of [`AllowRule`]s.
1883    Restricted { rules: Vec<AllowRule> },
1884
1885    /// Users can join the room if they are invited, or if they meet any of the
1886    /// conditions described in a set of [`AllowRule`]s, or they can request
1887    /// an invite to the room.
1888    KnockRestricted { rules: Vec<AllowRule> },
1889
1890    /// A custom join rule, up for interpretation by the consumer.
1891    Custom {
1892        /// The string representation for this custom rule.
1893        repr: String,
1894    },
1895}
1896
1897/// An allow rule which defines a condition that allows joining a room.
1898#[derive(Debug, Clone, uniffi::Enum)]
1899pub enum AllowRule {
1900    /// Only a member of the `room_id` Room can join the one this rule is used
1901    /// in.
1902    RoomMembership { room_id: String },
1903
1904    /// A custom allow rule implementation, containing its JSON representation
1905    /// as a `String`.
1906    Custom { json: String },
1907}
1908
1909impl TryFrom<JoinRule> for RumaJoinRule {
1910    type Error = ClientError;
1911
1912    fn try_from(value: JoinRule) -> Result<Self, Self::Error> {
1913        match value {
1914            JoinRule::Public => Ok(Self::Public),
1915            JoinRule::Invite => Ok(Self::Invite),
1916            JoinRule::Knock => Ok(Self::Knock),
1917            JoinRule::Private => Ok(Self::Private),
1918            JoinRule::Restricted { rules } => {
1919                let rules = ruma_allow_rules_from_ffi(rules)?;
1920                Ok(Self::Restricted(ruma::events::room::join_rules::Restricted::new(rules)))
1921            }
1922            JoinRule::KnockRestricted { rules } => {
1923                let rules = ruma_allow_rules_from_ffi(rules)?;
1924                Ok(Self::KnockRestricted(ruma::events::room::join_rules::Restricted::new(rules)))
1925            }
1926            JoinRule::Custom { repr } => Ok(serde_json::from_str(&repr)?),
1927        }
1928    }
1929}
1930
1931fn ruma_allow_rules_from_ffi(value: Vec<AllowRule>) -> Result<Vec<RumaAllowRule>, ClientError> {
1932    let mut ret = Vec::with_capacity(value.len());
1933    for rule in value {
1934        let rule: Result<RumaAllowRule, ClientError> = rule.try_into();
1935        match rule {
1936            Ok(rule) => ret.push(rule),
1937            Err(error) => return Err(error),
1938        }
1939    }
1940    Ok(ret)
1941}
1942
1943impl TryFrom<AllowRule> for RumaAllowRule {
1944    type Error = ClientError;
1945
1946    fn try_from(value: AllowRule) -> Result<Self, Self::Error> {
1947        match value {
1948            AllowRule::RoomMembership { room_id } => {
1949                let room_id = RoomId::parse(room_id)?;
1950                Ok(Self::RoomMembership(ruma::events::room::join_rules::RoomMembership::new(
1951                    room_id,
1952                )))
1953            }
1954            AllowRule::Custom { json } => Ok(Self::_Custom(Box::new(serde_json::from_str(&json)?))),
1955        }
1956    }
1957}
1958
1959impl TryFrom<RumaJoinRule> for JoinRule {
1960    type Error = String;
1961    fn try_from(value: RumaJoinRule) -> Result<Self, Self::Error> {
1962        match value {
1963            RumaJoinRule::Knock => Ok(JoinRule::Knock),
1964            RumaJoinRule::Public => Ok(JoinRule::Public),
1965            RumaJoinRule::Private => Ok(JoinRule::Private),
1966            RumaJoinRule::KnockRestricted(restricted) => {
1967                let rules = restricted.allow.into_iter().map(TryInto::try_into).collect::<Result<
1968                    Vec<_>,
1969                    Self::Error,
1970                >>(
1971                )?;
1972                Ok(JoinRule::KnockRestricted { rules })
1973            }
1974            RumaJoinRule::Restricted(restricted) => {
1975                let rules = restricted.allow.into_iter().map(TryInto::try_into).collect::<Result<
1976                    Vec<_>,
1977                    Self::Error,
1978                >>(
1979                )?;
1980                Ok(JoinRule::Restricted { rules })
1981            }
1982            RumaJoinRule::Invite => Ok(JoinRule::Invite),
1983            RumaJoinRule::_Custom(_) => Ok(JoinRule::Custom { repr: value.as_str().to_owned() }),
1984            _ => Err(format!("Unknown JoinRule: {:?}", value)),
1985        }
1986    }
1987}
1988
1989impl TryFrom<RumaAllowRule> for AllowRule {
1990    type Error = String;
1991    fn try_from(value: RumaAllowRule) -> Result<Self, Self::Error> {
1992        match value {
1993            RumaAllowRule::RoomMembership(membership) => {
1994                Ok(AllowRule::RoomMembership { room_id: membership.room_id.to_string() })
1995            }
1996            RumaAllowRule::_Custom(repr) => {
1997                let json = serde_json::to_string(&repr)
1998                    .map_err(|e| format!("Couldn't serialize custom AllowRule: {e:?}"))?;
1999                Ok(Self::Custom { json })
2000            }
2001            _ => Err(format!("Invalid AllowRule: {:?}", value)),
2002        }
2003    }
2004}