1use std::{
2 collections::HashMap,
3 fmt::Debug,
4 path::PathBuf,
5 sync::{Arc, OnceLock},
6 time::Duration,
7};
8
9use anyhow::{anyhow, Context as _};
10use futures_util::pin_mut;
11#[cfg(not(target_family = "wasm"))]
12use matrix_sdk::media::MediaFileHandle as SdkMediaFileHandle;
13#[cfg(feature = "sqlite")]
14use matrix_sdk::STATE_STORE_DATABASE_NAME;
15use matrix_sdk::{
16 authentication::oauth::{
17 AccountManagementActionFull, ClientId, OAuthAuthorizationData, OAuthSession,
18 },
19 deserialized_responses::RawAnySyncOrStrippedTimelineEvent,
20 media::{MediaFormat, MediaRequestParameters, MediaRetentionPolicy, MediaThumbnailSettings},
21 ruma::{
22 api::client::{
23 discovery::{
24 discover_homeserver::RtcFocusInfo,
25 get_authorization_server_metadata::v1::Prompt as RumaOidcPrompt,
26 },
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 store::RoomLoadSettings as SdkRoomLoadSettings,
44 Account, AuthApi, AuthSession, Client as MatrixClient, Error, SessionChange, SessionTokens,
45};
46use matrix_sdk_common::{stream::StreamExt, SendOutsideWasm, SyncOutsideWasm};
47use matrix_sdk_ui::{
48 notification_client::{
49 NotificationClient as MatrixNotificationClient,
50 NotificationProcessSetup as MatrixNotificationProcessSetup,
51 },
52 spaces::SpaceService as UISpaceService,
53 unable_to_decrypt_hook::UtdHookManager,
54};
55use mime::Mime;
56use oauth2::Scope;
57use ruma::{
58 api::client::{
59 alias::get_alias,
60 error::ErrorKind,
61 profile::{AvatarUrl, DisplayName},
62 uiaa::UserIdentifier,
63 },
64 events::{
65 direct::DirectEventContent,
66 fully_read::FullyReadEventContent,
67 identity_server::IdentityServerEventContent,
68 ignored_user_list::IgnoredUserListEventContent,
69 key::verification::request::ToDeviceKeyVerificationRequestEvent,
70 marked_unread::{MarkedUnreadEventContent, UnstableMarkedUnreadEventContent},
71 push_rules::PushRulesEventContent,
72 room::{
73 history_visibility::RoomHistoryVisibilityEventContent,
74 join_rules::{
75 AllowRule as RumaAllowRule, JoinRule as RumaJoinRule, RoomJoinRulesEventContent,
76 },
77 message::{OriginalSyncRoomMessageEvent, Relation},
78 power_levels::RoomPowerLevelsEventContent,
79 },
80 secret_storage::{
81 default_key::SecretStorageDefaultKeyEventContent, key::SecretStorageKeyEventContent,
82 },
83 tag::TagEventContent,
84 AnyMessageLikeEventContent, AnySyncTimelineEvent,
85 GlobalAccountDataEvent as RumaGlobalAccountDataEvent,
86 RoomAccountDataEvent as RumaRoomAccountDataEvent,
87 },
88 push::{HttpPusherData as RumaHttpPusherData, PushFormat as RumaPushFormat},
89 room_version_rules::AuthorizationRules,
90 OwnedDeviceId, OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
91};
92use serde::{Deserialize, Serialize};
93use serde_json::{json, Value};
94use tokio::sync::broadcast::error::RecvError;
95use tracing::{debug, error};
96use url::Url;
97
98use super::{
99 room::{room_info::RoomInfo, Room},
100 session_verification::SessionVerificationController,
101};
102use crate::{
103 authentication::{HomeserverLoginDetails, OidcConfiguration, OidcError, SsoError, SsoHandler},
104 client,
105 encryption::Encryption,
106 notification::{
107 NotificationClient, NotificationEvent, NotificationItem, NotificationRoomInfo,
108 NotificationSenderInfo,
109 },
110 notification_settings::NotificationSettings,
111 qr_code::{GrantLoginWithQrCodeHandler, LoginWithQrCodeHandler},
112 room::{RoomHistoryVisibility, RoomInfoListener, RoomSendQueueUpdate},
113 room_directory_search::RoomDirectorySearch,
114 room_preview::RoomPreview,
115 ruma::{
116 AccountDataEvent, AccountDataEventType, AuthData, InviteAvatars, MediaPreviewConfig,
117 MediaPreviews, MediaSource, RoomAccountDataEvent, RoomAccountDataEventType,
118 },
119 runtime::get_runtime_handle,
120 spaces::SpaceService,
121 sync_service::{SyncService, SyncServiceBuilder},
122 task_handle::TaskHandle,
123 utd::{UnableToDecryptDelegate, UtdHook},
124 utils::AsyncRuntimeDropped,
125 ClientError,
126};
127
128#[derive(Clone, uniffi::Record)]
129pub struct PusherIdentifiers {
130 pub pushkey: String,
131 pub app_id: String,
132}
133
134impl From<PusherIdentifiers> for PusherIds {
135 fn from(value: PusherIdentifiers) -> Self {
136 Self::new(value.pushkey, value.app_id)
137 }
138}
139
140#[derive(Clone, uniffi::Record)]
141pub struct HttpPusherData {
142 pub url: String,
143 pub format: Option<PushFormat>,
144 pub default_payload: Option<String>,
145}
146
147#[derive(Clone, uniffi::Enum)]
148pub enum PusherKind {
149 Http { data: HttpPusherData },
150 Email,
151}
152
153impl TryFrom<PusherKind> for RumaPusherKind {
154 type Error = anyhow::Error;
155
156 fn try_from(value: PusherKind) -> anyhow::Result<Self> {
157 match value {
158 PusherKind::Http { data } => {
159 let mut ruma_data = RumaHttpPusherData::new(data.url);
160 if let Some(payload) = data.default_payload {
161 let json: Value = serde_json::from_str(&payload)?;
162 ruma_data.data.insert("default_payload".to_owned(), json);
163 }
164 ruma_data.format = data.format.map(Into::into);
165 Ok(Self::Http(ruma_data))
166 }
167 PusherKind::Email => {
168 let ruma_data = EmailPusherData::new();
169 Ok(Self::Email(ruma_data))
170 }
171 }
172 }
173}
174
175#[derive(Clone, uniffi::Enum)]
176pub enum PushFormat {
177 EventIdOnly,
178}
179
180impl From<PushFormat> for RumaPushFormat {
181 fn from(value: PushFormat) -> Self {
182 match value {
183 client::PushFormat::EventIdOnly => Self::EventIdOnly,
184 }
185 }
186}
187
188#[matrix_sdk_ffi_macros::export(callback_interface)]
189pub trait ClientDelegate: SyncOutsideWasm + SendOutsideWasm {
190 fn did_receive_auth_error(&self, is_soft_logout: bool);
191}
192
193#[matrix_sdk_ffi_macros::export(callback_interface)]
194pub trait ClientSessionDelegate: SyncOutsideWasm + SendOutsideWasm {
195 fn retrieve_session_from_keychain(&self, user_id: String) -> Result<Session, ClientError>;
196 fn save_session_in_keychain(&self, session: Session);
197}
198
199#[matrix_sdk_ffi_macros::export(callback_interface)]
200pub trait ProgressWatcher: SyncOutsideWasm + SendOutsideWasm {
201 fn transmission_progress(&self, progress: TransmissionProgress);
202}
203
204#[matrix_sdk_ffi_macros::export(callback_interface)]
206pub trait SendQueueRoomUpdateListener: SyncOutsideWasm + SendOutsideWasm {
207 fn on_update(&self, room_id: String, update: RoomSendQueueUpdate);
209}
210
211#[matrix_sdk_ffi_macros::export(callback_interface)]
213pub trait SendQueueRoomErrorListener: SyncOutsideWasm + SendOutsideWasm {
214 fn on_error(&self, room_id: String, error: ClientError);
217}
218
219#[matrix_sdk_ffi_macros::export(callback_interface)]
221pub trait AccountDataListener: SyncOutsideWasm + SendOutsideWasm {
222 fn on_change(&self, event: AccountDataEvent);
224}
225
226#[matrix_sdk_ffi_macros::export(callback_interface)]
228pub trait RoomAccountDataListener: SyncOutsideWasm + SendOutsideWasm {
229 fn on_change(&self, event: RoomAccountDataEvent, room_id: String);
231}
232
233#[matrix_sdk_ffi_macros::export(callback_interface)]
238pub trait SyncNotificationListener: SyncOutsideWasm + SendOutsideWasm {
239 fn on_notification(&self, notification: NotificationItem, room_id: String);
241}
242
243#[derive(Clone, Copy, uniffi::Record)]
244pub struct TransmissionProgress {
245 pub current: u64,
246 pub total: u64,
247}
248
249impl From<matrix_sdk::TransmissionProgress> for TransmissionProgress {
250 fn from(value: matrix_sdk::TransmissionProgress) -> Self {
251 Self {
252 current: value.current.try_into().unwrap_or(u64::MAX),
253 total: value.total.try_into().unwrap_or(u64::MAX),
254 }
255 }
256}
257
258#[derive(uniffi::Object)]
259pub struct Client {
260 pub(crate) inner: AsyncRuntimeDropped<MatrixClient>,
261
262 delegate: OnceLock<Arc<dyn ClientDelegate>>,
263
264 pub(crate) utd_hook_manager: OnceLock<Arc<UtdHookManager>>,
265
266 session_verification_controller:
267 Arc<tokio::sync::RwLock<Option<SessionVerificationController>>>,
268
269 #[cfg_attr(not(feature = "sqlite"), allow(unused))]
273 store_path: Option<PathBuf>,
274}
275
276impl Client {
277 pub async fn new(
278 sdk_client: MatrixClient,
279 enable_oidc_refresh_lock: bool,
280 session_delegate: Option<Arc<dyn ClientSessionDelegate>>,
281 store_path: Option<PathBuf>,
282 ) -> Result<Self, ClientError> {
283 let session_verification_controller: Arc<
284 tokio::sync::RwLock<Option<SessionVerificationController>>,
285 > = Default::default();
286 let controller = session_verification_controller.clone();
287 sdk_client.add_event_handler(
288 move |event: ToDeviceKeyVerificationRequestEvent| async move {
289 if let Some(session_verification_controller) = &*controller.clone().read().await {
290 session_verification_controller
291 .process_incoming_verification_request(
292 &event.sender,
293 event.content.transaction_id,
294 )
295 .await;
296 }
297 },
298 );
299
300 let controller = session_verification_controller.clone();
301 sdk_client.add_event_handler(move |event: OriginalSyncRoomMessageEvent| async move {
302 if let MessageType::VerificationRequest(_) = &event.content.msgtype {
303 if let Some(session_verification_controller) = &*controller.clone().read().await {
304 session_verification_controller
305 .process_incoming_verification_request(&event.sender, event.event_id)
306 .await;
307 }
308 }
309 });
310
311 let cross_process_store_locks_holder_name =
312 sdk_client.cross_process_store_locks_holder_name().to_owned();
313
314 let client = Client {
315 inner: AsyncRuntimeDropped::new(sdk_client.clone()),
316 delegate: OnceLock::new(),
317 utd_hook_manager: OnceLock::new(),
318 session_verification_controller,
319 store_path,
320 };
321
322 if enable_oidc_refresh_lock {
323 if session_delegate.is_none() {
324 return Err(anyhow::anyhow!(
325 "missing session delegates when enabling the cross-process lock"
326 ))?;
327 }
328
329 client
330 .inner
331 .oauth()
332 .enable_cross_process_refresh_lock(cross_process_store_locks_holder_name)
333 .await?;
334 }
335
336 if let Some(session_delegate) = session_delegate {
337 client.inner.set_session_callbacks(
338 {
339 let session_delegate = session_delegate.clone();
340 Box::new(move |client| {
341 let session_delegate = session_delegate.clone();
342 let user_id = client.user_id().context("user isn't logged in")?;
343 Ok(Self::retrieve_session(session_delegate, user_id)?)
344 })
345 },
346 {
347 let session_delegate = session_delegate.clone();
348 Box::new(move |client| {
349 let session_delegate = session_delegate.clone();
350 Ok(Self::save_session(session_delegate, client)?)
351 })
352 },
353 )?;
354 }
355
356 Ok(client)
357 }
358}
359
360#[matrix_sdk_ffi_macros::export]
361impl Client {
362 pub async fn optimize_stores(&self) -> Result<(), ClientError> {
365 Ok(self.inner.optimize_stores().await?)
366 }
367
368 pub async fn get_store_sizes(&self) -> Result<StoreSizes, ClientError> {
370 Ok(self.inner.get_store_sizes().await?.into())
371 }
372
373 pub async fn homeserver_login_details(&self) -> Arc<HomeserverLoginDetails> {
375 let oauth = self.inner.oauth();
376 let (supports_oidc_login, supported_oidc_prompts) = match oauth.server_metadata().await {
377 Ok(metadata) => {
378 let prompts =
379 metadata.prompt_values_supported.into_iter().map(Into::into).collect();
380
381 (true, prompts)
382 }
383 Err(error) => {
384 error!("Failed to fetch OIDC provider metadata: {error}");
385 (false, Default::default())
386 }
387 };
388
389 let login_types = self.inner.matrix_auth().get_login_types().await.ok();
390 let supports_password_login = login_types
391 .as_ref()
392 .map(|login_types| {
393 login_types.flows.iter().any(|login_type| {
394 matches!(login_type, get_login_types::v3::LoginType::Password(_))
395 })
396 })
397 .unwrap_or(false);
398 let supports_sso_login = login_types
399 .as_ref()
400 .map(|login_types| {
401 login_types
402 .flows
403 .iter()
404 .any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Sso(_)))
405 })
406 .unwrap_or(false);
407 let sliding_sync_version = self.sliding_sync_version();
408
409 Arc::new(HomeserverLoginDetails {
410 url: self.homeserver(),
411 sliding_sync_version,
412 supports_oidc_login,
413 supported_oidc_prompts,
414 supports_sso_login,
415 supports_password_login,
416 })
417 }
418
419 pub async fn login(
421 &self,
422 username: String,
423 password: String,
424 initial_device_name: Option<String>,
425 device_id: Option<String>,
426 ) -> Result<(), ClientError> {
427 let mut builder = self.inner.matrix_auth().login_username(&username, &password);
428 if let Some(initial_device_name) = initial_device_name.as_ref() {
429 builder = builder.initial_device_display_name(initial_device_name);
430 }
431 if let Some(device_id) = device_id.as_ref() {
432 builder = builder.device_id(device_id);
433 }
434 builder.send().await?;
435 Ok(())
436 }
437
438 pub async fn custom_login_with_jwt(
442 &self,
443 jwt: String,
444 initial_device_name: Option<String>,
445 device_id: Option<String>,
446 ) -> Result<(), ClientError> {
447 let data = json!({ "token": jwt }).as_object().unwrap().clone();
448
449 let mut builder = self.inner.matrix_auth().login_custom("org.matrix.login.jwt", data)?;
450
451 if let Some(initial_device_name) = initial_device_name.as_ref() {
452 builder = builder.initial_device_display_name(initial_device_name);
453 }
454
455 if let Some(device_id) = device_id.as_ref() {
456 builder = builder.device_id(device_id);
457 }
458
459 builder.send().await?;
460 Ok(())
461 }
462
463 pub async fn login_with_email(
465 &self,
466 email: String,
467 password: String,
468 initial_device_name: Option<String>,
469 device_id: Option<String>,
470 ) -> Result<(), ClientError> {
471 let mut builder = self
472 .inner
473 .matrix_auth()
474 .login_identifier(UserIdentifier::Email { address: email }, &password);
475
476 if let Some(initial_device_name) = initial_device_name.as_ref() {
477 builder = builder.initial_device_display_name(initial_device_name);
478 }
479
480 if let Some(device_id) = device_id.as_ref() {
481 builder = builder.device_id(device_id);
482 }
483
484 builder.send().await?;
485
486 Ok(())
487 }
488
489 pub(crate) async fn start_sso_login(
491 self: &Arc<Self>,
492 redirect_url: String,
493 idp_id: Option<String>,
494 ) -> Result<Arc<SsoHandler>, SsoError> {
495 let auth = self.inner.matrix_auth();
496 let url = auth
497 .get_sso_login_url(redirect_url.as_str(), idp_id.as_deref())
498 .await
499 .map_err(|e| SsoError::Generic { message: e.to_string() })?;
500 Ok(Arc::new(SsoHandler { client: Arc::clone(self), url }))
501 }
502
503 pub async fn url_for_oidc(
536 &self,
537 oidc_configuration: &OidcConfiguration,
538 prompt: Option<OidcPrompt>,
539 login_hint: Option<String>,
540 device_id: Option<String>,
541 additional_scopes: Option<Vec<String>>,
542 ) -> Result<Arc<OAuthAuthorizationData>, OidcError> {
543 let registration_data = oidc_configuration.registration_data()?;
544 let redirect_uri = oidc_configuration.redirect_uri()?;
545
546 let device_id = device_id.map(OwnedDeviceId::from);
547
548 let additional_scopes =
549 additional_scopes.map(|scopes| scopes.into_iter().map(Scope::new).collect::<Vec<_>>());
550
551 let mut url_builder = self.inner.oauth().login(
552 redirect_uri,
553 device_id,
554 Some(registration_data),
555 additional_scopes,
556 );
557
558 if let Some(prompt) = prompt {
559 url_builder = url_builder.prompt(vec![prompt.into()]);
560 }
561 if let Some(login_hint) = login_hint {
562 url_builder = url_builder.login_hint(login_hint);
563 }
564
565 let data = url_builder.build().await?;
566
567 Ok(Arc::new(data))
568 }
569
570 pub async fn abort_oidc_auth(&self, authorization_data: Arc<OAuthAuthorizationData>) {
573 self.inner.oauth().abort_login(&authorization_data.state).await;
574 }
575
576 pub async fn login_with_oidc_callback(&self, callback_url: String) -> Result<(), OidcError> {
578 let url = Url::parse(&callback_url).or(Err(OidcError::CallbackUrlInvalid))?;
579
580 self.inner.oauth().finish_login(url.into()).await?;
581
582 Ok(())
583 }
584
585 pub fn new_login_with_qr_code_handler(
593 self: Arc<Self>,
594 oidc_configuration: OidcConfiguration,
595 ) -> LoginWithQrCodeHandler {
596 LoginWithQrCodeHandler::new(self.inner.oauth(), oidc_configuration)
597 }
598
599 pub fn new_grant_login_with_qr_code_handler(self: Arc<Self>) -> GrantLoginWithQrCodeHandler {
602 GrantLoginWithQrCodeHandler::new(self.inner.oauth())
603 }
604
605 pub async fn restore_session(&self, session: Session) -> Result<(), ClientError> {
612 self.restore_session_with(session, RoomLoadSettings::All).await
613 }
614
615 pub async fn restore_session_with(
619 &self,
620 session: Session,
621 room_load_settings: RoomLoadSettings,
622 ) -> Result<(), ClientError> {
623 let sliding_sync_version = session.sliding_sync_version.clone();
624 let auth_session: AuthSession = session.try_into()?;
625
626 self.inner
627 .restore_session_with(
628 auth_session,
629 room_load_settings
630 .try_into()
631 .map_err(|error| ClientError::from_str(error, None))?,
632 )
633 .await?;
634 self.inner.set_sliding_sync_version(sliding_sync_version.try_into()?);
635
636 Ok(())
637 }
638
639 pub async fn enable_all_send_queues(&self, enable: bool) {
647 self.inner.send_queue().set_enabled(enable).await;
648 }
649
650 pub fn enable_send_queue_upload_progress(&self, enable: bool) {
653 self.inner.send_queue().enable_upload_progress(enable);
654 }
655
656 pub async fn subscribe_to_send_queue_updates(
663 &self,
664 listener: Box<dyn SendQueueRoomUpdateListener>,
665 ) -> Result<Arc<TaskHandle>, ClientError> {
666 let q = self.inner.send_queue();
667 let local_echoes = q.local_echoes().await?;
668 let mut subscriber = q.subscribe();
669
670 for (room_id, local_echoes) in local_echoes {
671 for local_echo in local_echoes {
672 listener.on_update(
673 room_id.clone().into(),
674 RoomSendQueueUpdate::NewLocalEvent {
675 transaction_id: local_echo.transaction_id.into(),
676 },
677 );
678 }
679 }
680
681 Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
682 loop {
683 match subscriber.recv().await {
684 Ok(update) => {
685 let room_id = update.room_id.to_string();
686 match update.update.try_into() {
687 Ok(update) => listener.on_update(room_id, update),
688 Err(err) => error!("error when converting send queue update: {err}"),
689 }
690 }
691 Err(err) => {
692 error!("error when listening to the send queue update reporter: {err}");
693 }
694 }
695 }
696 }))))
697 }
698
699 pub fn subscribe_to_send_queue_status(
705 &self,
706 listener: Box<dyn SendQueueRoomErrorListener>,
707 ) -> Arc<TaskHandle> {
708 let q = self.inner.send_queue();
709 let mut subscriber = q.subscribe_errors();
710
711 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
712 q.respawn_tasks_for_rooms_with_unsent_requests().await;
715
716 loop {
717 match subscriber.recv().await {
718 Ok(report) => listener
719 .on_error(report.room_id.to_string(), ClientError::from_err(report.error)),
720 Err(err) => {
721 error!("error when listening to the send queue error reporter: {err}");
722 }
723 }
724 }
725 })))
726 }
727
728 pub fn observe_account_data_event(
734 &self,
735 event_type: AccountDataEventType,
736 listener: Box<dyn AccountDataListener>,
737 ) -> Arc<TaskHandle> {
738 macro_rules! observe {
739 ($t:ty, $cb: expr) => {{
740 let observer =
742 Arc::new(self.inner.observe_events::<RumaGlobalAccountDataEvent<$t>, ()>());
743
744 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
745 let mut subscriber = observer.subscribe();
746 loop {
747 if let Some(next) = subscriber.next().await {
748 $cb(next.0);
749 }
750 }
751 })))
752 }};
753
754 ($t:ty) => {{
755 observe!($t, |event: RumaGlobalAccountDataEvent<$t>| {
756 listener.on_change(event.into());
757 })
758 }};
759 }
760
761 match event_type {
762 AccountDataEventType::Direct => {
763 observe!(DirectEventContent)
764 }
765 AccountDataEventType::IdentityServer => {
766 observe!(IdentityServerEventContent)
767 }
768 AccountDataEventType::IgnoredUserList => {
769 observe!(IgnoredUserListEventContent)
770 }
771 AccountDataEventType::PushRules => {
772 observe!(PushRulesEventContent, |event: RumaGlobalAccountDataEvent<
773 PushRulesEventContent,
774 >| {
775 if let Ok(event) = event.try_into() {
776 listener.on_change(event);
777 }
778 })
779 }
780 AccountDataEventType::SecretStorageDefaultKey => {
781 observe!(SecretStorageDefaultKeyEventContent)
782 }
783 AccountDataEventType::SecretStorageKey { key_id } => {
784 observe!(SecretStorageKeyEventContent, |event: RumaGlobalAccountDataEvent<
785 SecretStorageKeyEventContent,
786 >| {
787 if event.content.key_id != key_id {
788 return;
789 }
790 if let Ok(event) = event.try_into() {
791 listener.on_change(event);
792 }
793 })
794 }
795 }
796 }
797
798 pub fn observe_room_account_data_event(
804 &self,
805 room_id: String,
806 event_type: RoomAccountDataEventType,
807 listener: Box<dyn RoomAccountDataListener>,
808 ) -> Result<Arc<TaskHandle>, ClientError> {
809 macro_rules! observe {
810 ($t:ty, $cb: expr) => {{
811 let observer =
813 Arc::new(self.inner.observe_room_events::<RumaRoomAccountDataEvent<$t>, ()>(
814 &RoomId::parse(&room_id)?,
815 ));
816
817 Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
818 let mut subscriber = observer.subscribe();
819 loop {
820 if let Some(next) = subscriber.next().await {
821 $cb(next.0);
822 }
823 }
824 }))))
825 }};
826
827 ($t:ty) => {{
828 observe!($t, |event: RumaRoomAccountDataEvent<$t>| {
829 listener.on_change(event.into(), room_id.clone());
830 })
831 }};
832 }
833
834 match event_type {
835 RoomAccountDataEventType::FullyRead => {
836 observe!(FullyReadEventContent)
837 }
838 RoomAccountDataEventType::MarkedUnread => {
839 observe!(MarkedUnreadEventContent)
840 }
841 RoomAccountDataEventType::Tag => {
842 observe!(TagEventContent, |event: RumaRoomAccountDataEvent<TagEventContent>| {
843 if let Ok(event) = event.try_into() {
844 listener.on_change(event, room_id.clone());
845 }
846 })
847 }
848 RoomAccountDataEventType::UnstableMarkedUnread => {
849 observe!(UnstableMarkedUnreadEventContent)
850 }
851 }
852 }
853
854 pub async fn register_notification_handler(&self, listener: Box<dyn SyncNotificationListener>) {
866 let listener = Arc::new(listener);
867 self.inner
868 .register_notification_handler(move |notification, room, _client| {
869 let listener = listener.clone();
870 let room_id = room.room_id().to_string();
871
872 async move {
873 let is_noisy = notification.actions.iter().any(|a| a.sound().is_some());
875 let has_mention = notification.actions.iter().any(|a| a.is_highlight());
876
877 let actions: Vec<crate::notification_settings::Action> = notification
879 .actions
880 .into_iter()
881 .filter_map(|action| action.try_into().ok())
882 .collect();
883
884 let (sender, event, thread_id) = match notification.event {
886 RawAnySyncOrStrippedTimelineEvent::Sync(raw) => match raw.deserialize() {
887 Ok(deserialized) => {
888 let sender = deserialized.sender().to_owned();
889 let thread_id = match &deserialized {
890 AnySyncTimelineEvent::MessageLike(event) => {
891 match event.original_content() {
892 Some(AnyMessageLikeEventContent::RoomMessage(
893 content,
894 )) => match content.relates_to {
895 Some(Relation::Thread(thread)) => {
896 Some(thread.event_id.to_string())
897 }
898 _ => None,
899 },
900 _ => None,
901 }
902 }
903 _ => None,
904 };
905 let event = NotificationEvent::Timeline {
906 event: Arc::new(crate::event::TimelineEvent(Box::new(
907 deserialized,
908 ))),
909 };
910 (sender, event, thread_id)
911 }
912 Err(err) => {
913 tracing::warn!("Failed to deserialize timeline event: {err}");
914 return;
915 }
916 },
917 RawAnySyncOrStrippedTimelineEvent::Stripped(raw) => {
918 match raw.deserialize() {
919 Ok(deserialized) => {
920 let sender = deserialized.sender().to_owned();
921 let event =
922 NotificationEvent::Invite { sender: sender.to_string() };
923 let thread_id = None;
924 (sender, event, thread_id)
925 }
926 Err(err) => {
927 tracing::warn!(
928 "Failed to deserialize stripped state event: {err}"
929 );
930 return;
931 }
932 }
933 }
934 };
935
936 let sender = room.get_member_no_sync(&sender).await.ok().flatten();
938 let sender_info = if let Some(sender) = sender.as_ref() {
939 NotificationSenderInfo {
940 display_name: sender.display_name().map(|name| name.to_owned()),
941 avatar_url: sender.avatar_url().map(|uri| uri.to_string()),
942 is_name_ambiguous: sender.name_ambiguous(),
943 }
944 } else {
945 NotificationSenderInfo {
946 display_name: None,
947 avatar_url: None,
948 is_name_ambiguous: false,
949 }
950 };
951
952 let display_name = match room.display_name().await {
954 Ok(name) => name.to_string(),
955 Err(err) => {
956 tracing::warn!("Failed to calculate the room's display name: {err}");
957 return;
958 }
959 };
960 let is_direct = match room.is_direct().await {
961 Ok(is_direct) => is_direct,
962 Err(err) => {
963 tracing::warn!("Failed to determine if room is direct or not: {err}");
964 return;
965 }
966 };
967 let room_info = NotificationRoomInfo {
968 display_name,
969 avatar_url: room.avatar_url().map(Into::into),
970 canonical_alias: room.canonical_alias().map(Into::into),
971 topic: room.topic(),
972 join_rule: room
973 .join_rule()
974 .map(TryInto::try_into)
975 .transpose()
976 .ok()
977 .flatten(),
978 joined_members_count: room.joined_members_count(),
979 is_encrypted: Some(room.encryption_state().is_encrypted()),
980 is_direct,
981 is_space: room.is_space(),
982 };
983
984 listener.on_notification(
985 NotificationItem {
986 event,
987 sender_info,
988 room_info,
989 is_noisy: Some(is_noisy),
990 has_mention: Some(has_mention),
991 thread_id,
992 actions: Some(actions),
993 },
994 room_id,
995 );
996 }
997 })
998 .await;
999 }
1000
1001 pub async fn get_url(&self, url: String) -> Result<Vec<u8>, ClientError> {
1010 let response = self.inner.http_client().get(url).send().await?;
1011 if response.status().is_success() {
1012 Ok(response.bytes().await?.into())
1013 } else {
1014 Err(ClientError::Generic {
1015 msg: response.status().to_string(),
1016 details: response.text().await.ok(),
1017 })
1018 }
1019 }
1020
1021 pub async fn reset_supported_versions(&self) -> Result<(), ClientError> {
1027 Ok(self.inner.reset_supported_versions().await?)
1028 }
1029
1030 pub async fn reset_well_known(&self) -> Result<(), ClientError> {
1036 Ok(self.inner.reset_well_known().await?)
1037 }
1038}
1039
1040#[matrix_sdk_ffi_macros::export]
1041impl Client {
1042 pub async fn get_media_file(
1046 &self,
1047 media_source: Arc<MediaSource>,
1048 filename: Option<String>,
1049 mime_type: String,
1050 use_cache: bool,
1051 temp_dir: Option<String>,
1052 ) -> Result<Arc<MediaFileHandle>, ClientError> {
1053 #[cfg(not(target_family = "wasm"))]
1054 {
1055 let source = (*media_source).clone();
1056 let mime_type: mime::Mime = mime_type.parse()?;
1057
1058 let handle = self
1059 .inner
1060 .media()
1061 .get_media_file(
1062 &MediaRequestParameters {
1063 source: source.media_source,
1064 format: MediaFormat::File,
1065 },
1066 filename,
1067 &mime_type,
1068 use_cache,
1069 temp_dir,
1070 )
1071 .await?;
1072
1073 Ok(Arc::new(MediaFileHandle::new(handle)))
1074 }
1075
1076 #[cfg(target_family = "wasm")]
1080 Err(ClientError::Generic {
1081 msg: "get_media_file is not supported on wasm platforms".to_owned(),
1082 details: None,
1083 })
1084 }
1085
1086 pub async fn set_display_name(&self, name: String) -> Result<(), ClientError> {
1087 #[cfg(not(target_family = "wasm"))]
1088 {
1089 self.inner
1090 .account()
1091 .set_display_name(Some(name.as_str()))
1092 .await
1093 .context("Unable to set display name")?;
1094 }
1095
1096 #[cfg(target_family = "wasm")]
1097 {
1098 self.inner.account().set_display_name(Some(name.as_str())).await.map_err(|e| {
1099 ClientError::Generic {
1100 msg: "Unable to set display name".to_owned(),
1101 details: Some(e.to_string()),
1102 }
1103 })?;
1104 }
1105
1106 Ok(())
1107 }
1108}
1109
1110#[matrix_sdk_ffi_macros::export]
1111impl Client {
1112 pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
1114 self.inner.sliding_sync_version().into()
1115 }
1116
1117 pub async fn available_sliding_sync_versions(&self) -> Vec<SlidingSyncVersion> {
1125 self.inner.available_sliding_sync_versions().await.into_iter().map(Into::into).collect()
1126 }
1127
1128 pub fn set_delegate(
1131 self: Arc<Self>,
1132 delegate: Option<Box<dyn ClientDelegate>>,
1133 ) -> Result<Option<Arc<TaskHandle>>, ClientError> {
1134 if self.delegate.get().is_some() {
1135 return Err(ClientError::Generic {
1136 msg: "Delegate already initialized".to_owned(),
1137 details: None,
1138 });
1139 }
1140
1141 Ok(delegate.map(|delegate| {
1142 let mut session_change_receiver = self.inner.subscribe_to_session_changes();
1143 let client_clone = self.clone();
1144 let session_change_task = get_runtime_handle().spawn(async move {
1145 loop {
1146 match session_change_receiver.recv().await {
1147 Ok(session_change) => client_clone.process_session_change(session_change),
1148 Err(receive_error) => {
1149 if let RecvError::Closed = receive_error {
1150 break;
1151 }
1152 }
1153 }
1154 }
1155 });
1156
1157 self.delegate.get_or_init(|| Arc::from(delegate));
1158
1159 Arc::new(TaskHandle::new(session_change_task))
1160 }))
1161 }
1162
1163 pub async fn set_utd_delegate(
1166 self: Arc<Self>,
1167 utd_delegate: Box<dyn UnableToDecryptDelegate>,
1168 ) -> Result<(), ClientError> {
1169 if self.utd_hook_manager.get().is_some() {
1170 return Err(ClientError::Generic {
1171 msg: "UTD delegate already initialized".to_owned(),
1172 details: None,
1173 });
1174 }
1175
1176 const UTD_HOOK_GRACE_PERIOD: Duration = Duration::from_secs(60);
1179
1180 let mut utd_hook_manager = UtdHookManager::new(
1181 Arc::new(UtdHook { delegate: utd_delegate.into() }),
1182 (*self.inner).clone(),
1183 )
1184 .with_max_delay(UTD_HOOK_GRACE_PERIOD);
1185
1186 if let Err(e) = utd_hook_manager.reload_from_store().await {
1187 error!("Unable to reload UTD hook data from data store: {e}");
1188 }
1191
1192 self.utd_hook_manager.get_or_init(|| Arc::new(utd_hook_manager));
1193
1194 Ok(())
1195 }
1196
1197 pub fn session(&self) -> Result<Session, ClientError> {
1198 Self::session_inner((*self.inner).clone())
1199 }
1200
1201 pub async fn account_url(
1202 &self,
1203 action: Option<AccountManagementAction>,
1204 ) -> Result<Option<String>, ClientError> {
1205 if !matches!(self.inner.auth_api(), Some(AuthApi::OAuth(..))) {
1206 return Ok(None);
1207 }
1208
1209 let mut url_builder = match self.inner.oauth().account_management_url().await {
1210 Ok(Some(url_builder)) => url_builder,
1211 Ok(None) => return Ok(None),
1212 Err(e) => {
1213 error!("Failed retrieving account management URL: {e}");
1214 return Err(e.into());
1215 }
1216 };
1217
1218 if let Some(action) = action {
1219 url_builder = url_builder.action(action.into());
1220 }
1221
1222 Ok(Some(url_builder.build().to_string()))
1223 }
1224
1225 pub fn user_id(&self) -> Result<String, ClientError> {
1226 let user_id = self.inner.user_id().context("No User ID found")?;
1227 Ok(user_id.to_string())
1228 }
1229
1230 pub fn user_id_server_name(&self) -> Result<String, ClientError> {
1232 let user_id = self.inner.user_id().context("No User ID found")?;
1233 Ok(user_id.server_name().to_string())
1234 }
1235
1236 pub async fn display_name(&self) -> Result<String, ClientError> {
1237 let display_name =
1238 self.inner.account().get_display_name().await?.context("No User ID found")?;
1239 Ok(display_name)
1240 }
1241
1242 pub async fn upload_avatar(&self, mime_type: String, data: Vec<u8>) -> Result<(), ClientError> {
1243 let mime: Mime = mime_type.parse()?;
1244 self.inner.account().upload_avatar(&mime, data).await?;
1245 Ok(())
1246 }
1247
1248 pub async fn remove_avatar(&self) -> Result<(), ClientError> {
1249 self.inner.account().set_avatar_url(None).await?;
1250 Ok(())
1251 }
1252
1253 pub async fn avatar_url(&self) -> Result<Option<String>, ClientError> {
1256 let avatar_url = self.inner.account().get_avatar_url().await?;
1257
1258 Ok(avatar_url.map(|u| u.to_string()))
1259 }
1260
1261 pub async fn cached_avatar_url(&self) -> Result<Option<String>, ClientError> {
1263 Ok(self.inner.account().get_cached_avatar_url().await?.map(Into::into))
1264 }
1265
1266 pub fn device_id(&self) -> Result<String, ClientError> {
1267 let device_id = self.inner.device_id().context("No Device ID found")?;
1268 Ok(device_id.to_string())
1269 }
1270
1271 pub async fn create_room(&self, request: CreateRoomParameters) -> Result<String, ClientError> {
1272 let response = self.inner.create_room(request.try_into()?).await?;
1273 Ok(String::from(response.room_id()))
1274 }
1275
1276 pub async fn account_data(&self, event_type: String) -> Result<Option<String>, ClientError> {
1281 let event = self.inner.account().account_data_raw(event_type.into()).await?;
1282 Ok(event.map(|e| e.json().get().to_owned()))
1283 }
1284
1285 pub async fn set_account_data(
1289 &self,
1290 event_type: String,
1291 content: String,
1292 ) -> Result<(), ClientError> {
1293 let raw_content = Raw::from_json_string(content)?;
1294 self.inner.account().set_account_data_raw(event_type.into(), raw_content).await?;
1295 Ok(())
1296 }
1297
1298 pub async fn upload_media(
1299 &self,
1300 mime_type: String,
1301 data: Vec<u8>,
1302 progress_watcher: Option<Box<dyn ProgressWatcher>>,
1303 ) -> Result<String, ClientError> {
1304 let mime_type: mime::Mime = mime_type.parse().context("Parsing mime type")?;
1305 let request = self.inner.media().upload(&mime_type, data, None);
1306
1307 if let Some(progress_watcher) = progress_watcher {
1308 let mut subscriber = request.subscribe_to_send_progress();
1309 get_runtime_handle().spawn(async move {
1310 while let Some(progress) = subscriber.next().await {
1311 progress_watcher.transmission_progress(progress.into());
1312 }
1313 });
1314 }
1315
1316 let response = request.await?;
1317
1318 Ok(String::from(response.content_uri))
1319 }
1320
1321 pub async fn get_media_content(
1322 &self,
1323 media_source: Arc<MediaSource>,
1324 ) -> Result<Vec<u8>, ClientError> {
1325 let source = (*media_source).clone().media_source;
1326
1327 debug!(?source, "requesting media file");
1328 Ok(self
1329 .inner
1330 .media()
1331 .get_media_content(&MediaRequestParameters { source, format: MediaFormat::File }, true)
1332 .await?)
1333 }
1334
1335 pub async fn get_media_thumbnail(
1336 &self,
1337 media_source: Arc<MediaSource>,
1338 width: u64,
1339 height: u64,
1340 ) -> Result<Vec<u8>, ClientError> {
1341 let source = (*media_source).clone().media_source;
1342
1343 debug!(?source, width, height, "requesting media thumbnail");
1344 Ok(self
1345 .inner
1346 .media()
1347 .get_media_content(
1348 &MediaRequestParameters {
1349 source,
1350 format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(
1351 UInt::new(width).unwrap(),
1352 UInt::new(height).unwrap(),
1353 )),
1354 },
1355 true,
1356 )
1357 .await?)
1358 }
1359
1360 pub async fn get_session_verification_controller(
1361 &self,
1362 ) -> Result<Arc<SessionVerificationController>, ClientError> {
1363 if let Some(session_verification_controller) =
1364 &*self.session_verification_controller.read().await
1365 {
1366 return Ok(Arc::new(session_verification_controller.clone()));
1367 }
1368 let user_id = self.inner.user_id().context("Failed retrieving current user_id")?;
1369 let user_identity = self
1370 .inner
1371 .encryption()
1372 .get_user_identity(user_id)
1373 .await?
1374 .context("Failed retrieving user identity")?;
1375
1376 let session_verification_controller = SessionVerificationController::new(
1377 self.inner.encryption(),
1378 user_identity,
1379 self.inner.account(),
1380 );
1381
1382 *self.session_verification_controller.write().await =
1383 Some(session_verification_controller.clone());
1384
1385 Ok(Arc::new(session_verification_controller))
1386 }
1387
1388 pub async fn logout(&self) -> Result<(), ClientError> {
1390 Ok(self.inner.logout().await?)
1391 }
1392
1393 pub async fn set_pusher(
1395 &self,
1396 identifiers: PusherIdentifiers,
1397 kind: PusherKind,
1398 app_display_name: String,
1399 device_display_name: String,
1400 profile_tag: Option<String>,
1401 lang: String,
1402 ) -> Result<(), ClientError> {
1403 let ids = identifiers.into();
1404
1405 let pusher_init = PusherInit {
1406 ids,
1407 kind: kind.try_into()?,
1408 app_display_name,
1409 device_display_name,
1410 profile_tag,
1411 lang,
1412 };
1413 self.inner.pusher().set(pusher_init.into()).await?;
1414 Ok(())
1415 }
1416
1417 pub async fn delete_pusher(&self, identifiers: PusherIdentifiers) -> Result<(), ClientError> {
1419 self.inner.pusher().delete(identifiers.into()).await?;
1420 Ok(())
1421 }
1422
1423 pub fn homeserver(&self) -> String {
1425 self.inner.homeserver().to_string()
1426 }
1427
1428 pub fn server(&self) -> Option<String> {
1440 self.inner.server().map(ToString::to_string)
1441 }
1442
1443 pub fn rooms(&self) -> Vec<Arc<Room>> {
1444 self.inner
1445 .rooms()
1446 .into_iter()
1447 .map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1448 .collect()
1449 }
1450
1451 pub fn get_room(&self, room_id: String) -> Result<Option<Arc<Room>>, ClientError> {
1463 let room_id = RoomId::parse(room_id)?;
1464 let sdk_room = self.inner.get_room(&room_id);
1465
1466 let room =
1467 sdk_room.map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())));
1468 Ok(room)
1469 }
1470
1471 pub fn get_dm_room(&self, user_id: String) -> Result<Option<Arc<Room>>, ClientError> {
1472 let user_id = UserId::parse(user_id)?;
1473 let sdk_room = self.inner.get_dm_room(&user_id);
1474 let dm =
1475 sdk_room.map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())));
1476 Ok(dm)
1477 }
1478
1479 pub async fn search_users(
1480 &self,
1481 search_term: String,
1482 limit: u64,
1483 ) -> Result<SearchUsersResults, ClientError> {
1484 let response = self.inner.search_users(&search_term, limit).await?;
1485 Ok(SearchUsersResults::from(response))
1486 }
1487
1488 pub async fn get_profile(&self, user_id: String) -> Result<UserProfile, ClientError> {
1489 let user_id = <&UserId>::try_from(user_id.as_str())?;
1490 UserProfile::fetch(&self.inner.account(), user_id).await
1491 }
1492
1493 pub async fn notification_client(
1494 self: Arc<Self>,
1495 process_setup: NotificationProcessSetup,
1496 ) -> Result<Arc<NotificationClient>, ClientError> {
1497 Ok(Arc::new(NotificationClient {
1498 inner: MatrixNotificationClient::new((*self.inner).clone(), process_setup.into())
1499 .await?,
1500 client: self.clone(),
1501 }))
1502 }
1503
1504 pub fn sync_service(&self) -> Arc<SyncServiceBuilder> {
1505 SyncServiceBuilder::new((*self.inner).clone(), self.utd_hook_manager.get().cloned())
1506 }
1507
1508 pub fn space_service(&self) -> Arc<SpaceService> {
1509 let inner = UISpaceService::new((*self.inner).clone());
1510 Arc::new(SpaceService::new(inner))
1511 }
1512
1513 pub async fn get_notification_settings(&self) -> Arc<NotificationSettings> {
1514 let inner = self.inner.notification_settings().await;
1515
1516 Arc::new(NotificationSettings::new((*self.inner).clone(), inner))
1517 }
1518
1519 pub fn encryption(self: Arc<Self>) -> Arc<Encryption> {
1520 Arc::new(Encryption { inner: self.inner.encryption(), _client: self.clone() })
1521 }
1522
1523 pub async fn ignored_users(&self) -> Result<Vec<String>, ClientError> {
1526 if let Some(raw_content) =
1527 self.inner.account().fetch_account_data_static::<IgnoredUserListEventContent>().await?
1528 {
1529 let content = raw_content.deserialize()?;
1530 let user_ids: Vec<String> =
1531 content.ignored_users.keys().map(|id| id.to_string()).collect();
1532
1533 return Ok(user_ids);
1534 }
1535
1536 Ok(vec![])
1537 }
1538
1539 pub async fn ignore_user(&self, user_id: String) -> Result<(), ClientError> {
1540 let user_id = UserId::parse(user_id)?;
1541 self.inner.account().ignore_user(&user_id).await?;
1542 Ok(())
1543 }
1544
1545 pub async fn unignore_user(&self, user_id: String) -> Result<(), ClientError> {
1546 let user_id = UserId::parse(user_id)?;
1547 self.inner.account().unignore_user(&user_id).await?;
1548 Ok(())
1549 }
1550
1551 pub fn subscribe_to_ignored_users(
1552 &self,
1553 listener: Box<dyn IgnoredUsersListener>,
1554 ) -> Arc<TaskHandle> {
1555 let mut subscriber = self.inner.subscribe_to_ignore_user_list_changes();
1556 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
1557 while let Some(user_ids) = subscriber.next().await {
1558 listener.call(user_ids);
1559 }
1560 })))
1561 }
1562
1563 pub fn room_directory_search(&self) -> Arc<RoomDirectorySearch> {
1564 Arc::new(RoomDirectorySearch::new(
1565 matrix_sdk::room_directory_search::RoomDirectorySearch::new((*self.inner).clone()),
1566 ))
1567 }
1568
1569 pub async fn join_room_by_id(&self, room_id: String) -> Result<Arc<Room>, ClientError> {
1575 let room_id = RoomId::parse(room_id)?;
1576 let room = self.inner.join_room_by_id(room_id.as_ref()).await?;
1577 Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1578 }
1579
1580 pub async fn join_room_by_id_or_alias(
1587 &self,
1588 room_id_or_alias: String,
1589 server_names: Vec<String>,
1590 ) -> Result<Arc<Room>, ClientError> {
1591 let room_id = RoomOrAliasId::parse(&room_id_or_alias)?;
1592 let server_names = server_names
1593 .iter()
1594 .map(|name| OwnedServerName::try_from(name.as_str()))
1595 .collect::<Result<Vec<_>, _>>()?;
1596 let room =
1597 self.inner.join_room_by_id_or_alias(room_id.as_ref(), server_names.as_ref()).await?;
1598 Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1599 }
1600
1601 pub async fn knock(
1603 &self,
1604 room_id_or_alias: String,
1605 reason: Option<String>,
1606 server_names: Vec<String>,
1607 ) -> Result<Arc<Room>, ClientError> {
1608 let room_id = RoomOrAliasId::parse(&room_id_or_alias)?;
1609 let server_names =
1610 server_names.iter().map(ServerName::parse).collect::<Result<Vec<_>, _>>()?;
1611 let room = self.inner.knock(room_id, reason, server_names).await?;
1612 Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1613 }
1614
1615 pub async fn get_recently_visited_rooms(&self) -> Result<Vec<String>, ClientError> {
1616 Ok(self
1617 .inner
1618 .account()
1619 .get_recently_visited_rooms()
1620 .await?
1621 .into_iter()
1622 .map(Into::into)
1623 .collect())
1624 }
1625
1626 pub async fn track_recently_visited_room(&self, room: String) -> Result<(), ClientError> {
1627 let room_id = RoomId::parse(room)?;
1628 self.inner.account().track_recently_visited_room(room_id).await?;
1629 Ok(())
1630 }
1631
1632 pub async fn resolve_room_alias(
1635 &self,
1636 room_alias: String,
1637 ) -> Result<Option<ResolvedRoomAlias>, ClientError> {
1638 let room_alias = RoomAliasId::parse(&room_alias)?;
1639 match self.inner.resolve_room_alias(&room_alias).await {
1640 Ok(response) => Ok(Some(response.into())),
1641 Err(error) => match error.client_api_error_kind() {
1642 Some(ErrorKind::NotFound) => Ok(None),
1644 _ => Err(error.into()),
1646 },
1647 }
1648 }
1649
1650 pub async fn room_alias_exists(&self, room_alias: String) -> Result<bool, ClientError> {
1652 self.resolve_room_alias(room_alias).await.map(|ret| ret.is_some())
1653 }
1654
1655 pub async fn get_room_preview_from_room_id(
1661 &self,
1662 room_id: String,
1663 via_servers: Vec<String>,
1664 ) -> Result<Arc<RoomPreview>, ClientError> {
1665 let room_id = RoomId::parse(&room_id).context("room_id is not a valid room id")?;
1666
1667 let via_servers = via_servers
1668 .into_iter()
1669 .map(ServerName::parse)
1670 .collect::<Result<Vec<_>, _>>()
1671 .context("at least one `via` server name is invalid")?;
1672
1673 let room_id: &RoomId = &room_id;
1676
1677 let room_preview = self.inner.get_room_preview(room_id.into(), via_servers).await?;
1678
1679 Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
1680 }
1681
1682 pub async fn get_room_preview_from_room_alias(
1684 &self,
1685 room_alias: String,
1686 ) -> Result<Arc<RoomPreview>, ClientError> {
1687 let room_alias =
1688 RoomAliasId::parse(&room_alias).context("room_alias is not a valid room alias")?;
1689
1690 let room_alias: &RoomAliasId = &room_alias;
1693
1694 let room_preview = self.inner.get_room_preview(room_alias.into(), Vec::new()).await?;
1695
1696 Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
1697 }
1698
1699 pub async fn await_room_remote_echo(&self, room_id: String) -> Result<Arc<Room>, ClientError> {
1705 let room_id = RoomId::parse(room_id)?;
1706 Ok(Arc::new(Room::new(
1707 self.inner.await_room_remote_echo(&room_id).await,
1708 self.utd_hook_manager.get().cloned(),
1709 )))
1710 }
1711
1712 pub fn can_deactivate_account(&self) -> bool {
1715 matches!(self.inner.auth_api(), Some(AuthApi::Matrix(_)))
1716 }
1717
1718 pub async fn deactivate_account(
1729 &self,
1730 auth_data: Option<AuthData>,
1731 erase_data: bool,
1732 ) -> Result<(), ClientError> {
1733 if let Some(auth_data) = auth_data {
1734 _ = self.inner.account().deactivate(None, Some(auth_data.into()), erase_data).await?;
1735 } else {
1736 _ = self.inner.account().deactivate(None, None, erase_data).await?;
1737 }
1738
1739 Ok(())
1740 }
1741
1742 pub async fn is_room_alias_available(&self, alias: String) -> Result<bool, ClientError> {
1750 let alias = RoomAliasId::parse(alias)?;
1751 self.inner.is_room_alias_available(&alias).await.map_err(Into::into)
1752 }
1753
1754 pub async fn set_media_retention_policy(
1756 &self,
1757 policy: MediaRetentionPolicy,
1758 ) -> Result<(), ClientError> {
1759 let closure = async || -> Result<_, Error> {
1760 let store = self.inner.media_store().lock().await?;
1761 Ok(store.set_media_retention_policy(policy).await?)
1762 };
1763
1764 Ok(closure().await?)
1765 }
1766
1767 pub async fn clear_caches(
1790 &self,
1791 sync_service: Option<Arc<SyncService>>,
1792 ) -> Result<(), ClientError> {
1793 let closure = async || -> Result<_, ClientError> {
1794 if let Some(sync_service) = sync_service {
1796 sync_service.inner.expire_sessions().await;
1797 }
1798
1799 self.inner.send_queue().set_enabled(false).await;
1805
1806 self.inner
1808 .media_store()
1809 .lock()
1810 .await
1811 .map_err(Error::from)?
1812 .clean()
1813 .await
1814 .map_err(Error::from)?;
1815
1816 self.inner.event_cache().clear_all_rooms().await?;
1821
1822 #[cfg(feature = "sqlite")]
1824 if let Some(store_path) = &self.store_path {
1825 debug!("Removing the state store: {}", store_path.display());
1826
1827 for file_name in [
1834 PathBuf::from(STATE_STORE_DATABASE_NAME),
1835 PathBuf::from(format!("{STATE_STORE_DATABASE_NAME}.wal")),
1836 PathBuf::from(format!("{STATE_STORE_DATABASE_NAME}.shm")),
1837 ] {
1838 let file_path = store_path.join(file_name);
1839 if file_path.exists() {
1840 debug!("Removing file: {}", file_path.display());
1841 std::fs::remove_file(&file_path).map_err(|err| ClientError::Generic {
1842 msg: format!(
1843 "couldn't delete the state store file {}: {err}",
1844 file_path.display()
1845 ),
1846 details: None,
1847 })?;
1848 }
1849 }
1850 }
1851
1852 Ok(())
1853 };
1854
1855 closure().await
1856 }
1857
1858 pub async fn is_report_room_api_supported(&self) -> Result<bool, ClientError> {
1860 Ok(self.inner.server_versions().await?.contains(&ruma::api::MatrixVersion::V1_13))
1861 }
1862
1863 pub async fn is_livekit_rtc_supported(&self) -> Result<bool, ClientError> {
1865 Ok(self
1866 .inner
1867 .rtc_foci()
1868 .await?
1869 .iter()
1870 .any(|focus| matches!(focus, RtcFocusInfo::LiveKit(_))))
1871 }
1872
1873 pub async fn is_login_with_qr_code_supported(&self) -> Result<bool, ClientError> {
1875 Ok(matches!(self.inner.auth_api(), Some(AuthApi::OAuth(_)))
1876 && self.inner.unstable_features().await?.contains(&ruma::api::FeatureFlag::Msc4108))
1877 }
1878
1879 pub async fn server_vendor_info(&self) -> Result<matrix_sdk::ServerVendorInfo, ClientError> {
1884 Ok(self.inner.server_vendor_info(None).await?)
1885 }
1886
1887 pub async fn subscribe_to_media_preview_config(
1889 &self,
1890 listener: Box<dyn MediaPreviewConfigListener>,
1891 ) -> Result<Arc<TaskHandle>, ClientError> {
1892 let (initial_value, stream) = self.inner.account().observe_media_preview_config().await?;
1893 Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
1894 listener.on_change(initial_value.map(|config| config.into()));
1896 pin_mut!(stream);
1898 while let Some(media_preview_config) = stream.next().await {
1899 listener.on_change(Some(media_preview_config.into()));
1900 }
1901 }))))
1902 }
1903
1904 pub async fn set_media_preview_display_policy(
1906 &self,
1907 policy: MediaPreviews,
1908 ) -> Result<(), ClientError> {
1909 self.inner.account().set_media_previews_display_policy(policy.into()).await?;
1910 Ok(())
1911 }
1912
1913 pub async fn get_media_preview_display_policy(
1916 &self,
1917 ) -> Result<Option<MediaPreviews>, ClientError> {
1918 let configuration = self.inner.account().get_media_preview_config_event_content().await?;
1919 match configuration {
1920 Some(configuration) => Ok(configuration.media_previews.map(Into::into)),
1921 None => Ok(None),
1922 }
1923 }
1924
1925 pub async fn set_invite_avatars_display_policy(
1927 &self,
1928 policy: InviteAvatars,
1929 ) -> Result<(), ClientError> {
1930 self.inner.account().set_invite_avatars_display_policy(policy.into()).await?;
1931 Ok(())
1932 }
1933
1934 pub async fn get_invite_avatars_display_policy(
1937 &self,
1938 ) -> Result<Option<InviteAvatars>, ClientError> {
1939 let configuration = self.inner.account().get_media_preview_config_event_content().await?;
1940 match configuration {
1941 Some(configuration) => Ok(configuration.invite_avatars.map(Into::into)),
1942 None => Ok(None),
1943 }
1944 }
1945
1946 pub async fn fetch_media_preview_config(
1948 &self,
1949 ) -> Result<Option<MediaPreviewConfig>, ClientError> {
1950 Ok(self.inner.account().fetch_media_preview_config_event_content().await?.map(Into::into))
1951 }
1952
1953 pub async fn get_max_media_upload_size(&self) -> Result<u64, ClientError> {
1956 let max_upload_size = self.inner.load_or_fetch_max_upload_size().await?;
1957 Ok(max_upload_size.into())
1958 }
1959
1960 pub async fn subscribe_to_room_info(
1971 &self,
1972 room_id: String,
1973 listener: Box<dyn RoomInfoListener>,
1974 ) -> Result<Arc<TaskHandle>, ClientError> {
1975 let room_id = RoomId::parse(room_id)?;
1976
1977 if let Some(room) = self.inner.get_room(&room_id) {
1979 if let Ok(room_info) = RoomInfo::new(&room).await {
1980 listener.call(room_info);
1981 }
1982 }
1983
1984 Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn({
1985 let client = self.inner.clone();
1986 let mut receiver = client.room_info_notable_update_receiver();
1987 async move {
1988 while let Ok(room_update) = receiver.recv().await {
1989 if room_update.room_id != room_id {
1990 continue;
1991 }
1992
1993 if let Some(room) = client.get_room(&room_id) {
1994 if let Ok(room_info) = RoomInfo::new(&room).await {
1995 listener.call(room_info);
1996 }
1997 }
1998 }
1999 }
2000 }))))
2001 }
2002}
2003
2004#[cfg(feature = "experimental-element-recent-emojis")]
2005mod recent_emoji {
2006 use crate::{client::Client, error::ClientError};
2007
2008 #[derive(Debug, uniffi::Record)]
2010 pub struct RecentEmoji {
2011 pub emoji: String,
2013 pub count: u64,
2015 }
2016
2017 #[matrix_sdk_ffi_macros::export]
2018 impl Client {
2019 pub async fn add_recent_emoji(&self, emoji: String) -> Result<(), ClientError> {
2022 Ok(self.inner.account().add_recent_emoji(&emoji).await?)
2023 }
2024
2025 pub async fn get_recent_emojis(&self) -> Result<Vec<RecentEmoji>, ClientError> {
2028 Ok(self
2029 .inner
2030 .account()
2031 .get_recent_emojis(false)
2032 .await?
2033 .into_iter()
2034 .map(|(emoji, count)| RecentEmoji { emoji, count: count.into() })
2035 .collect::<Vec<RecentEmoji>>())
2036 }
2037 }
2038}
2039
2040#[matrix_sdk_ffi_macros::export(callback_interface)]
2041pub trait MediaPreviewConfigListener: SyncOutsideWasm + SendOutsideWasm {
2042 fn on_change(&self, media_preview_config: Option<MediaPreviewConfig>);
2043}
2044
2045#[matrix_sdk_ffi_macros::export(callback_interface)]
2046pub trait IgnoredUsersListener: SyncOutsideWasm + SendOutsideWasm {
2047 fn call(&self, ignored_user_ids: Vec<String>);
2048}
2049
2050#[derive(uniffi::Enum)]
2051pub enum NotificationProcessSetup {
2052 MultipleProcesses,
2053 SingleProcess { sync_service: Arc<SyncService> },
2054}
2055
2056impl From<NotificationProcessSetup> for MatrixNotificationProcessSetup {
2057 fn from(value: NotificationProcessSetup) -> Self {
2058 match value {
2059 NotificationProcessSetup::MultipleProcesses => {
2060 MatrixNotificationProcessSetup::MultipleProcesses
2061 }
2062 NotificationProcessSetup::SingleProcess { sync_service } => {
2063 MatrixNotificationProcessSetup::SingleProcess {
2064 sync_service: sync_service.inner.clone(),
2065 }
2066 }
2067 }
2068 }
2069}
2070
2071#[derive(uniffi::Record)]
2073pub struct ResolvedRoomAlias {
2074 pub room_id: String,
2076 pub servers: Vec<String>,
2078}
2079
2080impl From<get_alias::v3::Response> for ResolvedRoomAlias {
2081 fn from(value: get_alias::v3::Response) -> Self {
2082 Self {
2083 room_id: value.room_id.to_string(),
2084 servers: value.servers.iter().map(ToString::to_string).collect(),
2085 }
2086 }
2087}
2088
2089#[derive(uniffi::Record)]
2090pub struct SearchUsersResults {
2091 pub results: Vec<UserProfile>,
2092 pub limited: bool,
2093}
2094
2095impl From<search_users::v3::Response> for SearchUsersResults {
2096 fn from(value: search_users::v3::Response) -> Self {
2097 let results: Vec<UserProfile> = value.results.iter().map(UserProfile::from).collect();
2098 SearchUsersResults { results, limited: value.limited }
2099 }
2100}
2101
2102#[derive(uniffi::Record)]
2103pub struct UserProfile {
2104 pub user_id: String,
2105 pub display_name: Option<String>,
2106 pub avatar_url: Option<String>,
2107}
2108
2109impl UserProfile {
2110 pub(crate) async fn fetch(account: &Account, user_id: &UserId) -> Result<Self, ClientError> {
2113 let response = account.fetch_user_profile_of(user_id).await?;
2114 let display_name = response.get_static::<DisplayName>()?;
2115 let avatar_url = response.get_static::<AvatarUrl>()?.map(|url| url.to_string());
2116
2117 Ok(UserProfile { user_id: user_id.to_string(), display_name, avatar_url })
2118 }
2119}
2120
2121impl From<&search_users::v3::User> for UserProfile {
2122 fn from(value: &search_users::v3::User) -> Self {
2123 UserProfile {
2124 user_id: value.user_id.to_string(),
2125 display_name: value.display_name.clone(),
2126 avatar_url: value.avatar_url.as_ref().map(|url| url.to_string()),
2127 }
2128 }
2129}
2130
2131impl Client {
2132 fn process_session_change(&self, session_change: SessionChange) {
2133 if let Some(delegate) = self.delegate.get().cloned() {
2134 debug!("Applying session change: {session_change:?}");
2135 get_runtime_handle().spawn_blocking(move || match session_change {
2136 SessionChange::UnknownToken { soft_logout } => {
2137 delegate.did_receive_auth_error(soft_logout);
2138 }
2139 SessionChange::TokensRefreshed => {}
2140 });
2141 } else {
2142 debug!(
2143 "No client delegate found, session change couldn't be applied: {session_change:?}"
2144 );
2145 }
2146 }
2147
2148 fn retrieve_session(
2149 session_delegate: Arc<dyn ClientSessionDelegate>,
2150 user_id: &UserId,
2151 ) -> anyhow::Result<SessionTokens> {
2152 Ok(session_delegate.retrieve_session_from_keychain(user_id.to_string())?.into_tokens())
2153 }
2154
2155 fn session_inner(client: matrix_sdk::Client) -> Result<Session, ClientError> {
2156 let auth_api = client.auth_api().context("Missing authentication API")?;
2157
2158 let homeserver_url = client.homeserver().into();
2159 let sliding_sync_version = client.sliding_sync_version();
2160
2161 Session::new(auth_api, homeserver_url, sliding_sync_version.into())
2162 }
2163
2164 fn save_session(
2165 session_delegate: Arc<dyn ClientSessionDelegate>,
2166 client: matrix_sdk::Client,
2167 ) -> anyhow::Result<()> {
2168 let session = Self::session_inner(client)?;
2169 session_delegate.save_session_in_keychain(session);
2170 Ok(())
2171 }
2172}
2173
2174#[derive(uniffi::Enum)]
2180pub enum RoomLoadSettings {
2181 All,
2184
2185 One { room_id: String },
2191}
2192
2193impl TryInto<SdkRoomLoadSettings> for RoomLoadSettings {
2194 type Error = String;
2195
2196 fn try_into(self) -> Result<SdkRoomLoadSettings, Self::Error> {
2197 Ok(match self {
2198 Self::All => SdkRoomLoadSettings::All,
2199 Self::One { room_id } => {
2200 SdkRoomLoadSettings::One(RoomId::parse(room_id).map_err(|error| error.to_string())?)
2201 }
2202 })
2203 }
2204}
2205
2206#[derive(uniffi::Record)]
2207pub struct NotificationPowerLevels {
2208 pub room: i32,
2209}
2210
2211impl From<NotificationPowerLevels> for ruma::power_levels::NotificationPowerLevels {
2212 fn from(value: NotificationPowerLevels) -> Self {
2213 let mut notification_power_levels = Self::new();
2214 notification_power_levels.room = value.room.into();
2215 notification_power_levels
2216 }
2217}
2218
2219#[derive(uniffi::Record)]
2220pub struct PowerLevels {
2221 pub users_default: Option<i32>,
2222 pub events_default: Option<i32>,
2223 pub state_default: Option<i32>,
2224 pub ban: Option<i32>,
2225 pub kick: Option<i32>,
2226 pub redact: Option<i32>,
2227 pub invite: Option<i32>,
2228 pub notifications: Option<NotificationPowerLevels>,
2229 pub users: HashMap<String, i32>,
2230 pub events: HashMap<String, i32>,
2231}
2232
2233impl From<PowerLevels> for RoomPowerLevelsEventContent {
2234 fn from(value: PowerLevels) -> Self {
2235 let mut power_levels = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
2236
2237 if let Some(users_default) = value.users_default {
2238 power_levels.users_default = users_default.into();
2239 }
2240 if let Some(state_default) = value.state_default {
2241 power_levels.state_default = state_default.into();
2242 }
2243 if let Some(events_default) = value.events_default {
2244 power_levels.events_default = events_default.into();
2245 }
2246 if let Some(ban) = value.ban {
2247 power_levels.ban = ban.into();
2248 }
2249 if let Some(kick) = value.kick {
2250 power_levels.kick = kick.into();
2251 }
2252 if let Some(redact) = value.redact {
2253 power_levels.redact = redact.into();
2254 }
2255 if let Some(invite) = value.invite {
2256 power_levels.invite = invite.into();
2257 }
2258 if let Some(notifications) = value.notifications {
2259 power_levels.notifications = notifications.into()
2260 }
2261 power_levels.users = value
2262 .users
2263 .iter()
2264 .filter_map(|(user_id, power_level)| match UserId::parse(user_id) {
2265 Ok(id) => Some((id, (*power_level).into())),
2266 Err(e) => {
2267 error!(user_id, "Skipping invalid user ID, error: {e}");
2268 None
2269 }
2270 })
2271 .collect();
2272
2273 power_levels.events = value
2274 .events
2275 .iter()
2276 .map(|(event_type, power_level)| {
2277 let event_type: ruma::events::TimelineEventType = event_type.as_str().into();
2278 (event_type, (*power_level).into())
2279 })
2280 .collect();
2281
2282 power_levels
2283 }
2284}
2285
2286#[derive(uniffi::Record)]
2287pub struct CreateRoomParameters {
2288 pub name: Option<String>,
2289 #[uniffi(default = None)]
2290 pub topic: Option<String>,
2291 pub is_encrypted: bool,
2292 #[uniffi(default = false)]
2293 pub is_direct: bool,
2294 pub visibility: RoomVisibility,
2295 pub preset: RoomPreset,
2296 #[uniffi(default = None)]
2297 pub invite: Option<Vec<String>>,
2298 #[uniffi(default = None)]
2299 pub avatar: Option<String>,
2300 #[uniffi(default = None)]
2301 pub power_level_content_override: Option<PowerLevels>,
2302 #[uniffi(default = None)]
2303 pub join_rule_override: Option<JoinRule>,
2304 #[uniffi(default = None)]
2305 pub history_visibility_override: Option<RoomHistoryVisibility>,
2306 #[uniffi(default = None)]
2307 pub canonical_alias: Option<String>,
2308}
2309
2310impl TryFrom<CreateRoomParameters> for create_room::v3::Request {
2311 type Error = ClientError;
2312
2313 fn try_from(value: CreateRoomParameters) -> Result<create_room::v3::Request, Self::Error> {
2314 let mut request = create_room::v3::Request::new();
2315 request.name = value.name;
2316 request.topic = value.topic;
2317 request.is_direct = value.is_direct;
2318 request.visibility = value.visibility.into();
2319 request.preset = Some(value.preset.into());
2320 request.room_alias_name = value.canonical_alias;
2321 request.invite = match value.invite {
2322 Some(invite) => invite
2323 .iter()
2324 .filter_map(|user_id| match UserId::parse(user_id) {
2325 Ok(id) => Some(id),
2326 Err(e) => {
2327 error!(user_id, "Skipping invalid user ID, error: {e}");
2328 None
2329 }
2330 })
2331 .collect(),
2332 None => vec![],
2333 };
2334
2335 let mut initial_state: Vec<Raw<AnyInitialStateEvent>> = vec![];
2336
2337 if value.is_encrypted {
2338 let content =
2339 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
2340 initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2341 }
2342
2343 if let Some(url) = value.avatar {
2344 let mut content = RoomAvatarEventContent::new();
2345 content.url = Some(url.into());
2346 initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2347 }
2348
2349 if let Some(join_rule_override) = value.join_rule_override {
2350 let content = RoomJoinRulesEventContent::new(join_rule_override.try_into()?);
2351 initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2352 }
2353
2354 if let Some(history_visibility_override) = value.history_visibility_override {
2355 let content =
2356 RoomHistoryVisibilityEventContent::new(history_visibility_override.try_into()?);
2357 initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2358 }
2359
2360 request.initial_state = initial_state;
2361
2362 if let Some(power_levels) = value.power_level_content_override {
2363 match Raw::new(&power_levels.into()) {
2364 Ok(power_levels) => {
2365 request.power_level_content_override = Some(power_levels);
2366 }
2367 Err(e) => {
2368 return Err(ClientError::Generic {
2369 msg: format!("Failed to serialize power levels, error: {e}"),
2370 details: Some(format!("{e:?}")),
2371 });
2372 }
2373 }
2374 }
2375
2376 Ok(request)
2377 }
2378}
2379
2380#[derive(uniffi::Enum)]
2381pub enum RoomVisibility {
2382 Public,
2384
2385 Private,
2387
2388 Custom { value: String },
2390}
2391
2392impl From<RoomVisibility> for Visibility {
2393 fn from(value: RoomVisibility) -> Self {
2394 match value {
2395 RoomVisibility::Public => Self::Public,
2396 RoomVisibility::Private => Self::Private,
2397 RoomVisibility::Custom { value } => value.as_str().into(),
2398 }
2399 }
2400}
2401
2402impl From<Visibility> for RoomVisibility {
2403 fn from(value: Visibility) -> Self {
2404 match value {
2405 Visibility::Public => Self::Public,
2406 Visibility::Private => Self::Private,
2407 _ => Self::Custom { value: value.as_str().to_owned() },
2408 }
2409 }
2410}
2411
2412#[derive(uniffi::Enum)]
2413#[allow(clippy::enum_variant_names)]
2414pub enum RoomPreset {
2415 PrivateChat,
2418
2419 PublicChat,
2422
2423 TrustedPrivateChat,
2426}
2427
2428impl From<RoomPreset> for create_room::v3::RoomPreset {
2429 fn from(value: RoomPreset) -> Self {
2430 match value {
2431 RoomPreset::PrivateChat => Self::PrivateChat,
2432 RoomPreset::PublicChat => Self::PublicChat,
2433 RoomPreset::TrustedPrivateChat => Self::TrustedPrivateChat,
2434 }
2435 }
2436}
2437
2438#[derive(uniffi::Record)]
2439pub struct Session {
2440 pub access_token: String,
2443 pub refresh_token: Option<String>,
2447 pub user_id: String,
2449 pub device_id: String,
2451
2452 pub homeserver_url: String,
2455 pub oidc_data: Option<String>,
2458 pub sliding_sync_version: SlidingSyncVersion,
2460}
2461
2462impl Session {
2463 fn new(
2464 auth_api: AuthApi,
2465 homeserver_url: String,
2466 sliding_sync_version: SlidingSyncVersion,
2467 ) -> Result<Session, ClientError> {
2468 match auth_api {
2469 AuthApi::Matrix(a) => {
2471 let matrix_sdk::authentication::matrix::MatrixSession {
2472 meta: matrix_sdk::SessionMeta { user_id, device_id },
2473 tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2474 } = a.session().context("Missing session")?;
2475
2476 Ok(Session {
2477 access_token,
2478 refresh_token,
2479 user_id: user_id.to_string(),
2480 device_id: device_id.to_string(),
2481 homeserver_url,
2482 oidc_data: None,
2483 sliding_sync_version,
2484 })
2485 }
2486 AuthApi::OAuth(api) => {
2488 let matrix_sdk::authentication::oauth::UserSession {
2489 meta: matrix_sdk::SessionMeta { user_id, device_id },
2490 tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2491 } = api.user_session().context("Missing session")?;
2492 let client_id = api.client_id().context("OIDC client ID is missing.")?.clone();
2493 let oidc_data = OidcSessionData { client_id };
2494
2495 let oidc_data = serde_json::to_string(&oidc_data).ok();
2496 Ok(Session {
2497 access_token,
2498 refresh_token,
2499 user_id: user_id.to_string(),
2500 device_id: device_id.to_string(),
2501 homeserver_url,
2502 oidc_data,
2503 sliding_sync_version,
2504 })
2505 }
2506 _ => Err(anyhow!("Unknown authentication API").into()),
2507 }
2508 }
2509
2510 fn into_tokens(self) -> matrix_sdk::SessionTokens {
2511 SessionTokens { access_token: self.access_token, refresh_token: self.refresh_token }
2512 }
2513}
2514
2515impl TryFrom<Session> for AuthSession {
2516 type Error = ClientError;
2517 fn try_from(value: Session) -> Result<Self, Self::Error> {
2518 let Session {
2519 access_token,
2520 refresh_token,
2521 user_id,
2522 device_id,
2523 homeserver_url: _,
2524 oidc_data,
2525 sliding_sync_version: _,
2526 } = value;
2527
2528 if let Some(oidc_data) = oidc_data {
2529 let oidc_data = serde_json::from_str::<OidcSessionData>(&oidc_data)?;
2531
2532 let user_session = matrix_sdk::authentication::oauth::UserSession {
2533 meta: matrix_sdk::SessionMeta {
2534 user_id: user_id.try_into()?,
2535 device_id: device_id.into(),
2536 },
2537 tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2538 };
2539
2540 let session = OAuthSession { client_id: oidc_data.client_id, user: user_session };
2541
2542 Ok(AuthSession::OAuth(session.into()))
2543 } else {
2544 let session = matrix_sdk::authentication::matrix::MatrixSession {
2546 meta: matrix_sdk::SessionMeta {
2547 user_id: user_id.try_into()?,
2548 device_id: device_id.into(),
2549 },
2550 tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2551 };
2552
2553 Ok(AuthSession::Matrix(session))
2554 }
2555 }
2556}
2557
2558#[derive(Serialize, Deserialize)]
2561pub(crate) struct OidcSessionData {
2562 client_id: ClientId,
2563}
2564
2565#[derive(uniffi::Enum)]
2566pub enum AccountManagementAction {
2567 Profile,
2568 SessionsList,
2569 SessionView { device_id: String },
2570 SessionEnd { device_id: String },
2571 AccountDeactivate,
2572 CrossSigningReset,
2573}
2574
2575impl From<AccountManagementAction> for AccountManagementActionFull {
2576 fn from(value: AccountManagementAction) -> Self {
2577 match value {
2578 AccountManagementAction::Profile => Self::Profile,
2579 AccountManagementAction::SessionsList => Self::SessionsList,
2580 AccountManagementAction::SessionView { device_id } => {
2581 Self::SessionView { device_id: device_id.into() }
2582 }
2583 AccountManagementAction::SessionEnd { device_id } => {
2584 Self::SessionEnd { device_id: device_id.into() }
2585 }
2586 AccountManagementAction::AccountDeactivate => Self::AccountDeactivate,
2587 AccountManagementAction::CrossSigningReset => Self::CrossSigningReset,
2588 }
2589 }
2590}
2591
2592#[matrix_sdk_ffi_macros::export]
2593fn gen_transaction_id() -> String {
2594 TransactionId::new().to_string()
2595}
2596
2597#[derive(uniffi::Object)]
2600pub struct MediaFileHandle {
2601 #[cfg(not(target_family = "wasm"))]
2602 inner: std::sync::RwLock<Option<SdkMediaFileHandle>>,
2603}
2604
2605impl MediaFileHandle {
2606 #[cfg(not(target_family = "wasm"))]
2607 fn new(handle: SdkMediaFileHandle) -> Self {
2608 Self { inner: std::sync::RwLock::new(Some(handle)) }
2609 }
2610}
2611
2612#[matrix_sdk_ffi_macros::export]
2613impl MediaFileHandle {
2614 pub fn path(&self) -> Result<String, ClientError> {
2616 #[cfg(not(target_family = "wasm"))]
2617 return Ok(self
2618 .inner
2619 .read()
2620 .unwrap()
2621 .as_ref()
2622 .context("MediaFileHandle must not be used after calling persist")?
2623 .path()
2624 .to_str()
2625 .unwrap()
2626 .to_owned());
2627 #[cfg(target_family = "wasm")]
2628 Err(ClientError::Generic {
2629 msg: "MediaFileHandle.path() is not supported on WASM targets".to_string(),
2630 details: None,
2631 })
2632 }
2633
2634 pub fn persist(&self, path: String) -> Result<bool, ClientError> {
2635 #[cfg(not(target_family = "wasm"))]
2636 {
2637 let mut guard = self.inner.write().unwrap();
2638 Ok(
2639 match guard
2640 .take()
2641 .context("MediaFileHandle was already persisted")?
2642 .persist(path.as_ref())
2643 {
2644 Ok(_) => true,
2645 Err(e) => {
2646 *guard = Some(e.file);
2647 false
2648 }
2649 },
2650 )
2651 }
2652 #[cfg(target_family = "wasm")]
2653 Err(ClientError::Generic {
2654 msg: "MediaFileHandle.persist() is not supported on WASM targets".to_string(),
2655 details: None,
2656 })
2657 }
2658}
2659
2660#[derive(Clone, uniffi::Enum)]
2661pub enum SlidingSyncVersion {
2662 None,
2663 Native,
2664}
2665
2666impl From<SdkSlidingSyncVersion> for SlidingSyncVersion {
2667 fn from(value: SdkSlidingSyncVersion) -> Self {
2668 match value {
2669 SdkSlidingSyncVersion::None => Self::None,
2670 SdkSlidingSyncVersion::Native => Self::Native,
2671 }
2672 }
2673}
2674
2675impl TryFrom<SlidingSyncVersion> for SdkSlidingSyncVersion {
2676 type Error = ClientError;
2677
2678 fn try_from(value: SlidingSyncVersion) -> Result<Self, Self::Error> {
2679 Ok(match value {
2680 SlidingSyncVersion::None => Self::None,
2681 SlidingSyncVersion::Native => Self::Native,
2682 })
2683 }
2684}
2685
2686#[derive(Clone, uniffi::Enum)]
2687pub enum OidcPrompt {
2688 Create,
2693
2694 Login,
2697
2698 Consent,
2701
2702 Unknown { value: String },
2704}
2705
2706impl From<RumaOidcPrompt> for OidcPrompt {
2707 fn from(value: RumaOidcPrompt) -> Self {
2708 match value {
2709 RumaOidcPrompt::Create => Self::Create,
2710 value => match value.as_str() {
2711 "consent" => Self::Consent,
2712 "login" => Self::Login,
2713 _ => Self::Unknown { value: value.to_string() },
2714 },
2715 }
2716 }
2717}
2718
2719impl From<OidcPrompt> for RumaOidcPrompt {
2720 fn from(value: OidcPrompt) -> Self {
2721 match value {
2722 OidcPrompt::Create => Self::Create,
2723 OidcPrompt::Consent => Self::from("consent"),
2724 OidcPrompt::Login => Self::from("login"),
2725 OidcPrompt::Unknown { value } => value.into(),
2726 }
2727 }
2728}
2729
2730#[derive(Debug, Clone, uniffi::Enum)]
2732pub enum JoinRule {
2733 Public,
2735
2736 Invite,
2739
2740 Knock,
2745
2746 Private,
2748
2749 Restricted { rules: Vec<AllowRule> },
2752
2753 KnockRestricted { rules: Vec<AllowRule> },
2757
2758 Custom {
2760 repr: String,
2762 },
2763}
2764
2765#[derive(Debug, Clone, uniffi::Enum)]
2767pub enum AllowRule {
2768 RoomMembership { room_id: String },
2771
2772 Custom { json: String },
2775}
2776
2777impl TryFrom<JoinRule> for RumaJoinRule {
2778 type Error = ClientError;
2779
2780 fn try_from(value: JoinRule) -> Result<Self, Self::Error> {
2781 match value {
2782 JoinRule::Public => Ok(Self::Public),
2783 JoinRule::Invite => Ok(Self::Invite),
2784 JoinRule::Knock => Ok(Self::Knock),
2785 JoinRule::Private => Ok(Self::Private),
2786 JoinRule::Restricted { rules } => {
2787 let rules = ruma_allow_rules_from_ffi(rules)?;
2788 Ok(Self::Restricted(ruma::events::room::join_rules::Restricted::new(rules)))
2789 }
2790 JoinRule::KnockRestricted { rules } => {
2791 let rules = ruma_allow_rules_from_ffi(rules)?;
2792 Ok(Self::KnockRestricted(ruma::events::room::join_rules::Restricted::new(rules)))
2793 }
2794 JoinRule::Custom { repr } => Ok(serde_json::from_str(&repr)?),
2795 }
2796 }
2797}
2798
2799fn ruma_allow_rules_from_ffi(value: Vec<AllowRule>) -> Result<Vec<RumaAllowRule>, ClientError> {
2800 let mut ret = Vec::with_capacity(value.len());
2801 for rule in value {
2802 let rule: Result<RumaAllowRule, ClientError> = rule.try_into();
2803 match rule {
2804 Ok(rule) => ret.push(rule),
2805 Err(error) => return Err(error),
2806 }
2807 }
2808 Ok(ret)
2809}
2810
2811impl TryFrom<AllowRule> for RumaAllowRule {
2812 type Error = ClientError;
2813
2814 fn try_from(value: AllowRule) -> Result<Self, Self::Error> {
2815 match value {
2816 AllowRule::RoomMembership { room_id } => {
2817 let room_id = RoomId::parse(room_id)?;
2818 Ok(Self::RoomMembership(ruma::room::RoomMembership::new(room_id)))
2819 }
2820 AllowRule::Custom { json } => Ok(Self::_Custom(Box::new(serde_json::from_str(&json)?))),
2821 }
2822 }
2823}
2824
2825impl TryFrom<RumaJoinRule> for JoinRule {
2826 type Error = String;
2827 fn try_from(value: RumaJoinRule) -> Result<Self, Self::Error> {
2828 match value {
2829 RumaJoinRule::Knock => Ok(JoinRule::Knock),
2830 RumaJoinRule::Public => Ok(JoinRule::Public),
2831 RumaJoinRule::Private => Ok(JoinRule::Private),
2832 RumaJoinRule::KnockRestricted(restricted) => {
2833 let rules = restricted.allow.into_iter().map(TryInto::try_into).collect::<Result<
2834 Vec<_>,
2835 Self::Error,
2836 >>(
2837 )?;
2838 Ok(JoinRule::KnockRestricted { rules })
2839 }
2840 RumaJoinRule::Restricted(restricted) => {
2841 let rules = restricted.allow.into_iter().map(TryInto::try_into).collect::<Result<
2842 Vec<_>,
2843 Self::Error,
2844 >>(
2845 )?;
2846 Ok(JoinRule::Restricted { rules })
2847 }
2848 RumaJoinRule::Invite => Ok(JoinRule::Invite),
2849 RumaJoinRule::_Custom(_) => Ok(JoinRule::Custom { repr: value.as_str().to_owned() }),
2850 _ => Err(format!("Unknown JoinRule: {value:?}")),
2851 }
2852 }
2853}
2854
2855impl TryFrom<RumaAllowRule> for AllowRule {
2856 type Error = String;
2857 fn try_from(value: RumaAllowRule) -> Result<Self, Self::Error> {
2858 match value {
2859 RumaAllowRule::RoomMembership(membership) => {
2860 Ok(AllowRule::RoomMembership { room_id: membership.room_id.to_string() })
2861 }
2862 RumaAllowRule::_Custom(repr) => {
2863 let json = serde_json::to_string(&repr)
2864 .map_err(|e| format!("Couldn't serialize custom AllowRule: {e:?}"))?;
2865 Ok(Self::Custom { json })
2866 }
2867 _ => Err(format!("Invalid AllowRule: {value:?}")),
2868 }
2869 }
2870}
2871
2872#[derive(Debug, Clone, uniffi::Record)]
2875pub struct StoreSizes {
2876 crypto_store: Option<u64>,
2878 state_store: Option<u64>,
2880 event_cache_store: Option<u64>,
2882 media_store: Option<u64>,
2884}
2885
2886impl From<matrix_sdk::StoreSizes> for StoreSizes {
2887 fn from(value: matrix_sdk::StoreSizes) -> Self {
2888 Self {
2889 crypto_store: value.crypto_store.map(|v| v as u64),
2890 state_store: value.state_store.map(|v| v as u64),
2891 event_cache_store: value.event_cache_store.map(|v| v as u64),
2892 media_store: value.media_store.map(|v| v as u64),
2893 }
2894 }
2895}