matrix_sdk_ffi/
client.rs

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