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#[matrix_sdk_ffi_macros::export(callback_interface)]
167pub trait SendQueueRoomErrorListener: Sync + Send {
168 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 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 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 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 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 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 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 pub async fn abort_oidc_auth(&self, authorization_data: Arc<OAuthAuthorizationData>) {
427 self.inner.oauth().abort_login(&authorization_data.state).await;
428 }
429
430 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 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 pub async fn enable_all_send_queues(&self, enable: bool) {
484 self.inner.send_queue().set_enabled(enable).await;
485 }
486
487 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 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 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 pub async fn reset_server_capabilities(&self) -> Result<(), ClientError> {
529 Ok(self.inner.reset_server_capabilities().await?)
530 }
531}
532
533impl Client {
534 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 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 pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
558 self.inner.sliding_sync_version().into()
559 }
560
561 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 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 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 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 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 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 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 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 pub async fn delete_pusher(&self, identifiers: PusherIdentifiers) -> Result<(), ClientError> {
845 self.inner.pusher().delete(identifiers.into()).await?;
846 Ok(())
847 }
848
849 pub fn homeserver(&self) -> String {
851 self.inner.homeserver().to_string()
852 }
853
854 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 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 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 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 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 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 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 Some(ErrorKind::NotFound) => Ok(None),
1068 _ => Err(error.into()),
1070 },
1071 }
1072 }
1073
1074 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 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 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 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 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 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 pub fn can_deactivate_account(&self) -> bool {
1136 matches!(self.inner.auth_api(), Some(AuthApi::Matrix(_)))
1137 }
1138
1139 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 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 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 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 store.clear_all_rooms_chunks().await?;
1200
1201 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#[derive(uniffi::Record)]
1239pub struct ResolvedRoomAlias {
1240 pub room_id: String,
1242 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 Public,
1507
1508 Private,
1510
1511 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 PrivateChat,
1541
1542 PublicChat,
1545
1546 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 pub access_token: String,
1566 pub refresh_token: Option<String>,
1570 pub user_id: String,
1572 pub device_id: String,
1574
1575 pub homeserver_url: String,
1578 pub oidc_data: Option<String>,
1581 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 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 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 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 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#[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#[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 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 Create,
1825
1826 Login,
1829
1830 Consent,
1833
1834 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#[derive(Debug, Clone, uniffi::Enum)]
1864pub enum JoinRule {
1865 Public,
1867
1868 Invite,
1871
1872 Knock,
1877
1878 Private,
1880
1881 Restricted { rules: Vec<AllowRule> },
1884
1885 KnockRestricted { rules: Vec<AllowRule> },
1889
1890 Custom {
1892 repr: String,
1894 },
1895}
1896
1897#[derive(Debug, Clone, uniffi::Enum)]
1899pub enum AllowRule {
1900 RoomMembership { room_id: String },
1903
1904 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}