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#[matrix_sdk_ffi_macros::export(callback_interface)]
173pub trait SendQueueRoomErrorListener: Sync + Send {
174 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 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 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 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 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 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 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 pub async fn abort_oidc_auth(&self, authorization_data: Arc<OidcAuthorizationData>) {
436 self.inner.oidc().abort_authorization(&authorization_data.state).await;
437 }
438
439 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 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 pub async fn enable_all_send_queues(&self, enable: bool) {
497 self.inner.send_queue().set_enabled(enable).await;
498 }
499
500 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 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 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 pub async fn reset_server_capabilities(&self) -> Result<(), ClientError> {
542 Ok(self.inner.reset_server_capabilities().await?)
543 }
544}
545
546impl Client {
547 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 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 pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
571 self.inner.sliding_sync_version().into()
572 }
573
574 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 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 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 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 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 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 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 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 pub async fn delete_pusher(&self, identifiers: PusherIdentifiers) -> Result<(), ClientError> {
859 self.inner.pusher().delete(identifiers.into()).await?;
860 Ok(())
861 }
862
863 pub fn homeserver(&self) -> String {
865 self.inner.homeserver().to_string()
866 }
867
868 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 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 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 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 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 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 Some(ErrorKind::NotFound) => Ok(None),
1064 _ => Err(error.into()),
1066 },
1067 }
1068 }
1069
1070 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 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 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 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 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 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 pub fn can_deactivate_account(&self) -> bool {
1132 matches!(self.inner.auth_api(), Some(AuthApi::Matrix(_)))
1133 }
1134
1135 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 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#[derive(uniffi::Record)]
1200pub struct ResolvedRoomAlias {
1201 pub room_id: String,
1203 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 Public,
1474
1475 Private,
1477
1478 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 PrivateChat,
1508
1509 PublicChat,
1512
1513 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 pub access_token: String,
1533 pub refresh_token: Option<String>,
1537 pub user_id: String,
1539 pub device_id: String,
1541
1542 pub homeserver_url: String,
1545 pub oidc_data: Option<String>,
1548 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 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 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 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 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#[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#[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 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#[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 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 None,
1848
1849 Login,
1852
1853 Consent,
1856
1857 SelectAccount,
1864
1865 Create,
1870
1871 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#[derive(Debug, Clone, uniffi::Enum)]
1904pub enum JoinRule {
1905 Public,
1907
1908 Invite,
1911
1912 Knock,
1917
1918 Private,
1920
1921 Restricted { rules: Vec<AllowRule> },
1924
1925 KnockRestricted { rules: Vec<AllowRule> },
1929
1930 Custom {
1932 repr: String,
1934 },
1935}
1936
1937#[derive(Debug, Clone, uniffi::Enum)]
1939pub enum AllowRule {
1940 RoomMembership { room_id: String },
1943
1944 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}