1use std::{
16 collections::HashMap,
17 fmt::Debug,
18 path::PathBuf,
19 sync::{Arc, OnceLock},
20 time::Duration,
21};
22
23use anyhow::{anyhow, Context as _};
24use futures_util::pin_mut;
25#[cfg(not(target_family = "wasm"))]
26use matrix_sdk::media::MediaFileHandle as SdkMediaFileHandle;
27#[cfg(feature = "sqlite")]
28use matrix_sdk::STATE_STORE_DATABASE_NAME;
29use matrix_sdk::{
30 authentication::oauth::{ClientId, OAuthAuthorizationData, OAuthError, OAuthSession},
31 deserialized_responses::RawAnySyncOrStrippedTimelineEvent,
32 executor::AbortOnDrop,
33 media::{MediaFormat, MediaRequestParameters, MediaRetentionPolicy, MediaThumbnailSettings},
34 ruma::{
35 api::client::{
36 discovery::{
37 discover_homeserver::RtcFocusInfo,
38 get_authorization_server_metadata::v1::Prompt as RumaOidcPrompt,
39 },
40 push::{EmailPusherData, PusherIds, PusherInit, PusherKind as RumaPusherKind},
41 room::{create_room, Visibility},
42 session::get_login_types,
43 user_directory::search_users,
44 },
45 events::{
46 room::{
47 avatar::RoomAvatarEventContent, encryption::RoomEncryptionEventContent,
48 message::MessageType,
49 },
50 AnyInitialStateEvent, InitialStateEvent,
51 },
52 serde::Raw,
53 EventEncryptionAlgorithm, RoomId, TransactionId, UInt, UserId,
54 },
55 sliding_sync::Version as SdkSlidingSyncVersion,
56 store::RoomLoadSettings as SdkRoomLoadSettings,
57 task_monitor::BackgroundTaskFailureReason,
58 Account, AuthApi, AuthSession, Client as MatrixClient, Error, SessionChange, SessionTokens,
59};
60use matrix_sdk_common::{
61 cross_process_lock::CrossProcessLockConfig, stream::StreamExt, SendOutsideWasm, SyncOutsideWasm,
62};
63use matrix_sdk_ui::{
64 notification_client::{
65 NotificationClient as MatrixNotificationClient,
66 NotificationProcessSetup as MatrixNotificationProcessSetup,
67 },
68 spaces::SpaceService as UISpaceService,
69 unable_to_decrypt_hook::UtdHookManager,
70};
71use mime::Mime;
72use oauth2::Scope;
73use ruma::{
74 api::client::{
75 alias::get_alias,
76 discovery::get_authorization_server_metadata::v1::{
77 AccountManagementActionData, DeviceDeleteData, DeviceViewData,
78 },
79 error::ErrorKind,
80 profile::{AvatarUrl, DisplayName},
81 room::create_room::{v3::CreationContent, RoomPowerLevelsContentOverride},
82 uiaa::{EmailUserIdentifier, UserIdentifier},
83 },
84 events::{
85 direct::DirectEventContent,
86 fully_read::FullyReadEventContent,
87 identity_server::IdentityServerEventContent,
88 ignored_user_list::IgnoredUserListEventContent,
89 key::verification::request::ToDeviceKeyVerificationRequestEvent,
90 marked_unread::{MarkedUnreadEventContent, UnstableMarkedUnreadEventContent},
91 push_rules::PushRulesEventContent,
92 room::{
93 history_visibility::RoomHistoryVisibilityEventContent,
94 join_rules::{
95 AllowRule as RumaAllowRule, JoinRule as RumaJoinRule, RoomJoinRulesEventContent,
96 },
97 message::{OriginalSyncRoomMessageEvent, Relation},
98 },
99 secret_storage::{
100 default_key::SecretStorageDefaultKeyEventContent, key::SecretStorageKeyEventContent,
101 },
102 tag::TagEventContent,
103 AnyMessageLikeEventContent, AnySyncTimelineEvent,
104 GlobalAccountDataEvent as RumaGlobalAccountDataEvent,
105 RoomAccountDataEvent as RumaRoomAccountDataEvent,
106 },
107 push::{HttpPusherData as RumaHttpPusherData, PushFormat as RumaPushFormat},
108 room::RoomType,
109 OwnedDeviceId, OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
110};
111use serde::{Deserialize, Serialize};
112use serde_json::{json, Value};
113use tokio::sync::broadcast::error::RecvError;
114use tracing::{debug, error};
115use url::Url;
116
117use super::{
118 room::{room_info::RoomInfo, Room},
119 session_verification::SessionVerificationController,
120};
121use crate::{
122 authentication::{HomeserverLoginDetails, OidcConfiguration, OidcError, SsoError, SsoHandler},
123 client,
124 encryption::Encryption,
125 notification::{
126 NotificationClient, NotificationEvent, NotificationItem, NotificationRoomInfo,
127 NotificationSenderInfo,
128 },
129 notification_settings::NotificationSettings,
130 qr_code::{GrantLoginWithQrCodeHandler, LoginWithQrCodeHandler},
131 room::{RoomHistoryVisibility, RoomInfoListener, RoomSendQueueUpdate},
132 room_directory_search::RoomDirectorySearch,
133 room_preview::RoomPreview,
134 ruma::{
135 AccountDataEvent, AccountDataEventType, AuthData, InviteAvatars, MediaPreviewConfig,
136 MediaPreviews, MediaSource, RoomAccountDataEvent, RoomAccountDataEventType,
137 },
138 runtime::get_runtime_handle,
139 spaces::SpaceService,
140 sync_service::{SyncService, SyncServiceBuilder},
141 sync_v2::{SyncListenerV2, SyncResponseV2, SyncSettingsV2},
142 task_handle::TaskHandle,
143 utd::{UnableToDecryptDelegate, UtdHook},
144 utils::AsyncRuntimeDropped,
145 ClientError,
146};
147
148#[derive(Clone, uniffi::Record)]
149pub struct PusherIdentifiers {
150 pub pushkey: String,
151 pub app_id: String,
152}
153
154impl From<PusherIdentifiers> for PusherIds {
155 fn from(value: PusherIdentifiers) -> Self {
156 Self::new(value.pushkey, value.app_id)
157 }
158}
159
160#[derive(Clone, uniffi::Record)]
161pub struct HttpPusherData {
162 pub url: String,
163 pub format: Option<PushFormat>,
164 pub default_payload: Option<String>,
165}
166
167#[derive(Clone, uniffi::Enum)]
168pub enum PusherKind {
169 Http { data: HttpPusherData },
170 Email,
171}
172
173impl TryFrom<PusherKind> for RumaPusherKind {
174 type Error = anyhow::Error;
175
176 fn try_from(value: PusherKind) -> anyhow::Result<Self> {
177 match value {
178 PusherKind::Http { data } => {
179 let mut ruma_data = RumaHttpPusherData::new(data.url);
180 if let Some(payload) = data.default_payload {
181 let json: Value = serde_json::from_str(&payload)?;
182 ruma_data.data.insert("default_payload".to_owned(), json);
183 }
184 ruma_data.format = data.format.map(Into::into);
185 Ok(Self::Http(ruma_data))
186 }
187 PusherKind::Email => {
188 let ruma_data = EmailPusherData::new();
189 Ok(Self::Email(ruma_data))
190 }
191 }
192 }
193}
194
195#[derive(Clone, uniffi::Enum)]
196pub enum PushFormat {
197 EventIdOnly,
198}
199
200impl From<PushFormat> for RumaPushFormat {
201 fn from(value: PushFormat) -> Self {
202 match value {
203 client::PushFormat::EventIdOnly => Self::EventIdOnly,
204 }
205 }
206}
207
208#[matrix_sdk_ffi_macros::export(callback_interface)]
209pub trait ClientDelegate: SyncOutsideWasm + SendOutsideWasm {
210 fn did_receive_auth_error(&self, is_soft_logout: bool);
212
213 fn on_background_task_error_report(
219 &self,
220 task_name: String,
221 error: BackgroundTaskFailureReason,
222 );
223}
224
225#[matrix_sdk_ffi_macros::export(callback_interface)]
226pub trait ClientSessionDelegate: SyncOutsideWasm + SendOutsideWasm {
227 fn retrieve_session_from_keychain(&self, user_id: String) -> Result<Session, ClientError>;
228 fn save_session_in_keychain(&self, session: Session);
229}
230
231#[matrix_sdk_ffi_macros::export(callback_interface)]
232pub trait ProgressWatcher: SyncOutsideWasm + SendOutsideWasm {
233 fn transmission_progress(&self, progress: TransmissionProgress);
234}
235
236#[matrix_sdk_ffi_macros::export(callback_interface)]
238pub trait SendQueueRoomUpdateListener: SyncOutsideWasm + SendOutsideWasm {
239 fn on_update(&self, room_id: String, update: RoomSendQueueUpdate);
241}
242
243#[matrix_sdk_ffi_macros::export(callback_interface)]
245pub trait SendQueueRoomErrorListener: SyncOutsideWasm + SendOutsideWasm {
246 fn on_error(&self, room_id: String, error: ClientError);
249}
250
251#[matrix_sdk_ffi_macros::export(callback_interface)]
253pub trait AccountDataListener: SyncOutsideWasm + SendOutsideWasm {
254 fn on_change(&self, event: AccountDataEvent);
256}
257
258#[matrix_sdk_ffi_macros::export(callback_interface)]
261pub trait DuplicateKeyUploadErrorListener: SyncOutsideWasm + SendOutsideWasm {
262 fn on_duplicate_key_upload_error(&self, message: Option<DuplicateOneTimeKeyErrorMessage>);
264}
265
266#[derive(uniffi::Record)]
269pub struct DuplicateOneTimeKeyErrorMessage {
270 pub old_key: String,
272 pub new_key: String,
274}
275
276impl From<matrix_sdk::encryption::DuplicateOneTimeKeyErrorMessage>
277 for DuplicateOneTimeKeyErrorMessage
278{
279 fn from(value: matrix_sdk::encryption::DuplicateOneTimeKeyErrorMessage) -> Self {
280 Self { old_key: value.old_key.to_base64(), new_key: value.new_key.to_base64() }
281 }
282}
283
284#[matrix_sdk_ffi_macros::export(callback_interface)]
286pub trait RoomAccountDataListener: SyncOutsideWasm + SendOutsideWasm {
287 fn on_change(&self, event: RoomAccountDataEvent, room_id: String);
289}
290
291#[matrix_sdk_ffi_macros::export(callback_interface)]
296pub trait SyncNotificationListener: SyncOutsideWasm + SendOutsideWasm {
297 fn on_notification(&self, notification: NotificationItem, room_id: String);
299}
300
301#[derive(Clone, Copy, uniffi::Record)]
302pub struct TransmissionProgress {
303 pub current: u64,
304 pub total: u64,
305}
306
307impl From<matrix_sdk::TransmissionProgress> for TransmissionProgress {
308 fn from(value: matrix_sdk::TransmissionProgress) -> Self {
309 Self {
310 current: value.current.try_into().unwrap_or(u64::MAX),
311 total: value.total.try_into().unwrap_or(u64::MAX),
312 }
313 }
314}
315
316struct ClientDelegateData {
317 delegate: Arc<dyn ClientDelegate>,
319
320 _background_error_listener_task: Arc<AbortOnDrop<()>>,
323}
324
325#[derive(uniffi::Object)]
326pub struct Client {
327 pub(crate) inner: AsyncRuntimeDropped<MatrixClient>,
328
329 delegate_data: OnceLock<ClientDelegateData>,
330
331 pub(crate) utd_hook_manager: OnceLock<Arc<UtdHookManager>>,
332
333 session_verification_controller:
334 Arc<tokio::sync::RwLock<Option<SessionVerificationController>>>,
335
336 #[cfg_attr(not(feature = "sqlite"), allow(unused))]
340 store_path: Option<PathBuf>,
341}
342
343impl Client {
344 pub async fn new(
345 sdk_client: MatrixClient,
346 session_delegate: Option<Arc<dyn ClientSessionDelegate>>,
347 store_path: Option<PathBuf>,
348 ) -> Result<Self, ClientError> {
349 let session_verification_controller: Arc<
350 tokio::sync::RwLock<Option<SessionVerificationController>>,
351 > = Default::default();
352 let controller = session_verification_controller.clone();
353 sdk_client.add_event_handler(
354 move |event: ToDeviceKeyVerificationRequestEvent| async move {
355 if let Some(session_verification_controller) = &*controller.clone().read().await {
356 session_verification_controller
357 .process_incoming_verification_request(
358 &event.sender,
359 event.content.transaction_id,
360 )
361 .await;
362 }
363 },
364 );
365
366 let controller = session_verification_controller.clone();
367 sdk_client.add_event_handler(move |event: OriginalSyncRoomMessageEvent| async move {
368 if let MessageType::VerificationRequest(_) = &event.content.msgtype {
369 if let Some(session_verification_controller) = &*controller.clone().read().await {
370 session_verification_controller
371 .process_incoming_verification_request(&event.sender, event.event_id)
372 .await;
373 }
374 }
375 });
376
377 let store_mode = sdk_client.cross_process_lock_config();
378
379 let client = Client {
380 inner: AsyncRuntimeDropped::new(sdk_client.clone()),
381 delegate_data: OnceLock::new(),
382 utd_hook_manager: OnceLock::new(),
383 session_verification_controller,
384 store_path,
385 };
386
387 match store_mode {
388 CrossProcessLockConfig::MultiProcess { holder_name } => {
389 if session_delegate.is_none() {
390 return Err(anyhow::anyhow!(
391 "missing session delegates with multi-process lock configuration"
392 ))?;
393 }
394 client.inner.oauth().enable_cross_process_refresh_lock(holder_name.clone()).await?;
395 }
396 CrossProcessLockConfig::SingleProcess => {}
397 }
398
399 if let Some(session_delegate) = session_delegate {
400 client.inner.set_session_callbacks(
401 {
402 let session_delegate = session_delegate.clone();
403 Box::new(move |client| {
404 let session_delegate = session_delegate.clone();
405 let user_id = client.user_id().context("user isn't logged in")?;
406 Ok(Self::retrieve_session(session_delegate, user_id)?)
407 })
408 },
409 {
410 let session_delegate = session_delegate.clone();
411 Box::new(move |client| {
412 let session_delegate = session_delegate.clone();
413 Ok(Self::save_session(session_delegate, client)?)
414 })
415 },
416 )?;
417 }
418
419 Ok(client)
420 }
421}
422
423#[matrix_sdk_ffi_macros::export]
424impl Client {
425 pub async fn optimize_stores(&self) -> Result<(), ClientError> {
428 Ok(self.inner.optimize_stores().await?)
429 }
430
431 pub async fn get_store_sizes(&self) -> Result<StoreSizes, ClientError> {
433 Ok(self.inner.get_store_sizes().await?.into())
434 }
435
436 pub async fn homeserver_login_details(&self) -> Arc<HomeserverLoginDetails> {
438 let oauth = self.inner.oauth();
439 let (supports_oidc_login, supported_oidc_prompts) = match oauth.server_metadata().await {
440 Ok(metadata) => {
441 let prompts =
442 metadata.prompt_values_supported.into_iter().map(Into::into).collect();
443
444 (true, prompts)
445 }
446 Err(error) => {
447 error!("Failed to fetch OIDC provider metadata: {error}");
448 (false, Default::default())
449 }
450 };
451
452 let login_types = self.inner.matrix_auth().get_login_types().await.ok();
453 let supports_password_login = login_types
454 .as_ref()
455 .map(|login_types| {
456 login_types.flows.iter().any(|login_type| {
457 matches!(login_type, get_login_types::v3::LoginType::Password(_))
458 })
459 })
460 .unwrap_or(false);
461 let supports_sso_login = login_types
462 .as_ref()
463 .map(|login_types| {
464 login_types
465 .flows
466 .iter()
467 .any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Sso(_)))
468 })
469 .unwrap_or(false);
470 let sliding_sync_version = self.sliding_sync_version();
471
472 Arc::new(HomeserverLoginDetails {
473 url: self.homeserver(),
474 sliding_sync_version,
475 supports_oidc_login,
476 supported_oidc_prompts,
477 supports_sso_login,
478 supports_password_login,
479 })
480 }
481
482 pub async fn login(
484 &self,
485 username: String,
486 password: String,
487 initial_device_name: Option<String>,
488 device_id: Option<String>,
489 ) -> Result<(), ClientError> {
490 let mut builder = self.inner.matrix_auth().login_username(&username, &password);
491 if let Some(initial_device_name) = initial_device_name.as_ref() {
492 builder = builder.initial_device_display_name(initial_device_name);
493 }
494 if let Some(device_id) = device_id.as_ref() {
495 builder = builder.device_id(device_id);
496 }
497 builder.send().await?;
498 Ok(())
499 }
500
501 pub async fn custom_login_with_jwt(
505 &self,
506 jwt: String,
507 initial_device_name: Option<String>,
508 device_id: Option<String>,
509 ) -> Result<(), ClientError> {
510 let data = json!({ "token": jwt }).as_object().unwrap().clone();
511
512 let mut builder = self.inner.matrix_auth().login_custom("org.matrix.login.jwt", data)?;
513
514 if let Some(initial_device_name) = initial_device_name.as_ref() {
515 builder = builder.initial_device_display_name(initial_device_name);
516 }
517
518 if let Some(device_id) = device_id.as_ref() {
519 builder = builder.device_id(device_id);
520 }
521
522 builder.send().await?;
523 Ok(())
524 }
525
526 pub async fn login_with_email(
528 &self,
529 email: String,
530 password: String,
531 initial_device_name: Option<String>,
532 device_id: Option<String>,
533 ) -> Result<(), ClientError> {
534 let mut builder = self
535 .inner
536 .matrix_auth()
537 .login_identifier(UserIdentifier::Email(EmailUserIdentifier::new(email)), &password);
538
539 if let Some(initial_device_name) = initial_device_name.as_ref() {
540 builder = builder.initial_device_display_name(initial_device_name);
541 }
542
543 if let Some(device_id) = device_id.as_ref() {
544 builder = builder.device_id(device_id);
545 }
546
547 builder.send().await?;
548
549 Ok(())
550 }
551
552 pub(crate) async fn start_sso_login(
554 self: &Arc<Self>,
555 redirect_url: String,
556 idp_id: Option<String>,
557 ) -> Result<Arc<SsoHandler>, SsoError> {
558 let auth = self.inner.matrix_auth();
559 let url = auth
560 .get_sso_login_url(redirect_url.as_str(), idp_id.as_deref())
561 .await
562 .map_err(|e| SsoError::Generic { message: e.to_string() })?;
563 Ok(Arc::new(SsoHandler { client: Arc::clone(self), url }))
564 }
565
566 pub async fn url_for_oidc(
599 &self,
600 oidc_configuration: &OidcConfiguration,
601 prompt: Option<OidcPrompt>,
602 login_hint: Option<String>,
603 device_id: Option<String>,
604 additional_scopes: Option<Vec<String>>,
605 ) -> Result<Arc<OAuthAuthorizationData>, OidcError> {
606 let registration_data = oidc_configuration.registration_data()?;
607 let redirect_uri = oidc_configuration.redirect_uri()?;
608
609 let device_id = device_id.map(OwnedDeviceId::from);
610
611 let additional_scopes =
612 additional_scopes.map(|scopes| scopes.into_iter().map(Scope::new).collect::<Vec<_>>());
613
614 let mut url_builder = self.inner.oauth().login(
615 redirect_uri,
616 device_id,
617 Some(registration_data),
618 additional_scopes,
619 );
620
621 if let Some(prompt) = prompt {
622 url_builder = url_builder.prompt(vec![prompt.into()]);
623 }
624 if let Some(login_hint) = login_hint {
625 url_builder = url_builder.login_hint(login_hint);
626 }
627
628 let data = url_builder.build().await?;
629
630 Ok(Arc::new(data))
631 }
632
633 pub async fn abort_oidc_auth(&self, authorization_data: Arc<OAuthAuthorizationData>) {
636 self.inner.oauth().abort_login(&authorization_data.state).await;
637 }
638
639 pub async fn login_with_oidc_callback(&self, callback_url: String) -> Result<(), OidcError> {
641 let url = Url::parse(&callback_url).or(Err(OidcError::CallbackUrlInvalid))?;
642
643 self.inner.oauth().finish_login(url.into()).await?;
644
645 Ok(())
646 }
647
648 pub fn new_login_with_qr_code_handler(
656 self: Arc<Self>,
657 oidc_configuration: OidcConfiguration,
658 ) -> LoginWithQrCodeHandler {
659 LoginWithQrCodeHandler::new(self.inner.oauth(), oidc_configuration)
660 }
661
662 pub fn new_grant_login_with_qr_code_handler(self: Arc<Self>) -> GrantLoginWithQrCodeHandler {
665 GrantLoginWithQrCodeHandler::new(self.inner.oauth())
666 }
667
668 pub async fn restore_session(&self, session: Session) -> Result<(), ClientError> {
675 self.restore_session_with(session, RoomLoadSettings::All).await
676 }
677
678 pub async fn restore_session_with(
682 &self,
683 session: Session,
684 room_load_settings: RoomLoadSettings,
685 ) -> Result<(), ClientError> {
686 let sliding_sync_version = session.sliding_sync_version.clone();
687 let auth_session: AuthSession = session.try_into()?;
688
689 self.inner
690 .restore_session_with(
691 auth_session,
692 room_load_settings
693 .try_into()
694 .map_err(|error| ClientError::from_str(error, None))?,
695 )
696 .await?;
697 self.inner.set_sliding_sync_version(sliding_sync_version.try_into()?);
698
699 Ok(())
700 }
701
702 pub async fn enable_all_send_queues(&self, enable: bool) {
710 self.inner.send_queue().set_enabled(enable).await;
711 }
712
713 pub fn enable_send_queue_upload_progress(&self, enable: bool) {
716 self.inner.send_queue().enable_upload_progress(enable);
717 }
718
719 pub async fn subscribe_to_send_queue_updates(
726 &self,
727 listener: Box<dyn SendQueueRoomUpdateListener>,
728 ) -> Result<Arc<TaskHandle>, ClientError> {
729 let q = self.inner.send_queue();
730 let local_echoes = q.local_echoes().await?;
731 let mut subscriber = q.subscribe();
732
733 for (room_id, local_echoes) in local_echoes {
734 for local_echo in local_echoes {
735 listener.on_update(
736 room_id.clone().into(),
737 RoomSendQueueUpdate::NewLocalEvent {
738 transaction_id: local_echo.transaction_id.into(),
739 },
740 );
741 }
742 }
743
744 Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
745 loop {
746 match subscriber.recv().await {
747 Ok(update) => {
748 let room_id = update.room_id.to_string();
749 match update.update.try_into() {
750 Ok(update) => listener.on_update(room_id, update),
751 Err(err) => error!("error when converting send queue update: {err}"),
752 }
753 }
754 Err(err) => {
755 error!("error when listening to the send queue update reporter: {err}");
756 }
757 }
758 }
759 }))))
760 }
761
762 pub fn subscribe_to_send_queue_status(
768 &self,
769 listener: Box<dyn SendQueueRoomErrorListener>,
770 ) -> Arc<TaskHandle> {
771 let q = self.inner.send_queue();
772 let mut subscriber = q.subscribe_errors();
773
774 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
775 q.respawn_tasks_for_rooms_with_unsent_requests().await;
778
779 loop {
780 match subscriber.recv().await {
781 Ok(report) => listener
782 .on_error(report.room_id.to_string(), ClientError::from_err(report.error)),
783 Err(err) => {
784 error!("error when listening to the send queue error reporter: {err}");
785 }
786 }
787 }
788 })))
789 }
790
791 pub fn subscribe_to_duplicate_key_upload_errors(
794 &self,
795 listener: Box<dyn DuplicateKeyUploadErrorListener>,
796 ) -> Arc<TaskHandle> {
797 let mut subscriber = self.inner.subscribe_to_duplicate_key_upload_errors();
798
799 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
800 loop {
801 match subscriber.recv().await {
802 Ok(message) => {
803 listener.on_duplicate_key_upload_error(message.map(|m| m.into()))
804 }
805 Err(err) => {
806 error!("error when listening to key upload errors: {err}");
807 }
808 }
809 }
810 })))
811 }
812
813 pub fn observe_account_data_event(
819 &self,
820 event_type: AccountDataEventType,
821 listener: Box<dyn AccountDataListener>,
822 ) -> Arc<TaskHandle> {
823 macro_rules! observe {
824 ($t:ty, $cb: expr) => {{
825 let observer =
827 Arc::new(self.inner.observe_events::<RumaGlobalAccountDataEvent<$t>, ()>());
828
829 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
830 let mut subscriber = observer.subscribe();
831 loop {
832 if let Some(next) = subscriber.next().await {
833 $cb(next.0);
834 }
835 }
836 })))
837 }};
838
839 ($t:ty) => {{
840 observe!($t, |event: RumaGlobalAccountDataEvent<$t>| {
841 listener.on_change(event.into());
842 })
843 }};
844 }
845
846 match event_type {
847 AccountDataEventType::Direct => {
848 observe!(DirectEventContent)
849 }
850 AccountDataEventType::IdentityServer => {
851 observe!(IdentityServerEventContent)
852 }
853 AccountDataEventType::IgnoredUserList => {
854 observe!(IgnoredUserListEventContent)
855 }
856 AccountDataEventType::PushRules => {
857 observe!(PushRulesEventContent, |event: RumaGlobalAccountDataEvent<
858 PushRulesEventContent,
859 >| {
860 if let Ok(event) = event.try_into() {
861 listener.on_change(event);
862 }
863 })
864 }
865 AccountDataEventType::SecretStorageDefaultKey => {
866 observe!(SecretStorageDefaultKeyEventContent)
867 }
868 AccountDataEventType::SecretStorageKey { key_id } => {
869 observe!(SecretStorageKeyEventContent, |event: RumaGlobalAccountDataEvent<
870 SecretStorageKeyEventContent,
871 >| {
872 if event.content.key_id != key_id {
873 return;
874 }
875 if let Ok(event) = event.try_into() {
876 listener.on_change(event);
877 }
878 })
879 }
880 }
881 }
882
883 pub fn observe_room_account_data_event(
889 &self,
890 room_id: String,
891 event_type: RoomAccountDataEventType,
892 listener: Box<dyn RoomAccountDataListener>,
893 ) -> Result<Arc<TaskHandle>, ClientError> {
894 macro_rules! observe {
895 ($t:ty, $cb: expr) => {{
896 let observer =
898 Arc::new(self.inner.observe_room_events::<RumaRoomAccountDataEvent<$t>, ()>(
899 &RoomId::parse(&room_id)?,
900 ));
901
902 Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
903 let mut subscriber = observer.subscribe();
904 loop {
905 if let Some(next) = subscriber.next().await {
906 $cb(next.0);
907 }
908 }
909 }))))
910 }};
911
912 ($t:ty) => {{
913 observe!($t, |event: RumaRoomAccountDataEvent<$t>| {
914 listener.on_change(event.into(), room_id.clone());
915 })
916 }};
917 }
918
919 match event_type {
920 RoomAccountDataEventType::FullyRead => {
921 observe!(FullyReadEventContent)
922 }
923 RoomAccountDataEventType::MarkedUnread => {
924 observe!(MarkedUnreadEventContent)
925 }
926 RoomAccountDataEventType::Tag => {
927 observe!(TagEventContent, |event: RumaRoomAccountDataEvent<TagEventContent>| {
928 if let Ok(event) = event.try_into() {
929 listener.on_change(event, room_id.clone());
930 }
931 })
932 }
933 RoomAccountDataEventType::UnstableMarkedUnread => {
934 observe!(UnstableMarkedUnreadEventContent)
935 }
936 }
937 }
938
939 pub async fn register_notification_handler(&self, listener: Box<dyn SyncNotificationListener>) {
951 let listener = Arc::new(listener);
952 self.inner
953 .register_notification_handler(move |notification, room, _client| {
954 let listener = listener.clone();
955 let room_id = room.room_id().to_string();
956
957 async move {
958 let is_noisy = notification.actions.iter().any(|a| a.sound().is_some());
960 let has_mention = notification.actions.iter().any(|a| a.is_highlight());
961
962 let actions: Vec<crate::notification_settings::Action> = notification
964 .actions
965 .into_iter()
966 .filter_map(|action| action.try_into().ok())
967 .collect();
968
969 let (sender, event, thread_id, raw_event) = match notification.event {
971 RawAnySyncOrStrippedTimelineEvent::Sync(raw) => {
972 let raw_event = raw.json().get().to_owned();
973 match raw.deserialize() {
974 Ok(deserialized) => {
975 let sender = deserialized.sender().to_owned();
976 let thread_id = match &deserialized {
977 AnySyncTimelineEvent::MessageLike(event) => {
978 match event.original_content() {
979 Some(AnyMessageLikeEventContent::RoomMessage(
980 content,
981 )) => match content.relates_to {
982 Some(Relation::Thread(thread)) => {
983 Some(thread.event_id.to_string())
984 }
985 _ => None,
986 },
987 _ => None,
988 }
989 }
990 _ => None,
991 };
992 let event = NotificationEvent::Timeline {
993 event: Arc::new(crate::event::TimelineEvent(Box::new(
994 deserialized,
995 ))),
996 };
997 (sender, event, thread_id, raw_event)
998 }
999 Err(err) => {
1000 tracing::warn!("Failed to deserialize timeline event: {err}");
1001 return;
1002 }
1003 }
1004 }
1005 RawAnySyncOrStrippedTimelineEvent::Stripped(raw) => {
1006 let raw_event = raw.json().get().to_owned();
1007 match raw.deserialize() {
1008 Ok(deserialized) => {
1009 let sender = deserialized.sender().to_owned();
1010 let event =
1011 NotificationEvent::Invite { sender: sender.to_string() };
1012 let thread_id = None;
1013 (sender, event, thread_id, raw_event)
1014 }
1015 Err(err) => {
1016 tracing::warn!(
1017 "Failed to deserialize stripped state event: {err}"
1018 );
1019 return;
1020 }
1021 }
1022 }
1023 };
1024
1025 let sender = room.get_member_no_sync(&sender).await.ok().flatten();
1027 let sender_info = if let Some(sender) = sender.as_ref() {
1028 NotificationSenderInfo {
1029 display_name: sender.display_name().map(|name| name.to_owned()),
1030 avatar_url: sender.avatar_url().map(|uri| uri.to_string()),
1031 is_name_ambiguous: sender.name_ambiguous(),
1032 }
1033 } else {
1034 NotificationSenderInfo {
1035 display_name: None,
1036 avatar_url: None,
1037 is_name_ambiguous: false,
1038 }
1039 };
1040
1041 let display_name = match room.display_name().await {
1043 Ok(name) => name.to_string(),
1044 Err(err) => {
1045 tracing::warn!("Failed to calculate the room's display name: {err}");
1046 return;
1047 }
1048 };
1049 let is_direct = match room.is_direct().await {
1050 Ok(is_direct) => is_direct,
1051 Err(err) => {
1052 tracing::warn!("Failed to determine if room is direct or not: {err}");
1053 return;
1054 }
1055 };
1056 let room_info = NotificationRoomInfo {
1057 display_name,
1058 avatar_url: room.avatar_url().map(Into::into),
1059 canonical_alias: room.canonical_alias().map(Into::into),
1060 topic: room.topic(),
1061 join_rule: room
1062 .join_rule()
1063 .map(TryInto::try_into)
1064 .transpose()
1065 .ok()
1066 .flatten(),
1067 joined_members_count: room.joined_members_count(),
1068 is_encrypted: Some(room.encryption_state().is_encrypted()),
1069 is_direct,
1070 is_space: room.is_space(),
1071 };
1072
1073 listener.on_notification(
1074 NotificationItem {
1075 event,
1076 raw_event,
1077 sender_info,
1078 room_info,
1079 is_noisy: Some(is_noisy),
1080 has_mention: Some(has_mention),
1081 thread_id,
1082 actions: Some(actions),
1083 },
1084 room_id,
1085 );
1086 }
1087 })
1088 .await;
1089 }
1090
1091 pub async fn get_url(&self, url: String) -> Result<Vec<u8>, ClientError> {
1100 let response = self.inner.http_client().get(url).send().await?;
1101 if response.status().is_success() {
1102 Ok(response.bytes().await?.into())
1103 } else {
1104 Err(ClientError::Generic {
1105 msg: response.status().to_string(),
1106 details: response.text().await.ok(),
1107 })
1108 }
1109 }
1110
1111 pub async fn reset_supported_versions(&self) -> Result<(), ClientError> {
1117 Ok(self.inner.reset_supported_versions().await?)
1118 }
1119
1120 pub async fn reset_well_known(&self) -> Result<(), ClientError> {
1126 Ok(self.inner.reset_well_known().await?)
1127 }
1128}
1129
1130#[matrix_sdk_ffi_macros::export]
1131impl Client {
1132 pub async fn get_media_file(
1136 &self,
1137 media_source: Arc<MediaSource>,
1138 filename: Option<String>,
1139 mime_type: String,
1140 use_cache: bool,
1141 temp_dir: Option<String>,
1142 ) -> Result<Arc<MediaFileHandle>, ClientError> {
1143 #[cfg(not(target_family = "wasm"))]
1144 {
1145 let source = (*media_source).clone();
1146 let mime_type: mime::Mime = mime_type.parse()?;
1147
1148 let handle = self
1149 .inner
1150 .media()
1151 .get_media_file(
1152 &MediaRequestParameters {
1153 source: source.media_source,
1154 format: MediaFormat::File,
1155 },
1156 filename,
1157 &mime_type,
1158 use_cache,
1159 temp_dir,
1160 )
1161 .await?;
1162
1163 Ok(Arc::new(MediaFileHandle::new(handle)))
1164 }
1165
1166 #[cfg(target_family = "wasm")]
1170 Err(ClientError::Generic {
1171 msg: "get_media_file is not supported on wasm platforms".to_owned(),
1172 details: None,
1173 })
1174 }
1175
1176 pub async fn set_display_name(&self, name: String) -> Result<(), ClientError> {
1177 #[cfg(not(target_family = "wasm"))]
1178 {
1179 self.inner
1180 .account()
1181 .set_display_name(Some(name.as_str()))
1182 .await
1183 .context("Unable to set display name")?;
1184 }
1185
1186 #[cfg(target_family = "wasm")]
1187 {
1188 self.inner.account().set_display_name(Some(name.as_str())).await.map_err(|e| {
1189 ClientError::Generic {
1190 msg: "Unable to set display name".to_owned(),
1191 details: Some(e.to_string()),
1192 }
1193 })?;
1194 }
1195
1196 Ok(())
1197 }
1198}
1199
1200#[matrix_sdk_ffi_macros::export]
1201impl Client {
1202 pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
1204 self.inner.sliding_sync_version().into()
1205 }
1206
1207 pub async fn available_sliding_sync_versions(&self) -> Vec<SlidingSyncVersion> {
1215 self.inner.available_sliding_sync_versions().await.into_iter().map(Into::into).collect()
1216 }
1217
1218 pub fn set_delegate(
1221 self: Arc<Self>,
1222 delegate: Option<Box<dyn ClientDelegate>>,
1223 ) -> Result<Option<Arc<TaskHandle>>, ClientError> {
1224 if self.delegate_data.get().is_some() {
1225 return Err(ClientError::Generic {
1226 msg: "Delegate already initialized".to_owned(),
1227 details: None,
1228 });
1229 }
1230
1231 let handle = delegate.map(|delegate| {
1232 let mut session_change_receiver = self.inner.subscribe_to_session_changes();
1233 let client_clone = self.clone();
1234 let session_change_task = get_runtime_handle().spawn(async move {
1235 loop {
1236 match session_change_receiver.recv().await {
1237 Ok(session_change) => client_clone.process_session_change(session_change),
1238 Err(receive_error) => {
1239 if let RecvError::Closed = receive_error {
1240 break;
1241 }
1242 }
1243 }
1244 }
1245 });
1246
1247 let delegate: Arc<dyn ClientDelegate> = delegate.into();
1248
1249 let client = self.inner.clone();
1250 let delegate_clone = delegate.clone();
1251 let task = Arc::new(AbortOnDrop::new(get_runtime_handle().spawn(async move {
1252 let mut receiver = client.task_monitor().subscribe();
1253 while let Ok(error) = receiver.recv().await {
1254 delegate_clone.on_background_task_error_report(error.task.name, error.reason);
1255 }
1256 })));
1257
1258 let delegate_data =
1259 ClientDelegateData { delegate, _background_error_listener_task: task };
1260
1261 self.delegate_data.get_or_init(|| delegate_data);
1262
1263 Arc::new(TaskHandle::new(session_change_task))
1264 });
1265
1266 Ok(handle)
1267 }
1268
1269 pub async fn set_utd_delegate(
1272 self: Arc<Self>,
1273 utd_delegate: Box<dyn UnableToDecryptDelegate>,
1274 ) -> Result<(), ClientError> {
1275 if self.utd_hook_manager.get().is_some() {
1276 return Err(ClientError::Generic {
1277 msg: "UTD delegate already initialized".to_owned(),
1278 details: None,
1279 });
1280 }
1281
1282 const UTD_HOOK_GRACE_PERIOD: Duration = Duration::from_secs(60);
1285
1286 let mut utd_hook_manager = UtdHookManager::new(
1287 Arc::new(UtdHook { delegate: utd_delegate.into() }),
1288 (*self.inner).clone(),
1289 )
1290 .with_max_delay(UTD_HOOK_GRACE_PERIOD);
1291
1292 if let Err(e) = utd_hook_manager.reload_from_store().await {
1293 error!("Unable to reload UTD hook data from data store: {e}");
1294 }
1297
1298 self.utd_hook_manager.get_or_init(|| Arc::new(utd_hook_manager));
1299
1300 Ok(())
1301 }
1302
1303 pub fn session(&self) -> Result<Session, ClientError> {
1304 Self::session_inner((*self.inner).clone())
1305 }
1306
1307 pub async fn account_url(
1308 &self,
1309 action: Option<AccountManagementAction>,
1310 ) -> Result<Option<String>, ClientError> {
1311 if !matches!(self.inner.auth_api(), Some(AuthApi::OAuth(..))) {
1312 return Ok(None);
1313 }
1314
1315 let server_metadata = match self.inner.oauth().cached_server_metadata().await {
1316 Ok(server_metadata) => server_metadata,
1317 Err(e) => {
1318 error!("Failed retrieving cached server metadata: {e}");
1319 return Err(OAuthError::from(e).into());
1320 }
1321 };
1322
1323 Ok(if let Some(action) = &action {
1324 server_metadata.account_management_url_with_action(action.into())
1325 } else {
1326 server_metadata.account_management_uri
1327 }
1328 .map(Into::into))
1329 }
1330
1331 pub fn user_id(&self) -> Result<String, ClientError> {
1332 let user_id = self.inner.user_id().context("No User ID found")?;
1333 Ok(user_id.to_string())
1334 }
1335
1336 pub fn user_id_server_name(&self) -> Result<String, ClientError> {
1338 let user_id = self.inner.user_id().context("No User ID found")?;
1339 Ok(user_id.server_name().to_string())
1340 }
1341
1342 pub async fn display_name(&self) -> Result<String, ClientError> {
1343 let display_name =
1344 self.inner.account().get_display_name().await?.context("No User ID found")?;
1345 Ok(display_name)
1346 }
1347
1348 pub async fn upload_avatar(&self, mime_type: String, data: Vec<u8>) -> Result<(), ClientError> {
1349 let mime: Mime = mime_type.parse()?;
1350 self.inner.account().upload_avatar(&mime, data).await?;
1351 Ok(())
1352 }
1353
1354 pub async fn remove_avatar(&self) -> Result<(), ClientError> {
1355 self.inner.account().set_avatar_url(None).await?;
1356 Ok(())
1357 }
1358
1359 pub async fn avatar_url(&self) -> Result<Option<String>, ClientError> {
1362 let avatar_url = self.inner.account().get_avatar_url().await?;
1363
1364 Ok(avatar_url.map(|u| u.to_string()))
1365 }
1366
1367 pub async fn cached_avatar_url(&self) -> Result<Option<String>, ClientError> {
1369 Ok(self.inner.account().get_cached_avatar_url().await?.map(Into::into))
1370 }
1371
1372 pub fn device_id(&self) -> Result<String, ClientError> {
1373 let device_id = self.inner.device_id().context("No Device ID found")?;
1374 Ok(device_id.to_string())
1375 }
1376
1377 pub async fn create_room(&self, request: CreateRoomParameters) -> Result<String, ClientError> {
1378 let response = self.inner.create_room(request.try_into()?).await?;
1379 Ok(String::from(response.room_id()))
1380 }
1381
1382 pub async fn account_data(&self, event_type: String) -> Result<Option<String>, ClientError> {
1387 let event = self.inner.account().account_data_raw(event_type.into()).await?;
1388 Ok(event.map(|e| e.json().get().to_owned()))
1389 }
1390
1391 pub async fn set_account_data(
1395 &self,
1396 event_type: String,
1397 content: String,
1398 ) -> Result<(), ClientError> {
1399 let raw_content = Raw::from_json_string(content)?;
1400 self.inner.account().set_account_data_raw(event_type.into(), raw_content).await?;
1401 Ok(())
1402 }
1403
1404 pub async fn upload_media(
1405 &self,
1406 mime_type: String,
1407 data: Vec<u8>,
1408 progress_watcher: Option<Box<dyn ProgressWatcher>>,
1409 ) -> Result<String, ClientError> {
1410 let mime_type: mime::Mime = mime_type.parse().context("Parsing mime type")?;
1411 let request = self.inner.media().upload(&mime_type, data, None);
1412
1413 if let Some(progress_watcher) = progress_watcher {
1414 let mut subscriber = request.subscribe_to_send_progress();
1415 get_runtime_handle().spawn(async move {
1416 while let Some(progress) = subscriber.next().await {
1417 progress_watcher.transmission_progress(progress.into());
1418 }
1419 });
1420 }
1421
1422 let response = request.await?;
1423
1424 Ok(String::from(response.content_uri))
1425 }
1426
1427 pub async fn get_media_content(
1428 &self,
1429 media_source: Arc<MediaSource>,
1430 ) -> Result<Vec<u8>, ClientError> {
1431 let source = (*media_source).clone().media_source;
1432
1433 debug!(?source, "requesting media file");
1434 Ok(self
1435 .inner
1436 .media()
1437 .get_media_content(&MediaRequestParameters { source, format: MediaFormat::File }, true)
1438 .await?)
1439 }
1440
1441 pub async fn get_media_thumbnail(
1442 &self,
1443 media_source: Arc<MediaSource>,
1444 width: u64,
1445 height: u64,
1446 ) -> Result<Vec<u8>, ClientError> {
1447 let source = (*media_source).clone().media_source;
1448
1449 debug!(?source, width, height, "requesting media thumbnail");
1450 Ok(self
1451 .inner
1452 .media()
1453 .get_media_content(
1454 &MediaRequestParameters {
1455 source,
1456 format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(
1457 UInt::new(width).unwrap(),
1458 UInt::new(height).unwrap(),
1459 )),
1460 },
1461 true,
1462 )
1463 .await?)
1464 }
1465
1466 pub async fn get_session_verification_controller(
1467 &self,
1468 ) -> Result<Arc<SessionVerificationController>, ClientError> {
1469 if let Some(session_verification_controller) =
1470 &*self.session_verification_controller.read().await
1471 {
1472 return Ok(Arc::new(session_verification_controller.clone()));
1473 }
1474 let user_id = self.inner.user_id().context("Failed retrieving current user_id")?;
1475 let user_identity = self
1476 .inner
1477 .encryption()
1478 .get_user_identity(user_id)
1479 .await?
1480 .context("Failed retrieving user identity")?;
1481
1482 let session_verification_controller = SessionVerificationController::new(
1483 self.inner.encryption(),
1484 user_identity,
1485 self.inner.account(),
1486 );
1487
1488 *self.session_verification_controller.write().await =
1489 Some(session_verification_controller.clone());
1490
1491 Ok(Arc::new(session_verification_controller))
1492 }
1493
1494 pub async fn logout(&self) -> Result<(), ClientError> {
1496 Ok(self.inner.logout().await?)
1497 }
1498
1499 pub async fn set_pusher(
1501 &self,
1502 identifiers: PusherIdentifiers,
1503 kind: PusherKind,
1504 app_display_name: String,
1505 device_display_name: String,
1506 profile_tag: Option<String>,
1507 lang: String,
1508 ) -> Result<(), ClientError> {
1509 let ids = identifiers.into();
1510
1511 let pusher_init = PusherInit {
1512 ids,
1513 kind: kind.try_into()?,
1514 app_display_name,
1515 device_display_name,
1516 profile_tag,
1517 lang,
1518 };
1519 self.inner.pusher().set(pusher_init.into()).await?;
1520 Ok(())
1521 }
1522
1523 pub async fn delete_pusher(&self, identifiers: PusherIdentifiers) -> Result<(), ClientError> {
1525 self.inner.pusher().delete(identifiers.into()).await?;
1526 Ok(())
1527 }
1528
1529 pub fn homeserver(&self) -> String {
1531 self.inner.homeserver().to_string()
1532 }
1533
1534 pub fn server(&self) -> Option<String> {
1546 self.inner.server().map(ToString::to_string)
1547 }
1548
1549 pub fn rooms(&self) -> Vec<Arc<Room>> {
1550 self.inner
1551 .rooms()
1552 .into_iter()
1553 .map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1554 .collect()
1555 }
1556
1557 pub fn get_room(&self, room_id: String) -> Result<Option<Arc<Room>>, ClientError> {
1569 let room_id = RoomId::parse(room_id)?;
1570 let sdk_room = self.inner.get_room(&room_id);
1571
1572 let room =
1573 sdk_room.map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())));
1574 Ok(room)
1575 }
1576
1577 pub fn get_dm_room(&self, user_id: String) -> Result<Option<Arc<Room>>, ClientError> {
1578 let user_id = UserId::parse(user_id)?;
1579 let sdk_room = self.inner.get_dm_room(&user_id);
1580 let dm =
1581 sdk_room.map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())));
1582 Ok(dm)
1583 }
1584
1585 pub async fn search_users(
1586 &self,
1587 search_term: String,
1588 limit: u64,
1589 ) -> Result<SearchUsersResults, ClientError> {
1590 let response = self.inner.search_users(&search_term, limit).await?;
1591 Ok(SearchUsersResults::from(response))
1592 }
1593
1594 pub async fn get_profile(&self, user_id: String) -> Result<UserProfile, ClientError> {
1595 let user_id = <&UserId>::try_from(user_id.as_str())?;
1596 UserProfile::fetch(&self.inner.account(), user_id).await
1597 }
1598
1599 pub async fn notification_client(
1600 self: Arc<Self>,
1601 process_setup: NotificationProcessSetup,
1602 ) -> Result<Arc<NotificationClient>, ClientError> {
1603 Ok(Arc::new(NotificationClient {
1604 inner: MatrixNotificationClient::new((*self.inner).clone(), process_setup.into())
1605 .await?,
1606 client: self.clone(),
1607 }))
1608 }
1609
1610 pub fn sync_service(&self) -> Arc<SyncServiceBuilder> {
1611 SyncServiceBuilder::new((*self.inner).clone(), self.utd_hook_manager.get().cloned())
1612 }
1613
1614 pub fn sync_v2(
1623 &self,
1624 settings: SyncSettingsV2,
1625 listener: Box<dyn SyncListenerV2>,
1626 ) -> Arc<TaskHandle> {
1627 let client = (*self.inner).clone();
1628 let sdk_settings: matrix_sdk::config::SyncSettings = settings.into();
1629 let listener: Arc<dyn SyncListenerV2> = Arc::from(listener);
1630
1631 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
1632 let result = client
1633 .sync_with_result_callback(sdk_settings, |result| {
1634 let listener = listener.clone();
1635 async move {
1636 let response = result?;
1637 let ffi_response: SyncResponseV2 = response.into();
1638 listener.on_update(ffi_response);
1639 Ok(matrix_sdk::LoopCtrl::Continue)
1640 }
1641 })
1642 .await;
1643
1644 if let Err(e) = result {
1645 tracing::error!("Sync loop ended with error: {e}");
1646 }
1647 })))
1648 }
1649
1650 pub async fn sync_once_v2(
1655 &self,
1656 settings: SyncSettingsV2,
1657 ) -> Result<SyncResponseV2, ClientError> {
1658 let sdk_settings: matrix_sdk::config::SyncSettings = settings.into();
1659 let response = self.inner.sync_once(sdk_settings).await?;
1660 Ok(response.into())
1661 }
1662
1663 pub async fn space_service(&self) -> Arc<SpaceService> {
1664 let inner = UISpaceService::new((*self.inner).clone()).await;
1665 Arc::new(SpaceService::new(inner))
1666 }
1667
1668 pub async fn get_notification_settings(&self) -> Arc<NotificationSettings> {
1669 let inner = self.inner.notification_settings().await;
1670
1671 Arc::new(NotificationSettings::new((*self.inner).clone(), inner))
1672 }
1673
1674 pub fn encryption(self: Arc<Self>) -> Arc<Encryption> {
1675 Arc::new(Encryption { inner: self.inner.encryption(), _client: self.clone() })
1676 }
1677
1678 pub async fn ignored_users(&self) -> Result<Vec<String>, ClientError> {
1681 if let Some(raw_content) =
1682 self.inner.account().fetch_account_data_static::<IgnoredUserListEventContent>().await?
1683 {
1684 let content = raw_content.deserialize()?;
1685 let user_ids: Vec<String> =
1686 content.ignored_users.keys().map(|id| id.to_string()).collect();
1687
1688 return Ok(user_ids);
1689 }
1690
1691 Ok(vec![])
1692 }
1693
1694 pub async fn ignore_user(&self, user_id: String) -> Result<(), ClientError> {
1695 let user_id = UserId::parse(user_id)?;
1696 self.inner.account().ignore_user(&user_id).await?;
1697 Ok(())
1698 }
1699
1700 pub async fn unignore_user(&self, user_id: String) -> Result<(), ClientError> {
1701 let user_id = UserId::parse(user_id)?;
1702 self.inner.account().unignore_user(&user_id).await?;
1703 Ok(())
1704 }
1705
1706 pub fn subscribe_to_ignored_users(
1707 &self,
1708 listener: Box<dyn IgnoredUsersListener>,
1709 ) -> Arc<TaskHandle> {
1710 let mut subscriber = self.inner.subscribe_to_ignore_user_list_changes();
1711 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
1712 while let Some(user_ids) = subscriber.next().await {
1713 listener.call(user_ids);
1714 }
1715 })))
1716 }
1717
1718 pub fn room_directory_search(&self) -> Arc<RoomDirectorySearch> {
1719 Arc::new(RoomDirectorySearch::new(
1720 matrix_sdk::room_directory_search::RoomDirectorySearch::new((*self.inner).clone()),
1721 ))
1722 }
1723
1724 pub async fn join_room_by_id(&self, room_id: String) -> Result<Arc<Room>, ClientError> {
1730 let room_id = RoomId::parse(room_id)?;
1731 let room = self.inner.join_room_by_id(room_id.as_ref()).await?;
1732 Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1733 }
1734
1735 pub async fn join_room_by_id_or_alias(
1742 &self,
1743 room_id_or_alias: String,
1744 server_names: Vec<String>,
1745 ) -> Result<Arc<Room>, ClientError> {
1746 let room_id = RoomOrAliasId::parse(&room_id_or_alias)?;
1747 let server_names = server_names
1748 .iter()
1749 .map(|name| OwnedServerName::try_from(name.as_str()))
1750 .collect::<Result<Vec<_>, _>>()?;
1751 let room =
1752 self.inner.join_room_by_id_or_alias(room_id.as_ref(), server_names.as_ref()).await?;
1753 Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1754 }
1755
1756 pub async fn knock(
1758 &self,
1759 room_id_or_alias: String,
1760 reason: Option<String>,
1761 server_names: Vec<String>,
1762 ) -> Result<Arc<Room>, ClientError> {
1763 let room_id = RoomOrAliasId::parse(&room_id_or_alias)?;
1764 let server_names =
1765 server_names.iter().map(ServerName::parse).collect::<Result<Vec<_>, _>>()?;
1766 let room = self.inner.knock(room_id, reason, server_names).await?;
1767 Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1768 }
1769
1770 pub async fn get_recently_visited_rooms(&self) -> Result<Vec<String>, ClientError> {
1771 Ok(self
1772 .inner
1773 .account()
1774 .get_recently_visited_rooms()
1775 .await?
1776 .into_iter()
1777 .map(Into::into)
1778 .collect())
1779 }
1780
1781 pub async fn track_recently_visited_room(&self, room: String) -> Result<(), ClientError> {
1782 let room_id = RoomId::parse(room)?;
1783 self.inner.account().track_recently_visited_room(room_id).await?;
1784 Ok(())
1785 }
1786
1787 pub async fn resolve_room_alias(
1790 &self,
1791 room_alias: String,
1792 ) -> Result<Option<ResolvedRoomAlias>, ClientError> {
1793 let room_alias = RoomAliasId::parse(&room_alias)?;
1794 match self.inner.resolve_room_alias(&room_alias).await {
1795 Ok(response) => Ok(Some(response.into())),
1796 Err(error) => match error.client_api_error_kind() {
1797 Some(ErrorKind::NotFound) => Ok(None),
1799 _ => Err(error.into()),
1801 },
1802 }
1803 }
1804
1805 pub async fn room_alias_exists(&self, room_alias: String) -> Result<bool, ClientError> {
1807 self.resolve_room_alias(room_alias).await.map(|ret| ret.is_some())
1808 }
1809
1810 pub async fn get_room_preview_from_room_id(
1816 &self,
1817 room_id: String,
1818 via_servers: Vec<String>,
1819 ) -> Result<Arc<RoomPreview>, ClientError> {
1820 let room_id = RoomId::parse(&room_id).context("room_id is not a valid room id")?;
1821
1822 let via_servers = via_servers
1823 .into_iter()
1824 .map(ServerName::parse)
1825 .collect::<Result<Vec<_>, _>>()
1826 .context("at least one `via` server name is invalid")?;
1827
1828 let room_id: &RoomId = &room_id;
1831
1832 let room_preview = self.inner.get_room_preview(room_id.into(), via_servers).await?;
1833
1834 Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
1835 }
1836
1837 pub async fn get_room_preview_from_room_alias(
1839 &self,
1840 room_alias: String,
1841 ) -> Result<Arc<RoomPreview>, ClientError> {
1842 let room_alias =
1843 RoomAliasId::parse(&room_alias).context("room_alias is not a valid room alias")?;
1844
1845 let room_alias: &RoomAliasId = &room_alias;
1848
1849 let room_preview = self.inner.get_room_preview(room_alias.into(), Vec::new()).await?;
1850
1851 Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
1852 }
1853
1854 pub async fn await_room_remote_echo(&self, room_id: String) -> Result<Arc<Room>, ClientError> {
1860 let room_id = RoomId::parse(room_id)?;
1861 Ok(Arc::new(Room::new(
1862 self.inner.await_room_remote_echo(&room_id).await,
1863 self.utd_hook_manager.get().cloned(),
1864 )))
1865 }
1866
1867 pub fn can_deactivate_account(&self) -> bool {
1870 matches!(self.inner.auth_api(), Some(AuthApi::Matrix(_)))
1871 }
1872
1873 pub async fn deactivate_account(
1884 &self,
1885 auth_data: Option<AuthData>,
1886 erase_data: bool,
1887 ) -> Result<(), ClientError> {
1888 if let Some(auth_data) = auth_data {
1889 _ = self.inner.account().deactivate(None, Some(auth_data.into()), erase_data).await?;
1890 } else {
1891 _ = self.inner.account().deactivate(None, None, erase_data).await?;
1892 }
1893
1894 Ok(())
1895 }
1896
1897 pub async fn is_room_alias_available(&self, alias: String) -> Result<bool, ClientError> {
1905 let alias = RoomAliasId::parse(alias)?;
1906 self.inner.is_room_alias_available(&alias).await.map_err(Into::into)
1907 }
1908
1909 pub async fn set_media_retention_policy(
1911 &self,
1912 policy: MediaRetentionPolicy,
1913 ) -> Result<(), ClientError> {
1914 let closure = async || -> Result<_, Error> {
1915 let store = self.inner.media_store().lock().await?;
1916 Ok(store.set_media_retention_policy(policy).await?)
1917 };
1918
1919 Ok(closure().await?)
1920 }
1921
1922 pub async fn clear_caches(
1945 &self,
1946 sync_service: Option<Arc<SyncService>>,
1947 ) -> Result<(), ClientError> {
1948 let closure = async || -> Result<_, ClientError> {
1949 if let Some(sync_service) = sync_service {
1951 sync_service.inner.expire_sessions().await;
1952 }
1953
1954 self.inner.send_queue().set_enabled(false).await;
1960
1961 self.inner
1963 .media_store()
1964 .lock()
1965 .await
1966 .map_err(Error::from)?
1967 .clean()
1968 .await
1969 .map_err(Error::from)?;
1970
1971 self.inner.event_cache().clear_all_rooms().await?;
1976
1977 #[cfg(feature = "sqlite")]
1979 if let Some(store_path) = &self.store_path {
1980 debug!("Removing the state store: {}", store_path.display());
1981
1982 for file_name in [
1989 PathBuf::from(STATE_STORE_DATABASE_NAME),
1990 PathBuf::from(format!("{STATE_STORE_DATABASE_NAME}.wal")),
1991 PathBuf::from(format!("{STATE_STORE_DATABASE_NAME}.shm")),
1992 ] {
1993 let file_path = store_path.join(file_name);
1994 if file_path.exists() {
1995 debug!("Removing file: {}", file_path.display());
1996 std::fs::remove_file(&file_path).map_err(|err| ClientError::Generic {
1997 msg: format!(
1998 "couldn't delete the state store file {}: {err}",
1999 file_path.display()
2000 ),
2001 details: None,
2002 })?;
2003 }
2004 }
2005 }
2006
2007 Ok(())
2008 };
2009
2010 closure().await
2011 }
2012
2013 pub async fn is_report_room_api_supported(&self) -> Result<bool, ClientError> {
2015 Ok(self.inner.server_versions().await?.contains(&ruma::api::MatrixVersion::V1_13))
2016 }
2017
2018 pub async fn is_livekit_rtc_supported(&self) -> Result<bool, ClientError> {
2020 Ok(self
2021 .inner
2022 .rtc_foci()
2023 .await?
2024 .iter()
2025 .any(|focus| matches!(focus, RtcFocusInfo::LiveKit(_))))
2026 }
2027
2028 pub async fn is_login_with_qr_code_supported(&self) -> Result<bool, ClientError> {
2030 Ok(matches!(self.inner.auth_api(), Some(AuthApi::OAuth(_)))
2031 && self.inner.unstable_features().await?.contains(&ruma::api::FeatureFlag::Msc4108))
2032 }
2033
2034 pub async fn server_vendor_info(&self) -> Result<matrix_sdk::ServerVendorInfo, ClientError> {
2039 Ok(self.inner.server_vendor_info(None).await?)
2040 }
2041
2042 pub async fn subscribe_to_media_preview_config(
2044 &self,
2045 listener: Box<dyn MediaPreviewConfigListener>,
2046 ) -> Result<Arc<TaskHandle>, ClientError> {
2047 let (initial_value, stream) = self.inner.account().observe_media_preview_config().await?;
2048 Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
2049 listener.on_change(initial_value.map(|config| config.into()));
2051 pin_mut!(stream);
2053 while let Some(media_preview_config) = stream.next().await {
2054 listener.on_change(Some(media_preview_config.into()));
2055 }
2056 }))))
2057 }
2058
2059 pub async fn set_media_preview_display_policy(
2061 &self,
2062 policy: MediaPreviews,
2063 ) -> Result<(), ClientError> {
2064 self.inner.account().set_media_previews_display_policy(policy.into()).await?;
2065 Ok(())
2066 }
2067
2068 pub async fn get_media_preview_display_policy(
2071 &self,
2072 ) -> Result<Option<MediaPreviews>, ClientError> {
2073 let configuration = self.inner.account().get_media_preview_config_event_content().await?;
2074 match configuration {
2075 Some(configuration) => Ok(configuration.media_previews.map(Into::into)),
2076 None => Ok(None),
2077 }
2078 }
2079
2080 pub async fn set_invite_avatars_display_policy(
2082 &self,
2083 policy: InviteAvatars,
2084 ) -> Result<(), ClientError> {
2085 self.inner.account().set_invite_avatars_display_policy(policy.into()).await?;
2086 Ok(())
2087 }
2088
2089 pub async fn get_invite_avatars_display_policy(
2092 &self,
2093 ) -> Result<Option<InviteAvatars>, ClientError> {
2094 let configuration = self.inner.account().get_media_preview_config_event_content().await?;
2095 match configuration {
2096 Some(configuration) => Ok(configuration.invite_avatars.map(Into::into)),
2097 None => Ok(None),
2098 }
2099 }
2100
2101 pub async fn fetch_media_preview_config(
2103 &self,
2104 ) -> Result<Option<MediaPreviewConfig>, ClientError> {
2105 Ok(self.inner.account().fetch_media_preview_config_event_content().await?.map(Into::into))
2106 }
2107
2108 pub async fn get_max_media_upload_size(&self) -> Result<u64, ClientError> {
2111 let max_upload_size = self.inner.load_or_fetch_max_upload_size().await?;
2112 Ok(max_upload_size.into())
2113 }
2114
2115 pub async fn subscribe_to_room_info(
2126 &self,
2127 room_id: String,
2128 listener: Box<dyn RoomInfoListener>,
2129 ) -> Result<Arc<TaskHandle>, ClientError> {
2130 let room_id = RoomId::parse(room_id)?;
2131
2132 if let Some(room) = self.inner.get_room(&room_id) {
2134 if let Ok(room_info) = RoomInfo::new(&room).await {
2135 listener.call(room_info);
2136 }
2137 }
2138
2139 Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn({
2140 let client = self.inner.clone();
2141 let mut receiver = client.room_info_notable_update_receiver();
2142 async move {
2143 while let Ok(room_update) = receiver.recv().await {
2144 if room_update.room_id != room_id {
2145 continue;
2146 }
2147
2148 if let Some(room) = client.get_room(&room_id) {
2149 if let Ok(room_info) = RoomInfo::new(&room).await {
2150 listener.call(room_info);
2151 }
2152 }
2153 }
2154 }
2155 }))))
2156 }
2157
2158 pub fn enable_automatic_backpagination(&self) {
2164 self.inner.event_cache().config_mut().experimental_auto_backpagination = true;
2165 }
2166
2167 pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
2168 HomeserverCapabilities::new(self.inner.homeserver_capabilities())
2169 }
2170}
2171
2172#[cfg(feature = "experimental-element-recent-emojis")]
2173mod recent_emoji {
2174 use crate::{client::Client, error::ClientError};
2175
2176 #[derive(Debug, uniffi::Record)]
2178 pub struct RecentEmoji {
2179 pub emoji: String,
2181 pub count: u64,
2183 }
2184
2185 #[matrix_sdk_ffi_macros::export]
2186 impl Client {
2187 pub async fn add_recent_emoji(&self, emoji: String) -> Result<(), ClientError> {
2190 Ok(self.inner.account().add_recent_emoji(&emoji).await?)
2191 }
2192
2193 pub async fn get_recent_emojis(&self) -> Result<Vec<RecentEmoji>, ClientError> {
2196 Ok(self
2197 .inner
2198 .account()
2199 .get_recent_emojis(false)
2200 .await?
2201 .into_iter()
2202 .map(|(emoji, count)| RecentEmoji { emoji, count: count.into() })
2203 .collect::<Vec<RecentEmoji>>())
2204 }
2205 }
2206}
2207
2208#[matrix_sdk_ffi_macros::export(callback_interface)]
2209pub trait MediaPreviewConfigListener: SyncOutsideWasm + SendOutsideWasm {
2210 fn on_change(&self, media_preview_config: Option<MediaPreviewConfig>);
2211}
2212
2213#[matrix_sdk_ffi_macros::export(callback_interface)]
2214pub trait IgnoredUsersListener: SyncOutsideWasm + SendOutsideWasm {
2215 fn call(&self, ignored_user_ids: Vec<String>);
2216}
2217
2218#[derive(uniffi::Enum)]
2219pub enum NotificationProcessSetup {
2220 MultipleProcesses,
2221 SingleProcess { sync_service: Arc<SyncService> },
2222}
2223
2224impl From<NotificationProcessSetup> for MatrixNotificationProcessSetup {
2225 fn from(value: NotificationProcessSetup) -> Self {
2226 match value {
2227 NotificationProcessSetup::MultipleProcesses => {
2228 MatrixNotificationProcessSetup::MultipleProcesses
2229 }
2230 NotificationProcessSetup::SingleProcess { sync_service } => {
2231 MatrixNotificationProcessSetup::SingleProcess {
2232 sync_service: sync_service.inner.clone(),
2233 }
2234 }
2235 }
2236 }
2237}
2238
2239#[derive(uniffi::Record)]
2241pub struct ResolvedRoomAlias {
2242 pub room_id: String,
2244 pub servers: Vec<String>,
2246}
2247
2248impl From<get_alias::v3::Response> for ResolvedRoomAlias {
2249 fn from(value: get_alias::v3::Response) -> Self {
2250 Self {
2251 room_id: value.room_id.to_string(),
2252 servers: value.servers.iter().map(ToString::to_string).collect(),
2253 }
2254 }
2255}
2256
2257#[derive(uniffi::Record)]
2258pub struct SearchUsersResults {
2259 pub results: Vec<UserProfile>,
2260 pub limited: bool,
2261}
2262
2263impl From<search_users::v3::Response> for SearchUsersResults {
2264 fn from(value: search_users::v3::Response) -> Self {
2265 let results: Vec<UserProfile> = value.results.iter().map(UserProfile::from).collect();
2266 SearchUsersResults { results, limited: value.limited }
2267 }
2268}
2269
2270#[derive(uniffi::Record)]
2271pub struct UserProfile {
2272 pub user_id: String,
2273 pub display_name: Option<String>,
2274 pub avatar_url: Option<String>,
2275}
2276
2277impl UserProfile {
2278 pub(crate) async fn fetch(account: &Account, user_id: &UserId) -> Result<Self, ClientError> {
2281 let response = account.fetch_user_profile_of(user_id).await?;
2282 let display_name = response.get_static::<DisplayName>()?;
2283 let avatar_url = response.get_static::<AvatarUrl>()?.map(|url| url.to_string());
2284
2285 Ok(UserProfile { user_id: user_id.to_string(), display_name, avatar_url })
2286 }
2287}
2288
2289impl From<&search_users::v3::User> for UserProfile {
2290 fn from(value: &search_users::v3::User) -> Self {
2291 UserProfile {
2292 user_id: value.user_id.to_string(),
2293 display_name: value.display_name.clone(),
2294 avatar_url: value.avatar_url.as_ref().map(|url| url.to_string()),
2295 }
2296 }
2297}
2298
2299impl Client {
2300 fn process_session_change(&self, session_change: SessionChange) {
2301 if let Some(delegate_data) = self.delegate_data.get() {
2302 debug!("Applying session change: {session_change:?}");
2303 let delegate = delegate_data.delegate.clone();
2304 get_runtime_handle().spawn_blocking(move || match session_change {
2305 SessionChange::UnknownToken(unknown_token) => {
2306 delegate.did_receive_auth_error(unknown_token.soft_logout);
2307 }
2308 SessionChange::TokensRefreshed => {}
2309 });
2310 } else {
2311 debug!(
2312 "No client delegate found, session change couldn't be applied: {session_change:?}"
2313 );
2314 }
2315 }
2316
2317 fn retrieve_session(
2318 session_delegate: Arc<dyn ClientSessionDelegate>,
2319 user_id: &UserId,
2320 ) -> anyhow::Result<SessionTokens> {
2321 Ok(session_delegate.retrieve_session_from_keychain(user_id.to_string())?.into_tokens())
2322 }
2323
2324 fn session_inner(client: matrix_sdk::Client) -> Result<Session, ClientError> {
2325 let auth_api = client.auth_api().context("Missing authentication API")?;
2326
2327 let homeserver_url = client.homeserver().into();
2328 let sliding_sync_version = client.sliding_sync_version();
2329
2330 Session::new(auth_api, homeserver_url, sliding_sync_version.into())
2331 }
2332
2333 fn save_session(
2334 session_delegate: Arc<dyn ClientSessionDelegate>,
2335 client: matrix_sdk::Client,
2336 ) -> anyhow::Result<()> {
2337 let session = Self::session_inner(client)?;
2338 session_delegate.save_session_in_keychain(session);
2339 Ok(())
2340 }
2341}
2342
2343#[derive(uniffi::Enum)]
2349pub enum RoomLoadSettings {
2350 All,
2353
2354 One { room_id: String },
2360}
2361
2362impl TryInto<SdkRoomLoadSettings> for RoomLoadSettings {
2363 type Error = String;
2364
2365 fn try_into(self) -> Result<SdkRoomLoadSettings, Self::Error> {
2366 Ok(match self {
2367 Self::All => SdkRoomLoadSettings::All,
2368 Self::One { room_id } => {
2369 SdkRoomLoadSettings::One(RoomId::parse(room_id).map_err(|error| error.to_string())?)
2370 }
2371 })
2372 }
2373}
2374
2375#[derive(uniffi::Record)]
2376pub struct NotificationPowerLevels {
2377 pub room: i32,
2378}
2379
2380impl From<NotificationPowerLevels> for ruma::power_levels::NotificationPowerLevels {
2381 fn from(value: NotificationPowerLevels) -> Self {
2382 let mut notification_power_levels = Self::new();
2383 notification_power_levels.room = value.room.into();
2384 notification_power_levels
2385 }
2386}
2387
2388#[derive(uniffi::Record)]
2389pub struct PowerLevels {
2390 pub users_default: Option<i32>,
2391 pub events_default: Option<i32>,
2392 pub state_default: Option<i32>,
2393 pub ban: Option<i32>,
2394 pub kick: Option<i32>,
2395 pub redact: Option<i32>,
2396 pub invite: Option<i32>,
2397 pub notifications: Option<NotificationPowerLevels>,
2398 pub users: HashMap<String, i32>,
2399 pub events: HashMap<String, i32>,
2400}
2401
2402impl From<PowerLevels> for RoomPowerLevelsContentOverride {
2403 fn from(value: PowerLevels) -> Self {
2404 let mut power_levels = RoomPowerLevelsContentOverride::default();
2405 power_levels.users_default = value.users_default.map(Into::into);
2406 power_levels.state_default = value.state_default.map(Into::into);
2407 power_levels.events_default = value.events_default.map(Into::into);
2408 power_levels.ban = value.ban.map(Into::into);
2409 power_levels.kick = value.kick.map(Into::into);
2410 power_levels.redact = value.redact.map(Into::into);
2411 power_levels.invite = value.invite.map(Into::into);
2412 power_levels.notifications = value.notifications.map(Into::into).unwrap_or_default();
2413 power_levels.users = value
2414 .users
2415 .iter()
2416 .filter_map(|(user_id, power_level)| match UserId::parse(user_id) {
2417 Ok(id) => Some((id, (*power_level).into())),
2418 Err(e) => {
2419 error!(user_id, "Skipping invalid user ID, error: {e}");
2420 None
2421 }
2422 })
2423 .collect();
2424 power_levels.events = value
2425 .events
2426 .iter()
2427 .map(|(event_type, power_level)| {
2428 let event_type: ruma::events::TimelineEventType = event_type.as_str().into();
2429 (event_type, (*power_level).into())
2430 })
2431 .collect();
2432 power_levels
2433 }
2434}
2435
2436#[derive(uniffi::Record)]
2437pub struct CreateRoomParameters {
2438 pub name: Option<String>,
2439 #[uniffi(default = None)]
2440 pub topic: Option<String>,
2441 pub is_encrypted: bool,
2442 #[uniffi(default = false)]
2443 pub is_direct: bool,
2444 pub visibility: RoomVisibility,
2445 pub preset: RoomPreset,
2446 #[uniffi(default = None)]
2447 pub invite: Option<Vec<String>>,
2448 #[uniffi(default = None)]
2449 pub avatar: Option<String>,
2450 #[uniffi(default = None)]
2451 pub power_level_content_override: Option<PowerLevels>,
2452 #[uniffi(default = None)]
2453 pub join_rule_override: Option<JoinRule>,
2454 #[uniffi(default = None)]
2455 pub history_visibility_override: Option<RoomHistoryVisibility>,
2456 #[uniffi(default = None)]
2457 pub canonical_alias: Option<String>,
2458 #[uniffi(default = false)]
2459 pub is_space: bool,
2460}
2461
2462impl TryFrom<CreateRoomParameters> for create_room::v3::Request {
2463 type Error = ClientError;
2464
2465 fn try_from(value: CreateRoomParameters) -> Result<create_room::v3::Request, Self::Error> {
2466 let mut request = create_room::v3::Request::new();
2467 request.name = value.name;
2468 request.topic = value.topic;
2469 request.is_direct = value.is_direct;
2470 request.visibility = value.visibility.into();
2471 request.preset = Some(value.preset.into());
2472 request.room_alias_name = value.canonical_alias;
2473 request.invite = match value.invite {
2474 Some(invite) => invite
2475 .iter()
2476 .filter_map(|user_id| match UserId::parse(user_id) {
2477 Ok(id) => Some(id),
2478 Err(e) => {
2479 error!(user_id, "Skipping invalid user ID, error: {e}");
2480 None
2481 }
2482 })
2483 .collect(),
2484 None => vec![],
2485 };
2486
2487 let mut initial_state: Vec<Raw<AnyInitialStateEvent>> = vec![];
2488
2489 if value.is_encrypted {
2490 let content =
2491 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
2492 initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2493 }
2494
2495 if let Some(url) = value.avatar {
2496 let mut content = RoomAvatarEventContent::new();
2497 content.url = Some(url.into());
2498 initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2499 }
2500
2501 if let Some(join_rule_override) = value.join_rule_override {
2502 let content = RoomJoinRulesEventContent::new(join_rule_override.try_into()?);
2503 initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2504 }
2505
2506 if let Some(history_visibility_override) = value.history_visibility_override {
2507 let content =
2508 RoomHistoryVisibilityEventContent::new(history_visibility_override.try_into()?);
2509 initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2510 }
2511
2512 request.initial_state = initial_state;
2513
2514 if value.is_space {
2515 let mut creation_content = CreationContent::new();
2516 creation_content.room_type = Some(RoomType::Space);
2517 request.creation_content = Some(Raw::new(&creation_content)?);
2518 }
2519
2520 if let Some(power_levels) = value.power_level_content_override {
2521 match Raw::<RoomPowerLevelsContentOverride>::new(&power_levels.into()) {
2522 Ok(power_levels) => {
2523 request.power_level_content_override = Some(power_levels);
2524 }
2525 Err(e) => {
2526 return Err(ClientError::Generic {
2527 msg: format!("Failed to serialize power levels, error: {e}"),
2528 details: Some(format!("{e:?}")),
2529 });
2530 }
2531 }
2532 }
2533
2534 Ok(request)
2535 }
2536}
2537
2538#[derive(uniffi::Enum)]
2539pub enum RoomVisibility {
2540 Public,
2542
2543 Private,
2545
2546 Custom { value: String },
2548}
2549
2550impl From<RoomVisibility> for Visibility {
2551 fn from(value: RoomVisibility) -> Self {
2552 match value {
2553 RoomVisibility::Public => Self::Public,
2554 RoomVisibility::Private => Self::Private,
2555 RoomVisibility::Custom { value } => value.as_str().into(),
2556 }
2557 }
2558}
2559
2560impl From<Visibility> for RoomVisibility {
2561 fn from(value: Visibility) -> Self {
2562 match value {
2563 Visibility::Public => Self::Public,
2564 Visibility::Private => Self::Private,
2565 _ => Self::Custom { value: value.as_str().to_owned() },
2566 }
2567 }
2568}
2569
2570#[derive(uniffi::Enum)]
2571#[allow(clippy::enum_variant_names)]
2572pub enum RoomPreset {
2573 PrivateChat,
2576
2577 PublicChat,
2580
2581 TrustedPrivateChat,
2584}
2585
2586impl From<RoomPreset> for create_room::v3::RoomPreset {
2587 fn from(value: RoomPreset) -> Self {
2588 match value {
2589 RoomPreset::PrivateChat => Self::PrivateChat,
2590 RoomPreset::PublicChat => Self::PublicChat,
2591 RoomPreset::TrustedPrivateChat => Self::TrustedPrivateChat,
2592 }
2593 }
2594}
2595
2596#[derive(uniffi::Record)]
2597pub struct Session {
2598 pub access_token: String,
2601 pub refresh_token: Option<String>,
2605 pub user_id: String,
2607 pub device_id: String,
2609
2610 pub homeserver_url: String,
2613 pub oidc_data: Option<String>,
2616 pub sliding_sync_version: SlidingSyncVersion,
2618}
2619
2620impl Session {
2621 fn new(
2622 auth_api: AuthApi,
2623 homeserver_url: String,
2624 sliding_sync_version: SlidingSyncVersion,
2625 ) -> Result<Session, ClientError> {
2626 match auth_api {
2627 AuthApi::Matrix(a) => {
2629 let matrix_sdk::authentication::matrix::MatrixSession {
2630 meta: matrix_sdk::SessionMeta { user_id, device_id },
2631 tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2632 } = a.session().context("Missing session")?;
2633
2634 Ok(Session {
2635 access_token,
2636 refresh_token,
2637 user_id: user_id.to_string(),
2638 device_id: device_id.to_string(),
2639 homeserver_url,
2640 oidc_data: None,
2641 sliding_sync_version,
2642 })
2643 }
2644 AuthApi::OAuth(api) => {
2646 let matrix_sdk::authentication::oauth::UserSession {
2647 meta: matrix_sdk::SessionMeta { user_id, device_id },
2648 tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2649 } = api.user_session().context("Missing session")?;
2650 let client_id = api.client_id().context("OIDC client ID is missing.")?.clone();
2651 let oidc_data = OidcSessionData { client_id };
2652
2653 let oidc_data = serde_json::to_string(&oidc_data).ok();
2654 Ok(Session {
2655 access_token,
2656 refresh_token,
2657 user_id: user_id.to_string(),
2658 device_id: device_id.to_string(),
2659 homeserver_url,
2660 oidc_data,
2661 sliding_sync_version,
2662 })
2663 }
2664 _ => Err(anyhow!("Unknown authentication API").into()),
2665 }
2666 }
2667
2668 fn into_tokens(self) -> matrix_sdk::SessionTokens {
2669 SessionTokens { access_token: self.access_token, refresh_token: self.refresh_token }
2670 }
2671}
2672
2673impl TryFrom<Session> for AuthSession {
2674 type Error = ClientError;
2675 fn try_from(value: Session) -> Result<Self, Self::Error> {
2676 let Session {
2677 access_token,
2678 refresh_token,
2679 user_id,
2680 device_id,
2681 homeserver_url: _,
2682 oidc_data,
2683 sliding_sync_version: _,
2684 } = value;
2685
2686 if let Some(oidc_data) = oidc_data {
2687 let oidc_data = serde_json::from_str::<OidcSessionData>(&oidc_data)?;
2689
2690 let user_session = matrix_sdk::authentication::oauth::UserSession {
2691 meta: matrix_sdk::SessionMeta {
2692 user_id: user_id.try_into()?,
2693 device_id: device_id.into(),
2694 },
2695 tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2696 };
2697
2698 let session = OAuthSession { client_id: oidc_data.client_id, user: user_session };
2699
2700 Ok(AuthSession::OAuth(session.into()))
2701 } else {
2702 let session = matrix_sdk::authentication::matrix::MatrixSession {
2704 meta: matrix_sdk::SessionMeta {
2705 user_id: user_id.try_into()?,
2706 device_id: device_id.into(),
2707 },
2708 tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2709 };
2710
2711 Ok(AuthSession::Matrix(session))
2712 }
2713 }
2714}
2715
2716#[derive(Serialize, Deserialize)]
2719pub(crate) struct OidcSessionData {
2720 client_id: ClientId,
2721}
2722
2723#[derive(uniffi::Enum)]
2724pub enum AccountManagementAction {
2725 Profile,
2726 DevicesList,
2727 DeviceView { device_id: String },
2728 DeviceDelete { device_id: String },
2729 AccountDeactivate,
2730 CrossSigningReset,
2731}
2732
2733impl<'a> From<&'a AccountManagementAction> for AccountManagementActionData<'a> {
2734 fn from(value: &'a AccountManagementAction) -> Self {
2735 match value {
2736 AccountManagementAction::Profile => Self::Profile,
2737 AccountManagementAction::DevicesList => Self::DevicesList,
2738 AccountManagementAction::DeviceView { device_id } => {
2739 Self::DeviceView(DeviceViewData::new(device_id.as_str().into()))
2740 }
2741 AccountManagementAction::DeviceDelete { device_id } => {
2742 Self::DeviceDelete(DeviceDeleteData::new(device_id.as_str().into()))
2743 }
2744 AccountManagementAction::AccountDeactivate => Self::AccountDeactivate,
2745 AccountManagementAction::CrossSigningReset => Self::CrossSigningReset,
2746 }
2747 }
2748}
2749
2750#[matrix_sdk_ffi_macros::export]
2751fn gen_transaction_id() -> String {
2752 TransactionId::new().to_string()
2753}
2754
2755#[derive(uniffi::Object)]
2758pub struct MediaFileHandle {
2759 #[cfg(not(target_family = "wasm"))]
2760 inner: std::sync::RwLock<Option<SdkMediaFileHandle>>,
2761}
2762
2763impl MediaFileHandle {
2764 #[cfg(not(target_family = "wasm"))]
2765 fn new(handle: SdkMediaFileHandle) -> Self {
2766 Self { inner: std::sync::RwLock::new(Some(handle)) }
2767 }
2768}
2769
2770#[matrix_sdk_ffi_macros::export]
2771impl MediaFileHandle {
2772 pub fn path(&self) -> Result<String, ClientError> {
2774 #[cfg(not(target_family = "wasm"))]
2775 return Ok(self
2776 .inner
2777 .read()
2778 .unwrap()
2779 .as_ref()
2780 .context("MediaFileHandle must not be used after calling persist")?
2781 .path()
2782 .to_str()
2783 .unwrap()
2784 .to_owned());
2785 #[cfg(target_family = "wasm")]
2786 Err(ClientError::Generic {
2787 msg: "MediaFileHandle.path() is not supported on WASM targets".to_string(),
2788 details: None,
2789 })
2790 }
2791
2792 pub fn persist(&self, path: String) -> Result<bool, ClientError> {
2793 #[cfg(not(target_family = "wasm"))]
2794 {
2795 let mut guard = self.inner.write().unwrap();
2796 Ok(
2797 match guard
2798 .take()
2799 .context("MediaFileHandle was already persisted")?
2800 .persist(path.as_ref())
2801 {
2802 Ok(_) => true,
2803 Err(e) => {
2804 *guard = Some(e.file);
2805 false
2806 }
2807 },
2808 )
2809 }
2810 #[cfg(target_family = "wasm")]
2811 Err(ClientError::Generic {
2812 msg: "MediaFileHandle.persist() is not supported on WASM targets".to_string(),
2813 details: None,
2814 })
2815 }
2816}
2817
2818#[derive(Clone, uniffi::Enum)]
2819pub enum SlidingSyncVersion {
2820 None,
2821 Native,
2822}
2823
2824impl From<SdkSlidingSyncVersion> for SlidingSyncVersion {
2825 fn from(value: SdkSlidingSyncVersion) -> Self {
2826 match value {
2827 SdkSlidingSyncVersion::None => Self::None,
2828 SdkSlidingSyncVersion::Native => Self::Native,
2829 }
2830 }
2831}
2832
2833impl TryFrom<SlidingSyncVersion> for SdkSlidingSyncVersion {
2834 type Error = ClientError;
2835
2836 fn try_from(value: SlidingSyncVersion) -> Result<Self, Self::Error> {
2837 Ok(match value {
2838 SlidingSyncVersion::None => Self::None,
2839 SlidingSyncVersion::Native => Self::Native,
2840 })
2841 }
2842}
2843
2844#[derive(Clone, uniffi::Enum)]
2845pub enum OidcPrompt {
2846 Create,
2851
2852 Login,
2855
2856 Consent,
2859
2860 Unknown { value: String },
2862}
2863
2864impl From<RumaOidcPrompt> for OidcPrompt {
2865 fn from(value: RumaOidcPrompt) -> Self {
2866 match value {
2867 RumaOidcPrompt::Create => Self::Create,
2868 value => match value.as_str() {
2869 "consent" => Self::Consent,
2870 "login" => Self::Login,
2871 _ => Self::Unknown { value: value.to_string() },
2872 },
2873 }
2874 }
2875}
2876
2877impl From<OidcPrompt> for RumaOidcPrompt {
2878 fn from(value: OidcPrompt) -> Self {
2879 match value {
2880 OidcPrompt::Create => Self::Create,
2881 OidcPrompt::Consent => Self::from("consent"),
2882 OidcPrompt::Login => Self::from("login"),
2883 OidcPrompt::Unknown { value } => value.into(),
2884 }
2885 }
2886}
2887
2888#[derive(Debug, Clone, uniffi::Enum)]
2890pub enum JoinRule {
2891 Public,
2893
2894 Invite,
2897
2898 Knock,
2903
2904 Private,
2906
2907 Restricted { rules: Vec<AllowRule> },
2910
2911 KnockRestricted { rules: Vec<AllowRule> },
2915
2916 Custom {
2918 repr: String,
2920 },
2921}
2922
2923#[derive(Debug, Clone, uniffi::Enum)]
2925pub enum AllowRule {
2926 RoomMembership { room_id: String },
2929
2930 Custom { json: String },
2933}
2934
2935impl TryFrom<JoinRule> for RumaJoinRule {
2936 type Error = ClientError;
2937
2938 fn try_from(value: JoinRule) -> Result<Self, Self::Error> {
2939 match value {
2940 JoinRule::Public => Ok(Self::Public),
2941 JoinRule::Invite => Ok(Self::Invite),
2942 JoinRule::Knock => Ok(Self::Knock),
2943 JoinRule::Private => Ok(Self::Private),
2944 JoinRule::Restricted { rules } => {
2945 let rules = ruma_allow_rules_from_ffi(rules)?;
2946 Ok(Self::Restricted(ruma::events::room::join_rules::Restricted::new(rules)))
2947 }
2948 JoinRule::KnockRestricted { rules } => {
2949 let rules = ruma_allow_rules_from_ffi(rules)?;
2950 Ok(Self::KnockRestricted(ruma::events::room::join_rules::Restricted::new(rules)))
2951 }
2952 JoinRule::Custom { repr } => Ok(serde_json::from_str(&repr)?),
2953 }
2954 }
2955}
2956
2957fn ruma_allow_rules_from_ffi(value: Vec<AllowRule>) -> Result<Vec<RumaAllowRule>, ClientError> {
2958 let mut ret = Vec::with_capacity(value.len());
2959 for rule in value {
2960 let rule: Result<RumaAllowRule, ClientError> = rule.try_into();
2961 match rule {
2962 Ok(rule) => ret.push(rule),
2963 Err(error) => return Err(error),
2964 }
2965 }
2966 Ok(ret)
2967}
2968
2969impl TryFrom<AllowRule> for RumaAllowRule {
2970 type Error = ClientError;
2971
2972 fn try_from(value: AllowRule) -> Result<Self, Self::Error> {
2973 match value {
2974 AllowRule::RoomMembership { room_id } => {
2975 let room_id = RoomId::parse(room_id)?;
2976 Ok(Self::RoomMembership(ruma::room::RoomMembership::new(room_id)))
2977 }
2978 AllowRule::Custom { json } => Ok(Self::_Custom(Box::new(serde_json::from_str(&json)?))),
2979 }
2980 }
2981}
2982
2983impl TryFrom<RumaJoinRule> for JoinRule {
2984 type Error = String;
2985 fn try_from(value: RumaJoinRule) -> Result<Self, Self::Error> {
2986 match value {
2987 RumaJoinRule::Knock => Ok(JoinRule::Knock),
2988 RumaJoinRule::Public => Ok(JoinRule::Public),
2989 RumaJoinRule::Private => Ok(JoinRule::Private),
2990 RumaJoinRule::KnockRestricted(restricted) => {
2991 let rules = restricted.allow.into_iter().map(TryInto::try_into).collect::<Result<
2992 Vec<_>,
2993 Self::Error,
2994 >>(
2995 )?;
2996 Ok(JoinRule::KnockRestricted { rules })
2997 }
2998 RumaJoinRule::Restricted(restricted) => {
2999 let rules = restricted.allow.into_iter().map(TryInto::try_into).collect::<Result<
3000 Vec<_>,
3001 Self::Error,
3002 >>(
3003 )?;
3004 Ok(JoinRule::Restricted { rules })
3005 }
3006 RumaJoinRule::Invite => Ok(JoinRule::Invite),
3007 RumaJoinRule::_Custom(_) => Ok(JoinRule::Custom { repr: value.as_str().to_owned() }),
3008 _ => Err(format!("Unknown JoinRule: {value:?}")),
3009 }
3010 }
3011}
3012
3013impl TryFrom<RumaAllowRule> for AllowRule {
3014 type Error = String;
3015 fn try_from(value: RumaAllowRule) -> Result<Self, Self::Error> {
3016 match value {
3017 RumaAllowRule::RoomMembership(membership) => {
3018 Ok(AllowRule::RoomMembership { room_id: membership.room_id.to_string() })
3019 }
3020 RumaAllowRule::_Custom(repr) => {
3021 let json = serde_json::to_string(&repr)
3022 .map_err(|e| format!("Couldn't serialize custom AllowRule: {e:?}"))?;
3023 Ok(Self::Custom { json })
3024 }
3025 _ => Err(format!("Invalid AllowRule: {value:?}")),
3026 }
3027 }
3028}
3029
3030#[derive(Debug, Clone, uniffi::Record)]
3033pub struct StoreSizes {
3034 crypto_store: Option<u64>,
3036 state_store: Option<u64>,
3038 event_cache_store: Option<u64>,
3040 media_store: Option<u64>,
3042}
3043
3044impl From<matrix_sdk::StoreSizes> for StoreSizes {
3045 fn from(value: matrix_sdk::StoreSizes) -> Self {
3046 Self {
3047 crypto_store: value.crypto_store.map(|v| v as u64),
3048 state_store: value.state_store.map(|v| v as u64),
3049 event_cache_store: value.event_cache_store.map(|v| v as u64),
3050 media_store: value.media_store.map(|v| v as u64),
3051 }
3052 }
3053}
3054
3055#[derive(uniffi::Object)]
3056pub struct HomeserverCapabilities {
3057 inner: matrix_sdk::HomeserverCapabilities,
3058}
3059
3060impl HomeserverCapabilities {
3061 pub(crate) fn new(capabilities: matrix_sdk::HomeserverCapabilities) -> Self {
3062 Self { inner: capabilities }
3063 }
3064}
3065
3066#[matrix_sdk_ffi_macros::export]
3067impl HomeserverCapabilities {
3068 pub async fn refresh(&self) -> Result<(), ClientError> {
3069 Ok(self.inner.refresh().await?)
3070 }
3071
3072 pub async fn can_change_password(&self) -> Result<bool, ClientError> {
3073 Ok(self.inner.can_change_password().await?)
3074 }
3075
3076 pub async fn can_change_displayname(&self) -> Result<bool, ClientError> {
3077 Ok(self.inner.can_change_displayname().await?)
3078 }
3079
3080 pub async fn can_change_avatar(&self) -> Result<bool, ClientError> {
3081 Ok(self.inner.can_change_avatar().await?)
3082 }
3083
3084 pub async fn can_change_thirdparty_ids(&self) -> Result<bool, ClientError> {
3085 Ok(self.inner.can_change_thirdparty_ids().await?)
3086 }
3087
3088 pub async fn can_get_login_token(&self) -> Result<bool, ClientError> {
3089 Ok(self.inner.can_get_login_token().await?)
3090 }
3091
3092 pub async fn extended_profile_fields(&self) -> Result<ExtendedProfileFields, ClientError> {
3093 let profile_fields = self.inner.extended_profile_fields().await?;
3094 Ok(ExtendedProfileFields {
3095 enabled: profile_fields.enabled,
3096 allowed: profile_fields
3097 .allowed
3098 .unwrap_or_default()
3099 .iter()
3100 .map(ToString::to_string)
3101 .collect(),
3102 disallowed: profile_fields
3103 .disallowed
3104 .unwrap_or_default()
3105 .iter()
3106 .map(ToString::to_string)
3107 .collect(),
3108 })
3109 }
3110
3111 pub async fn forgets_room_when_leaving(&self) -> Result<bool, ClientError> {
3112 Ok(self.inner.forgets_room_when_leaving().await?)
3113 }
3114}
3115
3116#[derive(uniffi::Record)]
3117pub struct ExtendedProfileFields {
3118 pub enabled: bool,
3119 pub allowed: Vec<String>,
3120 pub disallowed: Vec<String>,
3121}
3122
3123#[cfg(test)]
3124mod tests {
3125 use ruma::{
3126 api::client::room::{create_room, Visibility},
3127 events::StateEventType,
3128 room::RoomType,
3129 };
3130
3131 use crate::{
3132 client::{CreateRoomParameters, JoinRule, RoomPreset, RoomVisibility},
3133 room::RoomHistoryVisibility,
3134 };
3135
3136 #[test]
3137 fn test_create_room_parameters_mapping() {
3138 let params = CreateRoomParameters {
3139 name: Some("A room".to_owned()),
3140 topic: Some("A topic".to_owned()),
3141 is_encrypted: true,
3142 is_direct: true,
3143 visibility: RoomVisibility::Public,
3144 preset: RoomPreset::PublicChat,
3145 invite: Some(vec!["@user:example.com".to_owned()]),
3146 avatar: Some("http://example.com/avatar.jpg".to_owned()),
3147 power_level_content_override: None,
3148 join_rule_override: Some(JoinRule::Knock),
3149 history_visibility_override: Some(RoomHistoryVisibility::Shared),
3150 canonical_alias: Some("#a-room:example.com".to_owned()),
3151 is_space: true,
3152 };
3153
3154 let request: create_room::v3::Request =
3155 params.try_into().expect("CreateRoomParameters couldn't be transformed into a Request");
3156 let initial_state = request
3157 .initial_state
3158 .iter()
3159 .map(|raw| raw.deserialize().expect("Initial state event failed to deserialize"))
3160 .collect::<Vec<_>>();
3161
3162 assert_eq!(request.name, Some("A room".to_owned()));
3163 assert_eq!(request.topic, Some("A topic".to_owned()));
3164 assert!(initial_state.iter().any(|e| e.event_type() == StateEventType::RoomEncryption));
3165 assert!(request.is_direct);
3166 assert_eq!(request.visibility, Visibility::Public);
3167 assert_eq!(request.preset, Some(create_room::v3::RoomPreset::PublicChat));
3168 assert_eq!(request.invite.len(), 1);
3169 assert!(initial_state.iter().any(|e| e.event_type() == StateEventType::RoomAvatar));
3170 assert!(initial_state.iter().any(|e| e.event_type() == StateEventType::RoomJoinRules));
3171 assert!(initial_state
3172 .iter()
3173 .any(|e| e.event_type() == StateEventType::RoomHistoryVisibility));
3174 assert_eq!(request.room_alias_name, Some("#a-room:example.com".to_owned()));
3175
3176 let room_type = request
3177 .creation_content
3178 .expect("Creation content is missing")
3179 .deserialize()
3180 .expect("Creation content can't be deserialized")
3181 .room_type;
3182 assert_eq!(room_type, Some(RoomType::Space));
3183 }
3184}