atagen

matrix-rust-sdk

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk.git
bindings/matrix-sdk-ffi/src/client.rsRust3427 lines125.7 KB
1
// Copyright 2025 The Matrix.org Foundation C.I.C.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for that specific language governing permissions and
13
// limitations under the License.
14
15
use std::{
16
    collections::HashMap,
17
    fmt::Debug,
18
    path::PathBuf,
19
    sync::{Arc, OnceLock},
20
    time::Duration,
21
};
22
23
use anyhow::{Context as _, anyhow};
24
use futures_util::pin_mut;
25
#[cfg(feature = "sqlite")]
26
use matrix_sdk::STATE_STORE_DATABASE_NAME;
27
#[cfg(not(target_family = "wasm"))]
28
use matrix_sdk::media::MediaFileHandle as SdkMediaFileHandle;
29
use matrix_sdk::{
30
    Account, AuthApi, AuthSession, Client as MatrixClient, Error, SessionChange, SessionTokens,
31
    authentication::oauth::{
32
        ClientId, OAuthAuthorizationData, OAuthError as SdkOAuthError, OAuthSession,
33
    },
34
    deserialized_responses::RawAnySyncOrStrippedTimelineEvent,
35
    executor::AbortOnDrop,
36
    media::{MediaFormat, MediaRequestParameters, MediaRetentionPolicy, MediaThumbnailSettings},
37
    ruma::{
38
        EventEncryptionAlgorithm, RoomId, TransactionId, UInt, UserId,
39
        api::client::{
40
            account::request_openid_token,
41
            discovery::get_authorization_server_metadata::v1::Prompt as RumaOAuthPrompt,
42
            push::{EmailPusherData, PusherIds, PusherInit, PusherKind as RumaPusherKind},
43
            room::{Visibility, create_room},
44
            rtc::RtcTransport,
45
            session::get_login_types,
46
            user_directory::search_users,
47
        },
48
        events::{
49
            AnyInitialStateEvent, InitialStateEvent,
50
            room::{
51
                avatar::RoomAvatarEventContent, encryption::RoomEncryptionEventContent,
52
                message::MessageType,
53
            },
54
        },
55
        serde::Raw,
56
    },
57
    sliding_sync::Version as SdkSlidingSyncVersion,
58
    store::RoomLoadSettings as SdkRoomLoadSettings,
59
    sync::Notification,
60
    task_monitor::BackgroundTaskFailureReason,
61
};
62
use matrix_sdk_common::{
63
    SendOutsideWasm, SyncOutsideWasm, cross_process_lock::CrossProcessLockConfig, stream::StreamExt,
64
};
65
use matrix_sdk_ui::{
66
    notification_client::{
67
        NotificationClient as MatrixNotificationClient,
68
        NotificationProcessSetup as MatrixNotificationProcessSetup,
69
    },
70
    spaces::SpaceService as UISpaceService,
71
    unable_to_decrypt_hook::UtdHookManager,
72
};
73
use mime::Mime;
74
use oauth2::Scope;
75
use ruma::{
76
    OwnedDeviceId, OwnedMxcUri, OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
77
    api::{
78
        client::{
79
            alias::get_alias,
80
            discovery::get_authorization_server_metadata::v1::{
81
                AccountManagementActionData, DeviceDeleteData, DeviceViewData,
82
            },
83
            profile::{AvatarUrl, DisplayName},
84
            room::create_room::{RoomPowerLevelsContentOverride, v3::CreationContent},
85
            uiaa::{EmailUserIdentifier, UserIdentifier},
86
        },
87
        error::ErrorKind,
88
    },
89
    events::{
90
        AnyMessageLikeEventContent, AnySyncTimelineEvent,
91
        GlobalAccountDataEvent as RumaGlobalAccountDataEvent,
92
        RoomAccountDataEvent as RumaRoomAccountDataEvent,
93
        direct::DirectEventContent,
94
        fully_read::FullyReadEventContent,
95
        identity_server::IdentityServerEventContent,
96
        ignored_user_list::IgnoredUserListEventContent,
97
        key::verification::request::ToDeviceKeyVerificationRequestEvent,
98
        marked_unread::{MarkedUnreadEventContent, UnstableMarkedUnreadEventContent},
99
        push_rules::PushRulesEventContent,
100
        room::{
101
            history_visibility::RoomHistoryVisibilityEventContent,
102
            join_rules::{
103
                AllowRule as RumaAllowRule, JoinRule as RumaJoinRule, RoomJoinRulesEventContent,
104
            },
105
            message::{OriginalSyncRoomMessageEvent, Relation},
106
        },
107
        secret_storage::{
108
            default_key::SecretStorageDefaultKeyEventContent, key::SecretStorageKeyEventContent,
109
        },
110
        tag::TagEventContent,
111
    },
112
    push::{HttpPusherData as RumaHttpPusherData, PushFormat as RumaPushFormat},
113
    room::RoomType,
114
};
115
use serde::{Deserialize, Serialize};
116
use serde_json::{Value, json};
117
use tokio::sync::broadcast::error::RecvError;
118
use tracing::{debug, error, warn};
119
use url::Url;
120
121
use super::{
122
    room::{Room, room_info::RoomInfo},
123
    session_verification::SessionVerificationController,
124
};
125
use crate::{
126
    ClientError,
127
    authentication::{
128
        HomeserverLoginDetails, OAuthConfiguration, OAuthError, SsoError, SsoHandler,
129
    },
130
    client,
131
    encryption::Encryption,
132
    live_locations_observer::BeaconInfoUpdate,
133
    notification::{
134
        NotificationClient, NotificationEvent, NotificationItem, NotificationRoomInfo,
135
        NotificationSenderInfo,
136
    },
137
    notification_settings::NotificationSettings,
138
    qr_code::{GrantLoginWithQrCodeHandler, LoginWithQrCodeHandler},
139
    room::{RoomHistoryVisibility, RoomInfoListener, RoomSendQueueUpdate},
140
    room_directory_search::RoomDirectorySearch,
141
    room_preview::RoomPreview,
142
    ruma::{
143
        AccountDataEvent, AccountDataEventType, AuthData, InviteAvatars, MediaPreviewConfig,
144
        MediaPreviews, MediaSource, RoomAccountDataEvent, RoomAccountDataEventType,
145
    },
146
    runtime::get_runtime_handle,
147
    spaces::SpaceService,
148
    sync_service::{SyncService, SyncServiceBuilder},
149
    sync_v2::{SyncListenerV2, SyncResponseV2, SyncSettingsV2},
150
    task_handle::TaskHandle,
151
    utd::{UnableToDecryptDelegate, UtdHook},
152
    utils::AsyncRuntimeDropped,
153
};
154
155
#[derive(Clone, uniffi::Record)]
156
pub struct PusherIdentifiers {
157
    pub pushkey: String,
158
    pub app_id: String,
159
}
160
161
impl From<PusherIdentifiers> for PusherIds {
162
    fn from(value: PusherIdentifiers) -> Self {
163
        Self::new(value.pushkey, value.app_id)
164
    }
165
}
166
167
#[derive(Clone, uniffi::Record)]
168
pub struct HttpPusherData {
169
    pub url: String,
170
    pub format: Option<PushFormat>,
171
    pub default_payload: Option<String>,
172
}
173
174
#[derive(Clone, uniffi::Enum)]
175
pub enum PusherKind {
176
    Http { data: HttpPusherData },
177
    Email,
178
}
179
180
impl TryFrom<PusherKind> for RumaPusherKind {
181
    type Error = anyhow::Error;
182
183
    fn try_from(value: PusherKind) -> anyhow::Result<Self> {
184
        match value {
185
            PusherKind::Http { data } => {
186
                let mut ruma_data = RumaHttpPusherData::new(data.url);
187
                if let Some(payload) = data.default_payload {
188
                    let json: Value = serde_json::from_str(&payload)?;
189
                    ruma_data.data.insert("default_payload".to_owned(), json);
190
                }
191
                ruma_data.format = data.format.map(Into::into);
192
                Ok(Self::Http(ruma_data))
193
            }
194
            PusherKind::Email => {
195
                let ruma_data = EmailPusherData::new();
196
                Ok(Self::Email(ruma_data))
197
            }
198
        }
199
    }
200
}
201
202
#[derive(Clone, uniffi::Enum)]
203
pub enum PushFormat {
204
    EventIdOnly,
205
}
206
207
impl From<PushFormat> for RumaPushFormat {
208
    fn from(value: PushFormat) -> Self {
209
        match value {
210
            client::PushFormat::EventIdOnly => Self::EventIdOnly,
211
        }
212
    }
213
}
214
215
#[matrix_sdk_ffi_macros::export(callback_interface)]
216
pub trait ClientDelegate: SyncOutsideWasm + SendOutsideWasm {
217
    /// A callback invoked whenever the SDK runs into an unknown token error.
218
    fn did_receive_auth_error(&self, is_soft_logout: bool);
219
220
    /// A callback invoked when a background task registered with the client's
221
    /// task monitor encounters an error.
222
    ///
223
    /// Can default to an empty implementation, if the embedder doesn't care
224
    /// about handling background jobs errors.
225
    fn on_background_task_error_report(
226
        &self,
227
        task_name: String,
228
        error: BackgroundTaskFailureReason,
229
    );
230
}
231
232
#[matrix_sdk_ffi_macros::export(callback_interface)]
233
pub trait ClientSessionDelegate: SyncOutsideWasm + SendOutsideWasm {
234
    fn retrieve_session_from_keychain(&self, user_id: String) -> Result<Session, ClientError>;
235
    fn save_session_in_keychain(&self, session: Session);
236
}
237
238
#[matrix_sdk_ffi_macros::export(callback_interface)]
239
pub trait ProgressWatcher: SyncOutsideWasm + SendOutsideWasm {
240
    fn transmission_progress(&self, progress: TransmissionProgress);
241
}
242
243
/// A listener to the global (client-wide) update reporter of the send queue.
244
#[matrix_sdk_ffi_macros::export(callback_interface)]
245
pub trait SendQueueRoomUpdateListener: SyncOutsideWasm + SendOutsideWasm {
246
    /// Called every time the send queue emits an update for a given room.
247
    fn on_update(&self, room_id: String, update: RoomSendQueueUpdate);
248
}
249
250
/// A listener to the global (client-wide) error reporter of the send queue.
251
#[matrix_sdk_ffi_macros::export(callback_interface)]
252
pub trait SendQueueRoomErrorListener: SyncOutsideWasm + SendOutsideWasm {
253
    /// Called every time the send queue has ran into an error for a given room,
254
    /// which will disable the send queue for that particular room.
255
    fn on_error(&self, room_id: String, error: ClientError);
256
}
257
258
/// A listener for changes of global account data events.
259
#[matrix_sdk_ffi_macros::export(callback_interface)]
260
pub trait AccountDataListener: SyncOutsideWasm + SendOutsideWasm {
261
    /// Called when a global account data event has changed.
262
    fn on_change(&self, event: AccountDataEvent);
263
}
264
265
/// A listener for duplicate key upload errors triggered by requests to
266
/// /keys/upload.
267
#[matrix_sdk_ffi_macros::export(callback_interface)]
268
pub trait DuplicateKeyUploadErrorListener: SyncOutsideWasm + SendOutsideWasm {
269
    /// Called once when uploading keys fails.
270
    fn on_duplicate_key_upload_error(&self, message: Option<DuplicateOneTimeKeyErrorMessage>);
271
}
272
273
/// A listener for the current user's client-wide beacon_info updates.
274
#[matrix_sdk_ffi_macros::export(callback_interface)]
275
pub trait BeaconInfoListener: SyncOutsideWasm + SendOutsideWasm {
276
    /// Called whenever the current user's beacon_info changes in any room.
277
    fn on_update(&self, update: BeaconInfoUpdate);
278
}
279
280
/// Information about the old and new key that caused a duplicate key upload
281
/// error in /keys/upload.
282
#[derive(uniffi::Record)]
283
pub struct DuplicateOneTimeKeyErrorMessage {
284
    /// The previously uploaded one-time key, encoded as unpadded base64.
285
    pub old_key: String,
286
    /// The one-time key we attempted to upload, encoded as unpadded base64
287
    pub new_key: String,
288
}
289
290
impl From<matrix_sdk::encryption::DuplicateOneTimeKeyErrorMessage>
291
    for DuplicateOneTimeKeyErrorMessage
292
{
293
    fn from(value: matrix_sdk::encryption::DuplicateOneTimeKeyErrorMessage) -> Self {
294
        Self { old_key: value.old_key.to_base64(), new_key: value.new_key.to_base64() }
295
    }
296
}
297
298
/// A listener for changes of room account data events.
299
#[matrix_sdk_ffi_macros::export(callback_interface)]
300
pub trait RoomAccountDataListener: SyncOutsideWasm + SendOutsideWasm {
301
    /// Called when a room account data event was changed.
302
    fn on_change(&self, event: RoomAccountDataEvent, room_id: String);
303
}
304
305
/// A listener for notifications generated from sync responses.
306
///
307
/// This is called during sync for each event that triggers a notification
308
/// based on the user's push rules.
309
#[matrix_sdk_ffi_macros::export(callback_interface)]
310
pub trait SyncNotificationListener: SyncOutsideWasm + SendOutsideWasm {
311
    /// Called when a notifying event is received during sync.
312
    fn on_notification(&self, notification: NotificationItem, room_id: String);
313
}
314
315
#[derive(Clone, Copy, uniffi::Record)]
316
pub struct TransmissionProgress {
317
    pub current: u64,
318
    pub total: u64,
319
}
320
321
impl From<matrix_sdk::TransmissionProgress> for TransmissionProgress {
322
    fn from(value: matrix_sdk::TransmissionProgress) -> Self {
323
        Self {
324
            current: value.current.try_into().unwrap_or(u64::MAX),
325
            total: value.total.try_into().unwrap_or(u64::MAX),
326
        }
327
    }
328
}
329
330
#[derive(Clone, uniffi::Record)]
331
pub struct OpenIdToken {
332
    pub access_token: String,
333
    pub token_type: String,
334
    pub matrix_server_name: String,
335
    pub expires_in_seconds: u64,
336
}
337
338
impl From<request_openid_token::v3::Response> for OpenIdToken {
339
    fn from(value: request_openid_token::v3::Response) -> Self {
340
        Self {
341
            access_token: value.access_token,
342
            token_type: serde_json::to_value(&value.token_type)
343
                .ok()
344
                .and_then(|value| value.as_str().map(ToOwned::to_owned))
345
                .unwrap_or_else(|| format!("{:?}", value.token_type)),
346
            matrix_server_name: value.matrix_server_name.to_string(),
347
            expires_in_seconds: value.expires_in.as_secs(),
348
        }
349
    }
350
}
351
352
struct ClientDelegateData {
353
    /// The delegate itself, that will receive the callbacks.
354
    delegate: Arc<dyn ClientDelegate>,
355
356
    // The background task error listener task, that will forward errors occurring in background
357
    // jobs to the delegate.
358
    _background_error_listener_task: Arc<AbortOnDrop<()>>,
359
}
360
361
#[derive(uniffi::Object)]
362
pub struct Client {
363
    pub(crate) inner: AsyncRuntimeDropped<MatrixClient>,
364
365
    delegate_data: OnceLock<ClientDelegateData>,
366
367
    pub(crate) utd_hook_manager: OnceLock<Arc<UtdHookManager>>,
368
369
    session_verification_controller:
370
        Arc<tokio::sync::RwLock<Option<SessionVerificationController>>>,
371
372
    /// The path to the directory where the state store and the crypto store are
373
    /// located, if the `Client` instance has been built with a store (either
374
    /// SQLite or IndexedDB).
375
    #[cfg_attr(not(feature = "sqlite"), allow(unused))]
376
    store_path: Option<PathBuf>,
377
}
378
379
impl Client {
380
    pub async fn new(
381
        sdk_client: MatrixClient,
382
        session_delegate: Option<Arc<dyn ClientSessionDelegate>>,
383
        store_path: Option<PathBuf>,
384
    ) -> Result<Self, ClientError> {
385
        let session_verification_controller: Arc<
386
            tokio::sync::RwLock<Option<SessionVerificationController>>,
387
        > = Default::default();
388
        let controller = session_verification_controller.clone();
389
        sdk_client.add_event_handler(
390
            move |event: ToDeviceKeyVerificationRequestEvent| async move {
391
                if let Some(session_verification_controller) = &*controller.clone().read().await {
392
                    session_verification_controller
393
                        .process_incoming_verification_request(
394
                            &event.sender,
395
                            event.content.transaction_id,
396
                        )
397
                        .await;
398
                }
399
            },
400
        );
401
402
        let controller = session_verification_controller.clone();
403
        sdk_client.add_event_handler(move |event: OriginalSyncRoomMessageEvent| async move {
404
            if let MessageType::VerificationRequest(_) = &event.content.msgtype
405
                && let Some(session_verification_controller) = &*controller.clone().read().await
406
            {
407
                session_verification_controller
408
                    .process_incoming_verification_request(&event.sender, event.event_id)
409
                    .await;
410
            }
411
        });
412
413
        let store_mode = sdk_client.cross_process_lock_config();
414
415
        let client = Client {
416
            inner: AsyncRuntimeDropped::new(sdk_client.clone()),
417
            delegate_data: OnceLock::new(),
418
            utd_hook_manager: OnceLock::new(),
419
            session_verification_controller,
420
            store_path,
421
        };
422
423
        match store_mode {
424
            CrossProcessLockConfig::MultiProcess { holder_name } => {
425
                if session_delegate.is_none() {
426
                    return Err(anyhow::anyhow!(
427
                        "missing session delegates with multi-process lock configuration"
428
                    ))?;
429
                }
430
                client.inner.oauth().enable_cross_process_refresh_lock(holder_name.clone()).await?;
431
            }
432
            CrossProcessLockConfig::SingleProcess => {}
433
        }
434
435
        if let Some(session_delegate) = session_delegate {
436
            client.inner.set_session_callbacks(
437
                {
438
                    let session_delegate = session_delegate.clone();
439
                    Box::new(move |client| {
440
                        let session_delegate = session_delegate.clone();
441
                        let user_id = client.user_id().context("user isn't logged in")?;
442
                        Ok(Self::retrieve_session(session_delegate, user_id)?)
443
                    })
444
                },
445
                {
446
                    let session_delegate = session_delegate.clone();
447
                    Box::new(move |client| {
448
                        let session_delegate = session_delegate.clone();
449
                        Ok(Self::save_session(session_delegate, client)?)
450
                    })
451
                },
452
            )?;
453
        }
454
455
        Ok(client)
456
    }
457
}
458
459
#[matrix_sdk_ffi_macros::export]
460
impl Client {
461
    /// Perform database optimizations if any are available, i.e. vacuuming in
462
    /// SQLite.
463
    pub async fn optimize_stores(&self) -> Result<(), ClientError> {
464
        Ok(self.inner.optimize_stores().await?)
465
    }
466
467
    /// Pause the client for background suspension.
468
    ///
469
    /// This method:
470
    /// 1. Disables all send queues (prevents new message sends).
471
    /// 2. Pauses all database stores, waiting for in-flight operations and
472
    ///    releasing all connections and file locks.
473
    ///
474
    /// Call [`Client::resume()`] when the app returns to the foreground.
475
    ///
476
    /// # iOS
477
    ///
478
    /// Call this before the app is suspended to avoid `0xdead10cc` kills.
479
    /// Typically called from
480
    /// [`applicationDidEnterBackground`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/applicationdidenterbackground(_:))
481
    /// or an equivalent SwiftUI lifecycle event, *after* stopping the
482
    /// `matrix_sdk_ui::sync_service::SyncService`.
483
    pub async fn pause(&self) -> Result<(), ClientError> {
484
        Ok(self.inner.pause().await?)
485
    }
486
487
    /// Resume the client after a [`Client::pause()`].
488
    ///
489
    /// Re-acquires store resources and re-enables send queues.
490
    ///
491
    /// If your app stopped the `matrix_sdk_ui::sync_service::SyncService`
492
    /// before pausing, restart it separately as appropriate for your app
493
    /// lifecycle.
494
    pub async fn resume(&self) -> Result<(), ClientError> {
495
        Ok(self.inner.resume().await?)
496
    }
497
498
    /// Returns the sizes of the existing stores, if known.
499
    pub async fn get_store_sizes(&self) -> Result<StoreSizes, ClientError> {
500
        Ok(self.inner.get_store_sizes().await?.into())
501
    }
502
503
    /// Information about login options for the client's homeserver.
504
    pub async fn homeserver_login_details(&self) -> Arc<HomeserverLoginDetails> {
505
        let oauth = self.inner.oauth();
506
        let (supports_oauth_login, supported_oauth_prompts) = match oauth.server_metadata().await {
507
            Ok(metadata) => {
508
                let prompts =
509
                    metadata.prompt_values_supported.into_iter().map(Into::into).collect();
510
511
                (true, prompts)
512
            }
513
            Err(error) => {
514
                error!("Failed to fetch OAuth provider metadata: {error}");
515
                (false, Default::default())
516
            }
517
        };
518
519
        let login_types = self.inner.matrix_auth().get_login_types().await.ok();
520
        let supports_password_login = login_types
521
            .as_ref()
522
            .map(|login_types| {
523
                login_types.flows.iter().any(|login_type| {
524
                    matches!(login_type, get_login_types::v3::LoginType::Password(_))
525
                })
526
            })
527
            .unwrap_or(false);
528
        let supports_sso_login = login_types
529
            .as_ref()
530
            .map(|login_types| {
531
                login_types
532
                    .flows
533
                    .iter()
534
                    .any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Sso(_)))
535
            })
536
            .unwrap_or(false);
537
        let sliding_sync_version = self.sliding_sync_version();
538
539
        Arc::new(HomeserverLoginDetails {
540
            url: self.homeserver(),
541
            sliding_sync_version,
542
            supports_oauth_login,
543
            supported_oauth_prompts,
544
            supports_sso_login,
545
            supports_password_login,
546
        })
547
    }
548
549
    /// Login using a username and password.
550
    pub async fn login(
551
        &self,
552
        username: String,
553
        password: String,
554
        initial_device_name: Option<String>,
555
        device_id: Option<String>,
556
    ) -> Result<(), ClientError> {
557
        let mut builder = self.inner.matrix_auth().login_username(&username, &password);
558
559
        if let Some(initial_device_name) = initial_device_name.as_ref() {
560
            builder = builder.initial_device_display_name(initial_device_name);
561
        }
562
563
        if let Some(device_id) = device_id.as_ref() {
564
            builder = builder.device_id(device_id);
565
        }
566
567
        builder.send().await?;
568
569
        Ok(())
570
    }
571
572
    /// Login using JWT
573
    /// This is an implementation of the custom_login https://docs.rs/matrix-sdk/latest/matrix_sdk/matrix_auth/struct.MatrixAuth.html#method.login_custom
574
    /// For more information on logging in with JWT: https://element-hq.github.io/synapse/latest/jwt.html
575
    pub async fn custom_login_with_jwt(
576
        &self,
577
        jwt: String,
578
        initial_device_name: Option<String>,
579
        device_id: Option<String>,
580
    ) -> Result<(), ClientError> {
581
        let data = json!({ "token": jwt }).as_object().unwrap().clone();
582
583
        let mut builder = self.inner.matrix_auth().login_custom("org.matrix.login.jwt", data)?;
584
585
        if let Some(initial_device_name) = initial_device_name.as_ref() {
586
            builder = builder.initial_device_display_name(initial_device_name);
587
        }
588
589
        if let Some(device_id) = device_id.as_ref() {
590
            builder = builder.device_id(device_id);
591
        }
592
593
        builder.send().await?;
594
595
        Ok(())
596
    }
597
598
    /// Login using an email and password.
599
    pub async fn login_with_email(
600
        &self,
601
        email: String,
602
        password: String,
603
        initial_device_name: Option<String>,
604
        device_id: Option<String>,
605
    ) -> Result<(), ClientError> {
606
        let mut builder = self
607
            .inner
608
            .matrix_auth()
609
            .login_identifier(UserIdentifier::Email(EmailUserIdentifier::new(email)), &password);
610
611
        if let Some(initial_device_name) = initial_device_name.as_ref() {
612
            builder = builder.initial_device_display_name(initial_device_name);
613
        }
614
615
        if let Some(device_id) = device_id.as_ref() {
616
            builder = builder.device_id(device_id);
617
        }
618
619
        builder.send().await?;
620
621
        Ok(())
622
    }
623
624
    /// Returns a handler to start the SSO login process.
625
    pub(crate) async fn start_sso_login(
626
        self: &Arc<Self>,
627
        redirect_url: String,
628
        idp_id: Option<String>,
629
    ) -> Result<Arc<SsoHandler>, SsoError> {
630
        let auth = self.inner.matrix_auth();
631
        let url = auth
632
            .get_sso_login_url(redirect_url.as_str(), idp_id.as_deref())
633
            .await
634
            .map_err(|e| SsoError::Generic { message: e.to_string() })?;
635
        Ok(Arc::new(SsoHandler { client: Arc::clone(self), url }))
636
    }
637
638
    /// Requests the URL needed for opening a web view using OAuth. Once the web
639
    /// view has succeeded, call `login_with_oauth_callback` with the callback
640
    /// it returns. If a failure occurs and a callback isn't available, make
641
    /// sure to call `abort_oauth_auth` to inform the client of this.
642
    ///
643
    /// # Arguments
644
    ///
645
    /// * `oauth_configuration` - The configuration used to load the credentials
646
    ///   of the client if it is already registered with the authorization
647
    ///   server, or register the client and store its credentials if it isn't.
648
    ///
649
    /// * `prompt` - The desired user experience in the web UI. No value means
650
    ///   that the user wishes to login into an existing account, and a value of
651
    ///   `Create` means that the user wishes to register a new account.
652
    ///
653
    /// * `login_hint` - A generic login hint that an identity provider can use
654
    ///   to pre-fill the login form. The format of this hint is not restricted
655
    ///   by the spec as external providers all have their own way to handle the hint.
656
    ///   However, it should be noted that when providing a user ID as a hint
657
    ///   for MAS (with no upstream provider), then the format to use is defined
658
    ///   by [MSC4198]: https://github.com/matrix-org/matrix-spec-proposals/pull/4198
659
    ///
660
    /// * `device_id` - The unique ID that will be associated with the session.
661
    ///   If not set, a random one will be generated. It can be an existing
662
    ///   device ID from a previous login call. Note that this should be done
663
    ///   only if the client also holds the corresponding encryption keys.
664
    ///
665
    /// * `additional_scopes` - Additional scopes to request from the
666
    ///   authorization server, e.g. "urn:matrix:client:com.example.msc9999.foo".
667
    ///   The scopes for API access and the device ID according to the
668
    ///   [specification](https://spec.matrix.org/v1.15/client-server-api/#allocated-scope-tokens)
669
    ///   are always requested.
670
    pub async fn url_for_oauth(
671
        &self,
672
        oauth_configuration: &OAuthConfiguration,
673
        prompt: Option<OAuthPrompt>,
674
        login_hint: Option<String>,
675
        device_id: Option<String>,
676
        additional_scopes: Option<Vec<String>>,
677
    ) -> Result<Arc<OAuthAuthorizationData>, OAuthError> {
678
        let registration_data = oauth_configuration.registration_data()?;
679
        let redirect_uri = oauth_configuration.redirect_uri()?;
680
681
        let device_id = device_id.map(OwnedDeviceId::from);
682
683
        let additional_scopes =
684
            additional_scopes.map(|scopes| scopes.into_iter().map(Scope::new).collect::<Vec<_>>());
685
686
        let mut url_builder = self.inner.oauth().login(
687
            redirect_uri,
688
            device_id,
689
            Some(registration_data),
690
            additional_scopes,
691
        );
692
693
        if let Some(prompt) = prompt {
694
            url_builder = url_builder.prompt(vec![prompt.into()]);
695
        }
696
        if let Some(login_hint) = login_hint {
697
            url_builder = url_builder.login_hint(login_hint);
698
        }
699
700
        let data = url_builder.build().await?;
701
702
        Ok(Arc::new(data))
703
    }
704
705
    /// Aborts an existing OAuth login operation that might have been cancelled,
706
    /// failed etc.
707
    pub async fn abort_oauth_auth(&self, authorization_data: Arc<OAuthAuthorizationData>) {
708
        self.inner.oauth().abort_login(&authorization_data.state).await;
709
    }
710
711
    /// Completes the OAuth login process.
712
    pub async fn login_with_oauth_callback(&self, callback_url: String) -> Result<(), OAuthError> {
713
        let url = Url::parse(&callback_url).or(Err(OAuthError::CallbackUrlInvalid))?;
714
715
        self.inner.oauth().finish_login(url.into()).await?;
716
717
        Ok(())
718
    }
719
720
    /// Create a handler for requesting an existing device to grant login to
721
    /// this device by way of a QR code.
722
    ///
723
    /// # Arguments
724
    ///
725
    /// * `oauth_configuration` - The data to restore or register the client
726
    ///   with the server.
727
    pub fn new_login_with_qr_code_handler(
728
        self: Arc<Self>,
729
        oauth_configuration: OAuthConfiguration,
730
    ) -> LoginWithQrCodeHandler {
731
        LoginWithQrCodeHandler::new(self.inner.oauth(), oauth_configuration)
732
    }
733
734
    /// Create a handler for granting login from this device to a new device by
735
    /// way of a QR code.
736
    pub fn new_grant_login_with_qr_code_handler(self: Arc<Self>) -> GrantLoginWithQrCodeHandler {
737
        GrantLoginWithQrCodeHandler::new(self.inner.oauth())
738
    }
739
740
    /// Restores the client from a `Session`.
741
    ///
742
    /// It reloads the entire set of rooms from the previous session.
743
    ///
744
    /// If you want to control the amount of rooms to reloads, check
745
    /// [`Client::restore_session_with`].
746
    pub async fn restore_session(&self, session: Session) -> Result<(), ClientError> {
747
        self.restore_session_with(session, RoomLoadSettings::All).await
748
    }
749
750
    /// Restores the client from a `Session`.
751
    ///
752
    /// It reloads a set of rooms controlled by [`RoomLoadSettings`].
753
    pub async fn restore_session_with(
754
        &self,
755
        session: Session,
756
        room_load_settings: RoomLoadSettings,
757
    ) -> Result<(), ClientError> {
758
        let sliding_sync_version = session.sliding_sync_version.clone();
759
        let auth_session: AuthSession = session.try_into()?;
760
761
        self.inner
762
            .restore_session_with(
763
                auth_session,
764
                room_load_settings
765
                    .try_into()
766
                    .map_err(|error| ClientError::from_str(error, None))?,
767
            )
768
            .await?;
769
        self.inner.set_sliding_sync_version(sliding_sync_version.try_into()?);
770
771
        Ok(())
772
    }
773
774
    /// Enables or disables all the room send queues at once.
775
    ///
776
    /// When connectivity is lost on a device, it is recommended to disable the
777
    /// room sending queues.
778
    ///
779
    /// This can be controlled for individual rooms, using
780
    /// [`Room::enable_send_queue`].
781
    pub async fn enable_all_send_queues(&self, enable: bool) {
782
        self.inner.send_queue().set_enabled(enable).await;
783
    }
784
785
    /// Enables or disables progress reporting for media uploads in the send
786
    /// queue.
787
    pub fn enable_send_queue_upload_progress(&self, enable: bool) {
788
        self.inner.send_queue().enable_upload_progress(enable);
789
    }
790
791
    /// Subscribe to the global send queue update reporter, at the
792
    /// client-wide level.
793
    ///
794
    /// The given listener will be immediately called with
795
    /// `RoomSendQueueUpdate::NewLocalEvent` for each local echo existing in
796
    /// the queue.
797
    pub async fn subscribe_to_send_queue_updates(
798
        &self,
799
        listener: Box<dyn SendQueueRoomUpdateListener>,
800
    ) -> Result<Arc<TaskHandle>, ClientError> {
801
        let q = self.inner.send_queue();
802
        let local_echoes = q.local_echoes().await?;
803
        let mut subscriber = q.subscribe();
804
805
        for (room_id, local_echoes) in local_echoes {
806
            for local_echo in local_echoes {
807
                listener.on_update(
808
                    room_id.clone().into(),
809
                    RoomSendQueueUpdate::NewLocalEvent {
810
                        transaction_id: local_echo.transaction_id.into(),
811
                    },
812
                );
813
            }
814
        }
815
816
        Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
817
            loop {
818
                match subscriber.recv().await {
819
                    Ok(update) => {
820
                        let room_id = update.room_id.to_string();
821
                        match update.update.try_into() {
822
                            Ok(update) => listener.on_update(room_id, update),
823
                            Err(err) => error!("error when converting send queue update: {err}"),
824
                        }
825
                    }
826
                    Err(err) => {
827
                        error!("error when listening to the send queue update reporter: {err}");
828
                    }
829
                }
830
            }
831
        }))))
832
    }
833
834
    /// Subscribe to the global enablement status of the send queue, at the
835
    /// client-wide level.
836
    ///
837
    /// The given listener will be immediately called with the initial value of
838
    /// the enablement status.
839
    pub fn subscribe_to_send_queue_status(
840
        &self,
841
        listener: Box<dyn SendQueueRoomErrorListener>,
842
    ) -> Arc<TaskHandle> {
843
        let q = self.inner.send_queue();
844
        let mut subscriber = q.subscribe_errors();
845
846
        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
847
            // Respawn tasks for rooms that had unsent events. At this point we've just
848
            // created the subscriber, so it'll be notified about errors.
849
            q.respawn_tasks_for_rooms_with_unsent_requests().await;
850
851
            loop {
852
                match subscriber.recv().await {
853
                    Ok(report) => listener
854
                        .on_error(report.room_id.to_string(), ClientError::from_err(report.error)),
855
                    Err(err) => {
856
                        error!("error when listening to the send queue error reporter: {err}");
857
                    }
858
                }
859
            }
860
        })))
861
    }
862
863
    /// Subscribe to duplicate key upload errors triggered by requests to
864
    /// /keys/upload.
865
    pub fn subscribe_to_duplicate_key_upload_errors(
866
        &self,
867
        listener: Box<dyn DuplicateKeyUploadErrorListener>,
868
    ) -> Arc<TaskHandle> {
869
        let mut subscriber = self.inner.subscribe_to_duplicate_key_upload_errors();
870
871
        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
872
            loop {
873
                match subscriber.recv().await {
874
                    Ok(message) => {
875
                        listener.on_duplicate_key_upload_error(message.map(|m| m.into()))
876
                    }
877
                    Err(err) => {
878
                        error!("error when listening to key upload errors: {err}");
879
                    }
880
                }
881
            }
882
        })))
883
    }
884
885
    /// Subscribe to beacon_info updates for the current user across all rooms.
886
    ///
887
    /// The listener is only called for new matching updates; there is no
888
    /// initial replay.
889
    pub fn subscribe_to_own_beacon_info_updates(
890
        &self,
891
        listener: Box<dyn BeaconInfoListener>,
892
    ) -> Result<Arc<TaskHandle>, ClientError> {
893
        let stream = self.inner.observe_own_beacon_info_updates()?;
894
895
        Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
896
            pin_mut!(stream);
897
898
            while let Some(update) = stream.next().await {
899
                listener.on_update(update.into());
900
            }
901
        }))))
902
    }
903
904
    /// Subscribe to updates of global account data events.
905
    ///
906
    /// Be careful that only the most recent value can be observed. Subscribers
907
    /// are notified when a new value is sent, but there is no guarantee that
908
    /// they will see all values.
909
    pub fn observe_account_data_event(
910
        &self,
911
        event_type: AccountDataEventType,
912
        listener: Box<dyn AccountDataListener>,
913
    ) -> Arc<TaskHandle> {
914
        macro_rules! observe {
915
            ($t:ty, $cb: expr) => {{
916
                // Using an Arc here is mandatory or else the subscriber will never trigger
917
                let observer =
918
                    Arc::new(self.inner.observe_events::<RumaGlobalAccountDataEvent<$t>, ()>());
919
920
                Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
921
                    let mut subscriber = observer.subscribe();
922
                    loop {
923
                        if let Some(next) = subscriber.next().await {
924
                            $cb(next.0);
925
                        }
926
                    }
927
                })))
928
            }};
929
930
            ($t:ty) => {{
931
                observe!($t, |event: RumaGlobalAccountDataEvent<$t>| {
932
                    listener.on_change(event.into());
933
                })
934
            }};
935
        }
936
937
        match event_type {
938
            AccountDataEventType::Direct => {
939
                observe!(DirectEventContent)
940
            }
941
            AccountDataEventType::IdentityServer => {
942
                observe!(IdentityServerEventContent)
943
            }
944
            AccountDataEventType::IgnoredUserList => {
945
                observe!(IgnoredUserListEventContent)
946
            }
947
            AccountDataEventType::PushRules => {
948
                observe!(PushRulesEventContent, |event: RumaGlobalAccountDataEvent<
949
                    PushRulesEventContent,
950
                >| {
951
                    if let Ok(event) = event.try_into() {
952
                        listener.on_change(event);
953
                    }
954
                })
955
            }
956
            AccountDataEventType::SecretStorageDefaultKey => {
957
                observe!(SecretStorageDefaultKeyEventContent)
958
            }
959
            AccountDataEventType::SecretStorageKey { key_id } => {
960
                observe!(SecretStorageKeyEventContent, |event: RumaGlobalAccountDataEvent<
961
                    SecretStorageKeyEventContent,
962
                >| {
963
                    if event.content.key_id != key_id {
964
                        return;
965
                    }
966
                    if let Ok(event) = event.try_into() {
967
                        listener.on_change(event);
968
                    }
969
                })
970
            }
971
        }
972
    }
973
974
    /// Subscribe to updates of room account data events.
975
    ///
976
    /// Be careful that only the most recent value can be observed. Subscribers
977
    /// are notified when a new value is sent, but there is no guarantee that
978
    /// they will see all values.
979
    pub fn observe_room_account_data_event(
980
        &self,
981
        room_id: String,
982
        event_type: RoomAccountDataEventType,
983
        listener: Box<dyn RoomAccountDataListener>,
984
    ) -> Result<Arc<TaskHandle>, ClientError> {
985
        macro_rules! observe {
986
            ($t:ty, $cb: expr) => {{
987
                // Using an Arc here is mandatory or else the subscriber will never trigger
988
                let observer =
989
                    Arc::new(self.inner.observe_room_events::<RumaRoomAccountDataEvent<$t>, ()>(
990
                        &RoomId::parse(&room_id)?,
991
                    ));
992
993
                Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
994
                    let mut subscriber = observer.subscribe();
995
                    loop {
996
                        if let Some(next) = subscriber.next().await {
997
                            $cb(next.0);
998
                        }
999
                    }
1000
                }))))
1001
            }};
1002
1003
            ($t:ty) => {{
1004
                observe!($t, |event: RumaRoomAccountDataEvent<$t>| {
1005
                    listener.on_change(event.into(), room_id.clone());
1006
                })
1007
            }};
1008
        }
1009
1010
        match event_type {
1011
            RoomAccountDataEventType::FullyRead => {
1012
                observe!(FullyReadEventContent)
1013
            }
1014
            RoomAccountDataEventType::MarkedUnread => {
1015
                observe!(MarkedUnreadEventContent)
1016
            }
1017
            RoomAccountDataEventType::Tag => {
1018
                observe!(TagEventContent, |event: RumaRoomAccountDataEvent<TagEventContent>| {
1019
                    if let Ok(event) = event.try_into() {
1020
                        listener.on_change(event, room_id.clone());
1021
                    }
1022
                })
1023
            }
1024
            RoomAccountDataEventType::UnstableMarkedUnread => {
1025
                observe!(UnstableMarkedUnreadEventContent)
1026
            }
1027
        }
1028
    }
1029
1030
    /// Register a handler for notifications generated from sync responses.
1031
    ///
1032
    /// The handler will be called during sync for each event that triggers
1033
    /// a notification based on the user's push rules.
1034
    ///
1035
    /// The handler receives:
1036
    /// - The notification with push actions and event data
1037
    /// - The room ID where the notification occurred
1038
    ///
1039
    /// This is useful for implementing custom notification logic, such as
1040
    /// displaying local notifications or updating notification badges.
1041
    pub async fn register_notification_handler(&self, listener: Box<dyn SyncNotificationListener>) {
1042
        let listener = Arc::new(listener);
1043
        self.inner
1044
            .register_notification_handler(move |notification, room, _client| {
1045
                notification_handler(notification, room, listener.clone())
1046
            })
1047
            .await;
1048
    }
1049
1050
    /// Allows generic GET requests to be made through the SDK's internal HTTP
1051
    /// client. This is useful when the caller's native HTTP client wouldn't
1052
    /// have the same configuration (such as certificates, proxies, etc.) This
1053
    /// method returns the raw bytes of the response, so that any kind of
1054
    /// resource can be fetched including images, files, etc.
1055
    ///
1056
    /// Note: When an HTTP error occurs, the error response can be found in the
1057
    /// `ClientError::Generic`'s `details` field.
1058
    pub async fn get_url(&self, url: String) -> Result<Vec<u8>, ClientError> {
1059
        let response = self.inner.http_client().get(url).send().await?;
1060
        if response.status().is_success() {
1061
            Ok(response.bytes().await?.into())
1062
        } else {
1063
            Err(ClientError::Generic {
1064
                msg: response.status().to_string(),
1065
                details: response.text().await.ok(),
1066
            })
1067
        }
1068
    }
1069
1070
    /// Empty the server version and unstable features cache.
1071
    ///
1072
    /// Since the SDK caches the supported versions, it's possible to have a
1073
    /// stale entry in the cache. This functions makes it possible to force
1074
    /// reset it.
1075
    pub async fn reset_supported_versions(&self) -> Result<(), ClientError> {
1076
        Ok(self.inner.reset_supported_versions().await?)
1077
    }
1078
1079
    /// Empty the well-known cache.
1080
    ///
1081
    /// Since the SDK caches the well-known, it's possible to have a stale
1082
    /// entry in the cache. This functions makes it possible to force reset
1083
    /// it.
1084
    pub async fn reset_well_known(&self) -> Result<(), ClientError> {
1085
        Ok(self.inner.reset_well_known().await?)
1086
    }
1087
1088
    /// Retrieves a media file from the media source
1089
    ///
1090
    /// Not available on Wasm platforms, due to lack of accessible file system.
1091
    pub async fn get_media_file(
1092
        &self,
1093
        media_source: Arc<MediaSource>,
1094
        filename: Option<String>,
1095
        mime_type: String,
1096
        use_cache: bool,
1097
        temp_dir: Option<String>,
1098
    ) -> Result<Arc<MediaFileHandle>, ClientError> {
1099
        #[cfg(not(target_family = "wasm"))]
1100
        {
1101
            let source = (*media_source).clone();
1102
            let mime_type: mime::Mime = mime_type.parse()?;
1103
1104
            let handle = self
1105
                .inner
1106
                .media()
1107
                .get_media_file(
1108
                    &MediaRequestParameters {
1109
                        source: source.media_source,
1110
                        format: MediaFormat::File,
1111
                    },
1112
                    filename,
1113
                    &mime_type,
1114
                    use_cache,
1115
                    temp_dir,
1116
                )
1117
                .await?;
1118
1119
            Ok(Arc::new(MediaFileHandle::new(handle)))
1120
        }
1121
1122
        /// MediaFileHandle uses SdkMediaFileHandle which requires an
1123
        /// intermediate TempFile which is not available on wasm
1124
        /// platforms due to lack of an accessible file system.
1125
        #[cfg(target_family = "wasm")]
1126
        Err(ClientError::Generic {
1127
            msg: "get_media_file is not supported on wasm platforms".to_owned(),
1128
            details: None,
1129
        })
1130
    }
1131
1132
    pub async fn set_display_name(&self, name: String) -> Result<(), ClientError> {
1133
        #[cfg(not(target_family = "wasm"))]
1134
        {
1135
            self.inner
1136
                .account()
1137
                .set_display_name(Some(name.as_str()))
1138
                .await
1139
                .context("Unable to set display name")?;
1140
        }
1141
1142
        #[cfg(target_family = "wasm")]
1143
        {
1144
            self.inner.account().set_display_name(Some(name.as_str())).await.map_err(|e| {
1145
                ClientError::Generic {
1146
                    msg: "Unable to set display name".to_owned(),
1147
                    details: Some(e.to_string()),
1148
                }
1149
            })?;
1150
        }
1151
1152
        Ok(())
1153
    }
1154
1155
    /// The sliding sync version.
1156
    pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
1157
        self.inner.sliding_sync_version().into()
1158
    }
1159
1160
    /// Find all sliding sync versions that are available.
1161
    ///
1162
    /// Be careful: This method may hit the store and will send new requests for
1163
    /// each call. It can be costly to call it repeatedly.
1164
    ///
1165
    /// If `.well-known` or `/versions` is unreachable, it will simply move
1166
    /// potential sliding sync versions aside. No error will be reported.
1167
    pub async fn available_sliding_sync_versions(&self) -> Vec<SlidingSyncVersion> {
1168
        self.inner.available_sliding_sync_versions().await.into_iter().map(Into::into).collect()
1169
    }
1170
1171
    /// Sets the [ClientDelegate] which will inform about authentication errors.
1172
    /// Returns an error if the delegate was already set.
1173
    pub fn set_delegate(
1174
        self: Arc<Self>,
1175
        delegate: Option<Box<dyn ClientDelegate>>,
1176
    ) -> Result<Option<Arc<TaskHandle>>, ClientError> {
1177
        if self.delegate_data.get().is_some() {
1178
            return Err(ClientError::Generic {
1179
                msg: "Delegate already initialized".to_owned(),
1180
                details: None,
1181
            });
1182
        }
1183
1184
        let handle = delegate.map(|delegate| {
1185
            let mut session_change_receiver = self.inner.subscribe_to_session_changes();
1186
            let client_clone = self.clone();
1187
            let session_change_task = get_runtime_handle().spawn(async move {
1188
                loop {
1189
                    match session_change_receiver.recv().await {
1190
                        Ok(session_change) => client_clone.process_session_change(session_change),
1191
                        Err(receive_error) => {
1192
                            if let RecvError::Closed = receive_error {
1193
                                break;
1194
                            }
1195
                        }
1196
                    }
1197
                }
1198
            });
1199
1200
            let delegate: Arc<dyn ClientDelegate> = delegate.into();
1201
1202
            let client = self.inner.clone();
1203
            let delegate_clone = delegate.clone();
1204
            let task = Arc::new(AbortOnDrop::new(get_runtime_handle().spawn(async move {
1205
                let mut receiver = client.task_monitor().subscribe();
1206
                while let Ok(error) = receiver.recv().await {
1207
                    delegate_clone.on_background_task_error_report(error.task.name, error.reason);
1208
                }
1209
            })));
1210
1211
            let delegate_data =
1212
                ClientDelegateData { delegate, _background_error_listener_task: task };
1213
1214
            self.delegate_data.get_or_init(|| delegate_data);
1215
1216
            Arc::new(TaskHandle::new(session_change_task))
1217
        });
1218
1219
        Ok(handle)
1220
    }
1221
1222
    /// Sets the [`UnableToDecryptDelegate`] which will inform about UTDs.
1223
    /// Returns an error if the delegate was already set.
1224
    pub async fn set_utd_delegate(
1225
        self: Arc<Self>,
1226
        utd_delegate: Box<dyn UnableToDecryptDelegate>,
1227
    ) -> Result<(), ClientError> {
1228
        if self.utd_hook_manager.get().is_some() {
1229
            return Err(ClientError::Generic {
1230
                msg: "UTD delegate already initialized".to_owned(),
1231
                details: None,
1232
            });
1233
        }
1234
1235
        // UTDs detected before this duration may be reclassified as "late decryption"
1236
        // events (or discarded, if they get decrypted fast enough).
1237
        const UTD_HOOK_GRACE_PERIOD: Duration = Duration::from_secs(60);
1238
1239
        let mut utd_hook_manager = UtdHookManager::new(
1240
            Arc::new(UtdHook { delegate: utd_delegate.into() }),
1241
            (*self.inner).clone(),
1242
        )
1243
        .with_max_delay(UTD_HOOK_GRACE_PERIOD);
1244
1245
        if let Err(e) = utd_hook_manager.reload_from_store().await {
1246
            error!("Unable to reload UTD hook data from data store: {e}");
1247
            // Carry on with the setup anyway; we shouldn't fail setup just
1248
            // because the UTD hook failed to load its data.
1249
        }
1250
1251
        self.utd_hook_manager.get_or_init(|| Arc::new(utd_hook_manager));
1252
1253
        Ok(())
1254
    }
1255
1256
    pub fn session(&self) -> Result<Session, ClientError> {
1257
        Self::session_inner((*self.inner).clone())
1258
    }
1259
1260
    pub async fn account_url(
1261
        &self,
1262
        action: Option<AccountManagementAction>,
1263
    ) -> Result<Option<String>, ClientError> {
1264
        if !matches!(self.inner.auth_api(), Some(AuthApi::OAuth(..))) {
1265
            return Ok(None);
1266
        }
1267
1268
        let server_metadata = match self.inner.oauth().cached_server_metadata().await {
1269
            Ok(server_metadata) => server_metadata,
1270
            Err(e) => {
1271
                error!("Failed retrieving cached server metadata: {e}");
1272
                return Err(SdkOAuthError::from(e).into());
1273
            }
1274
        };
1275
1276
        Ok(if let Some(action) = &action {
1277
            server_metadata.account_management_url_with_action(action.into())
1278
        } else {
1279
            server_metadata.account_management_uri
1280
        }
1281
        .map(Into::into))
1282
    }
1283
1284
    pub fn user_id(&self) -> Result<String, ClientError> {
1285
        let user_id = self.inner.user_id().context("No User ID found")?;
1286
        Ok(user_id.to_string())
1287
    }
1288
1289
    /// The server name part of the current user ID
1290
    pub fn user_id_server_name(&self) -> Result<String, ClientError> {
1291
        let user_id = self.inner.user_id().context("No User ID found")?;
1292
        Ok(user_id.server_name().to_string())
1293
    }
1294
1295
    pub async fn display_name(&self) -> Result<String, ClientError> {
1296
        let display_name =
1297
            self.inner.account().get_display_name().await?.context("No User ID found")?;
1298
        Ok(display_name)
1299
    }
1300
1301
    pub async fn request_openid_token(&self) -> Result<OpenIdToken, ClientError> {
1302
        Ok(self.inner.account().request_openid_token().await?.into())
1303
    }
1304
1305
    pub async fn upload_avatar(&self, mime_type: String, data: Vec<u8>) -> Result<(), ClientError> {
1306
        let mime: Mime = mime_type.parse()?;
1307
        self.inner.account().upload_avatar(&mime, data).await?;
1308
        Ok(())
1309
    }
1310
1311
    /// Updates the user's avatar using the provided MXC url.
1312
    pub async fn set_avatar_url(&self, url: String) -> Result<(), ClientError> {
1313
        // MxcUri can't just be instantiated, serde deserialization seems to be the only
1314
        // way
1315
        let mxc = serde_json::from_str::<OwnedMxcUri>(&url)?;
1316
        // Validate the newly generated MxcUri
1317
        mxc.validate().map_err(ClientError::from_err)?;
1318
1319
        self.inner.account().set_avatar_url(Some(&mxc)).await?;
1320
        Ok(())
1321
    }
1322
1323
    pub async fn remove_avatar(&self) -> Result<(), ClientError> {
1324
        self.inner.account().set_avatar_url(None).await?;
1325
        Ok(())
1326
    }
1327
1328
    /// Sends a request to retrieve the avatar URL. Will fill the cache used by
1329
    /// [`Self::cached_avatar_url`] on success.
1330
    pub async fn avatar_url(&self) -> Result<Option<String>, ClientError> {
1331
        let avatar_url = self.inner.account().get_avatar_url().await?;
1332
1333
        Ok(avatar_url.map(|u| u.to_string()))
1334
    }
1335
1336
    /// Retrieves an avatar cached from a previous call to [`Self::avatar_url`].
1337
    pub async fn cached_avatar_url(&self) -> Result<Option<String>, ClientError> {
1338
        Ok(self.inner.account().get_cached_avatar_url().await?.map(Into::into))
1339
    }
1340
1341
    pub fn device_id(&self) -> Result<String, ClientError> {
1342
        let device_id = self.inner.device_id().context("No Device ID found")?;
1343
        Ok(device_id.to_string())
1344
    }
1345
1346
    pub async fn create_room(&self, request: CreateRoomParameters) -> Result<String, ClientError> {
1347
        let response = self.inner.create_room(request.try_into()?).await?;
1348
        Ok(String::from(response.room_id()))
1349
    }
1350
1351
    /// Get the content of the event of the given type out of the account data
1352
    /// store.
1353
    ///
1354
    /// It will be returned as a JSON string.
1355
    pub async fn account_data(&self, event_type: String) -> Result<Option<String>, ClientError> {
1356
        let event = self.inner.account().account_data_raw(event_type.into()).await?;
1357
        Ok(event.map(|e| e.json().get().to_owned()))
1358
    }
1359
1360
    /// Set the given account data content for the given event type.
1361
    ///
1362
    /// It should be supplied as a JSON string.
1363
    pub async fn set_account_data(
1364
        &self,
1365
        event_type: String,
1366
        content: String,
1367
    ) -> Result<(), ClientError> {
1368
        let raw_content = Raw::from_json_string(content)?;
1369
        self.inner.account().set_account_data_raw(event_type.into(), raw_content).await?;
1370
        Ok(())
1371
    }
1372
1373
    pub async fn upload_media(
1374
        &self,
1375
        mime_type: String,
1376
        data: Vec<u8>,
1377
        progress_watcher: Option<Box<dyn ProgressWatcher>>,
1378
    ) -> Result<String, ClientError> {
1379
        let mime_type: mime::Mime = mime_type.parse().context("Parsing mime type")?;
1380
        let request = self.inner.media().upload(&mime_type, data, None);
1381
1382
        if let Some(progress_watcher) = progress_watcher {
1383
            let mut subscriber = request.subscribe_to_send_progress();
1384
            get_runtime_handle().spawn(async move {
1385
                while let Some(progress) = subscriber.next().await {
1386
                    progress_watcher.transmission_progress(progress.into());
1387
                }
1388
            });
1389
        }
1390
1391
        let response = request.await?;
1392
1393
        Ok(String::from(response.content_uri))
1394
    }
1395
1396
    pub async fn get_media_content(
1397
        &self,
1398
        media_source: Arc<MediaSource>,
1399
    ) -> Result<Vec<u8>, ClientError> {
1400
        let source = (*media_source).clone().media_source;
1401
1402
        debug!(?source, "requesting media file");
1403
        Ok(self
1404
            .inner
1405
            .media()
1406
            .get_media_content(&MediaRequestParameters { source, format: MediaFormat::File }, true)
1407
            .await?)
1408
    }
1409
1410
    pub async fn get_media_thumbnail(
1411
        &self,
1412
        media_source: Arc<MediaSource>,
1413
        width: u64,
1414
        height: u64,
1415
    ) -> Result<Vec<u8>, ClientError> {
1416
        let source = (*media_source).clone().media_source;
1417
1418
        debug!(?source, width, height, "requesting media thumbnail");
1419
        Ok(self
1420
            .inner
1421
            .media()
1422
            .get_media_content(
1423
                &MediaRequestParameters {
1424
                    source,
1425
                    format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(
1426
                        UInt::new(width).unwrap(),
1427
                        UInt::new(height).unwrap(),
1428
                    )),
1429
                },
1430
                true,
1431
            )
1432
            .await?)
1433
    }
1434
1435
    pub async fn get_session_verification_controller(
1436
        &self,
1437
    ) -> Result<Arc<SessionVerificationController>, ClientError> {
1438
        if let Some(session_verification_controller) =
1439
            &*self.session_verification_controller.read().await
1440
        {
1441
            return Ok(Arc::new(session_verification_controller.clone()));
1442
        }
1443
        let user_id = self.inner.user_id().context("Failed retrieving current user_id")?;
1444
        let user_identity = self
1445
            .inner
1446
            .encryption()
1447
            .get_user_identity(user_id)
1448
            .await?
1449
            .context("Failed retrieving user identity")?;
1450
1451
        let session_verification_controller = SessionVerificationController::new(
1452
            self.inner.encryption(),
1453
            user_identity,
1454
            self.inner.account(),
1455
        );
1456
1457
        *self.session_verification_controller.write().await =
1458
            Some(session_verification_controller.clone());
1459
1460
        Ok(Arc::new(session_verification_controller))
1461
    }
1462
1463
    /// Log the current user out.
1464
    pub async fn logout(&self) -> Result<(), ClientError> {
1465
        Ok(self.inner.logout().await?)
1466
    }
1467
1468
    /// Registers a pusher with given parameters
1469
    #[allow(clippy::too_many_arguments)]
1470
    pub async fn set_pusher(
1471
        &self,
1472
        identifiers: PusherIdentifiers,
1473
        kind: PusherKind,
1474
        app_display_name: String,
1475
        device_display_name: String,
1476
        profile_tag: Option<String>,
1477
        lang: String,
1478
        append: bool,
1479
    ) -> Result<(), ClientError> {
1480
        let ids = identifiers.into();
1481
1482
        let pusher_init = PusherInit {
1483
            ids,
1484
            kind: kind.try_into()?,
1485
            app_display_name,
1486
            device_display_name,
1487
            profile_tag,
1488
            lang,
1489
        };
1490
        self.inner.pusher().set(pusher_init.into(), append).await?;
1491
        Ok(())
1492
    }
1493
1494
    /// Deletes a pusher of given pusher ids
1495
    pub async fn delete_pusher(&self, identifiers: PusherIdentifiers) -> Result<(), ClientError> {
1496
        self.inner.pusher().delete(identifiers.into()).await?;
1497
        Ok(())
1498
    }
1499
1500
    /// The homeserver this client is configured to use.
1501
    pub fn homeserver(&self) -> String {
1502
        self.inner.homeserver().to_string()
1503
    }
1504
1505
    /// The URL of the server.
1506
    ///
1507
    /// Not to be confused with the `Self::homeserver`. `server` is usually
1508
    /// the server part in a user ID, e.g. with `@mnt_io:matrix.org`, here
1509
    /// `matrix.org` is the server, whilst `matrix-client.matrix.org` is the
1510
    /// homeserver (at the time of writing — 2024-08-28).
1511
    ///
1512
    /// This value is optional depending on how the `Client` has been built.
1513
    /// If it's been built from a homeserver URL directly, we don't know the
1514
    /// server. However, if the `Client` has been built from a server URL or
1515
    /// name, then the homeserver has been discovered, and we know both.
1516
    pub fn server(&self) -> Option<String> {
1517
        self.inner.server().map(ToString::to_string)
1518
    }
1519
1520
    pub fn rooms(&self) -> Vec<Arc<Room>> {
1521
        self.inner
1522
            .rooms()
1523
            .into_iter()
1524
            .map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1525
            .collect()
1526
    }
1527
1528
    /// Mark all joined rooms as read by sending public, private and fully-read
1529
    /// receipts on each room's latest event.
1530
    ///
1531
    /// This is a best-effort operation — per-room errors are logged and
1532
    /// skipped. Receipts are sent unthreaded, which per the Matrix spec
1533
    /// covers all events in a room including those inside threads.
1534
    ///
1535
    /// This is useful to mitigate backend led wrong iOS app badges and work
1536
    /// around https://github.com/element-hq/element-x-ios/issues/3151
1537
    pub async fn mark_all_rooms_as_read(&self) -> Result<(), ClientError> {
1538
        use matrix_sdk::room::Receipts;
1539
        use matrix_sdk_base::RoomStateFilter;
1540
        use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineFocus};
1541
1542
        for sdk_room in self.inner.rooms_filtered(RoomStateFilter::JOINED) {
1543
            let timeline = match TimelineBuilder::new(&sdk_room)
1544
                .with_focus(TimelineFocus::Live { hide_threaded_events: false })
1545
                .build()
1546
                .await
1547
            {
1548
                Ok(tl) => tl,
1549
                Err(err) => {
1550
                    warn!(
1551
                        "mark_all_rooms_as_read: failed to build timeline for {}: {err}",
1552
                        sdk_room.room_id()
1553
                    );
1554
                    continue;
1555
                }
1556
            };
1557
1558
            if let Some(event_id) = timeline.latest_event_id().await {
1559
                let receipts = Receipts::new()
1560
                    .fully_read_marker(event_id.clone())
1561
                    .private_read_receipt(event_id);
1562
                if let Err(err) = sdk_room.send_multiple_receipts(receipts).await {
1563
                    warn!(
1564
                        "mark_all_rooms_as_read: failed to send receipts for {}: {err}",
1565
                        sdk_room.room_id()
1566
                    );
1567
                }
1568
            } else {
1569
                // Room has no events; just clear any stale explicit unread flag.
1570
                if let Err(err) = sdk_room.set_unread_flag(false).await {
1571
                    warn!(
1572
                        "mark_all_rooms_as_read: failed to clear unread flag for {}: {err}",
1573
                        sdk_room.room_id()
1574
                    );
1575
                }
1576
            }
1577
        }
1578
1579
        Ok(())
1580
    }
1581
1582
    /// Get a room by its ID.
1583
    ///
1584
    /// # Arguments
1585
    ///
1586
    /// * `room_id` - The ID of the room to get.
1587
    ///
1588
    /// # Returns
1589
    ///
1590
    /// A `Result` containing an optional room, or a `ClientError`.
1591
    /// This method will not initialize the room's timeline or populate it with
1592
    /// events.
1593
    pub fn get_room(&self, room_id: String) -> Result<Option<Arc<Room>>, ClientError> {
1594
        let room_id = RoomId::parse(room_id)?;
1595
        let sdk_room = self.inner.get_room(&room_id);
1596
1597
        let room =
1598
            sdk_room.map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())));
1599
        Ok(room)
1600
    }
1601
1602
    /// Get the first existing DM room with the given user, if any.
1603
    pub fn get_dm_room(&self, user_id: String) -> Result<Option<Arc<Room>>, ClientError> {
1604
        let user_id = UserId::parse(user_id)?;
1605
        let sdk_room = self.inner.get_dm_room(&user_id);
1606
        let dm =
1607
            sdk_room.map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())));
1608
        Ok(dm)
1609
    }
1610
1611
    /// Get an iterator with the existing DM rooms for the given user.
1612
    pub fn get_dm_rooms(&self, user_id: String) -> Result<Vec<Arc<Room>>, ClientError> {
1613
        let user_id = UserId::parse(user_id)?;
1614
        let sdk_rooms = self.inner.get_dm_rooms(&user_id);
1615
        let dms = sdk_rooms
1616
            .map(|room| Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1617
            .collect();
1618
        Ok(dms)
1619
    }
1620
1621
    pub async fn search_users(
1622
        &self,
1623
        search_term: String,
1624
        limit: u64,
1625
    ) -> Result<SearchUsersResults, ClientError> {
1626
        let response = self.inner.search_users(&search_term, limit).await?;
1627
        Ok(SearchUsersResults::from(response))
1628
    }
1629
1630
    pub async fn get_profile(&self, user_id: String) -> Result<UserProfile, ClientError> {
1631
        let user_id = <&UserId>::try_from(user_id.as_str())?;
1632
        UserProfile::fetch(&self.inner.account(), user_id).await
1633
    }
1634
1635
    pub async fn notification_client(
1636
        self: Arc<Self>,
1637
        process_setup: NotificationProcessSetup,
1638
    ) -> Result<Arc<NotificationClient>, ClientError> {
1639
        Ok(Arc::new(NotificationClient {
1640
            inner: MatrixNotificationClient::new((*self.inner).clone(), process_setup.into())
1641
                .await?,
1642
            client: self.clone(),
1643
        }))
1644
    }
1645
1646
    pub fn sync_service(&self) -> Arc<SyncServiceBuilder> {
1647
        SyncServiceBuilder::new((*self.inner).clone(), self.utd_hook_manager.get().cloned())
1648
    }
1649
1650
    /// Start a sync v2 loop.
1651
    ///
1652
    /// This is an alternative to [`Client::sync_service`] (which uses Sliding
1653
    /// Sync / MSC4186). It works with any homeserver, including older
1654
    /// Synapse versions that do not support Sliding Sync.
1655
    ///
1656
    /// Returns a `TaskHandle` that can be used to cancel the sync loop.
1657
    /// The listener is called after each successful sync response.
1658
    pub fn sync_v2(
1659
        &self,
1660
        settings: SyncSettingsV2,
1661
        listener: Box<dyn SyncListenerV2>,
1662
    ) -> Arc<TaskHandle> {
1663
        let client = (*self.inner).clone();
1664
        let sdk_settings: matrix_sdk::config::SyncSettings = settings.into();
1665
        let listener: Arc<dyn SyncListenerV2> = Arc::from(listener);
1666
1667
        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
1668
            let result = client
1669
                .sync_with_result_callback(sdk_settings, |result| {
1670
                    let listener = listener.clone();
1671
                    async move {
1672
                        let response = result?;
1673
                        let ffi_response: SyncResponseV2 = response.into();
1674
                        listener.on_update(ffi_response);
1675
                        Ok(matrix_sdk::LoopCtrl::Continue)
1676
                    }
1677
                })
1678
                .await;
1679
1680
            if let Err(e) = result {
1681
                tracing::error!("Sync loop ended with error: {e}");
1682
            }
1683
        })))
1684
    }
1685
1686
    /// Perform a single sync v2 call.
1687
    ///
1688
    /// This is useful for performing an initial sync or a one-shot sync
1689
    /// without entering a continuous loop.
1690
    pub async fn sync_once_v2(
1691
        &self,
1692
        settings: SyncSettingsV2,
1693
    ) -> Result<SyncResponseV2, ClientError> {
1694
        let sdk_settings: matrix_sdk::config::SyncSettings = settings.into();
1695
        let response = self.inner.sync_once(sdk_settings).await?;
1696
        Ok(response.into())
1697
    }
1698
1699
    pub async fn space_service(&self) -> Arc<SpaceService> {
1700
        let inner = UISpaceService::new((*self.inner).clone()).await;
1701
        Arc::new(SpaceService::new(inner))
1702
    }
1703
1704
    pub async fn get_notification_settings(&self) -> Arc<NotificationSettings> {
1705
        let inner = self.inner.notification_settings().await;
1706
1707
        Arc::new(NotificationSettings::new((*self.inner).clone(), inner))
1708
    }
1709
1710
    pub fn encryption(self: Arc<Self>) -> Arc<Encryption> {
1711
        Arc::new(Encryption { inner: self.inner.encryption(), _client: self.clone() })
1712
    }
1713
1714
    // Ignored users
1715
1716
    pub async fn ignored_users(&self) -> Result<Vec<String>, ClientError> {
1717
        if let Some(raw_content) =
1718
            self.inner.account().fetch_account_data_static::<IgnoredUserListEventContent>().await?
1719
        {
1720
            let content = raw_content.deserialize()?;
1721
            let user_ids: Vec<String> =
1722
                content.ignored_users.keys().map(|id| id.to_string()).collect();
1723
1724
            return Ok(user_ids);
1725
        }
1726
1727
        Ok(vec![])
1728
    }
1729
1730
    pub async fn ignore_user(&self, user_id: String) -> Result<(), ClientError> {
1731
        let user_id = UserId::parse(user_id)?;
1732
        self.inner.account().ignore_user(&user_id).await?;
1733
        Ok(())
1734
    }
1735
1736
    pub async fn unignore_user(&self, user_id: String) -> Result<(), ClientError> {
1737
        let user_id = UserId::parse(user_id)?;
1738
        self.inner.account().unignore_user(&user_id).await?;
1739
        Ok(())
1740
    }
1741
1742
    pub fn subscribe_to_ignored_users(
1743
        &self,
1744
        listener: Box<dyn IgnoredUsersListener>,
1745
    ) -> Arc<TaskHandle> {
1746
        let mut subscriber = self.inner.subscribe_to_ignore_user_list_changes();
1747
        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
1748
            while let Some(user_ids) = subscriber.next().await {
1749
                listener.call(user_ids);
1750
            }
1751
        })))
1752
    }
1753
1754
    pub fn room_directory_search(&self) -> Arc<RoomDirectorySearch> {
1755
        Arc::new(RoomDirectorySearch::new(
1756
            matrix_sdk::room_directory_search::RoomDirectorySearch::new((*self.inner).clone()),
1757
        ))
1758
    }
1759
1760
    /// Join a room by its ID.
1761
    ///
1762
    /// Use this method when the homeserver already knows of the given room ID.
1763
    /// Otherwise use `join_room_by_id_or_alias` so you can pass a list of
1764
    /// server names for the homeserver to find the room.
1765
    pub async fn join_room_by_id(&self, room_id: String) -> Result<Arc<Room>, ClientError> {
1766
        let room_id = RoomId::parse(room_id)?;
1767
        let room = self.inner.join_room_by_id(room_id.as_ref()).await?;
1768
        Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1769
    }
1770
1771
    /// Join a room by its ID or alias.
1772
    ///
1773
    /// When supplying the room's ID, you can also supply a list of server names
1774
    /// for the homeserver to find the room. Typically these server names
1775
    /// come from a permalink's `via` parameters, or from resolving a room's
1776
    /// alias into an ID.
1777
    pub async fn join_room_by_id_or_alias(
1778
        &self,
1779
        room_id_or_alias: String,
1780
        server_names: Vec<String>,
1781
    ) -> Result<Arc<Room>, ClientError> {
1782
        let room_id = RoomOrAliasId::parse(&room_id_or_alias)?;
1783
        let server_names = server_names
1784
            .iter()
1785
            .map(|name| OwnedServerName::try_from(name.as_str()))
1786
            .collect::<Result<Vec<_>, _>>()?;
1787
        let room =
1788
            self.inner.join_room_by_id_or_alias(room_id.as_ref(), server_names.as_ref()).await?;
1789
        Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1790
    }
1791
1792
    /// Knock on a room to join it using its ID or alias.
1793
    pub async fn knock(
1794
        &self,
1795
        room_id_or_alias: String,
1796
        reason: Option<String>,
1797
        server_names: Vec<String>,
1798
    ) -> Result<Arc<Room>, ClientError> {
1799
        let room_id = RoomOrAliasId::parse(&room_id_or_alias)?;
1800
        let server_names =
1801
            server_names.iter().map(ServerName::parse).collect::<Result<Vec<_>, _>>()?;
1802
        let room = self.inner.knock(room_id, reason, server_names).await?;
1803
        Ok(Arc::new(Room::new(room, self.utd_hook_manager.get().cloned())))
1804
    }
1805
1806
    pub async fn get_recently_visited_rooms(&self) -> Result<Vec<String>, ClientError> {
1807
        Ok(self
1808
            .inner
1809
            .account()
1810
            .get_recently_visited_rooms()
1811
            .await?
1812
            .into_iter()
1813
            .map(Into::into)
1814
            .collect())
1815
    }
1816
1817
    pub async fn track_recently_visited_room(&self, room: String) -> Result<(), ClientError> {
1818
        let room_id = RoomId::parse(room)?;
1819
        self.inner.account().track_recently_visited_room(room_id).await?;
1820
        Ok(())
1821
    }
1822
1823
    /// Resolves the given room alias to a room ID (and a list of servers), if
1824
    /// possible.
1825
    pub async fn resolve_room_alias(
1826
        &self,
1827
        room_alias: String,
1828
    ) -> Result<Option<ResolvedRoomAlias>, ClientError> {
1829
        let room_alias = RoomAliasId::parse(&room_alias)?;
1830
        match self.inner.resolve_room_alias(&room_alias).await {
1831
            Ok(response) => Ok(Some(response.into())),
1832
            Err(error) => match error.client_api_error_kind() {
1833
                // The room alias wasn't found, so we return None.
1834
                Some(ErrorKind::NotFound) => Ok(None),
1835
                // In any other case we just return the error, mapped.
1836
                _ => Err(error.into()),
1837
            },
1838
        }
1839
    }
1840
1841
    /// Checks if a room alias exists in the current homeserver.
1842
    pub async fn room_alias_exists(&self, room_alias: String) -> Result<bool, ClientError> {
1843
        self.resolve_room_alias(room_alias).await.map(|ret| ret.is_some())
1844
    }
1845
1846
    /// Given a room id, get the preview of a room, to interact with it.
1847
    ///
1848
    /// The list of `via_servers` must be a list of servers that know
1849
    /// about the room and can resolve it, and that may appear as a `via`
1850
    /// parameter in e.g. a permalink URL. This list can be empty.
1851
    pub async fn get_room_preview_from_room_id(
1852
        &self,
1853
        room_id: String,
1854
        via_servers: Vec<String>,
1855
    ) -> Result<Arc<RoomPreview>, ClientError> {
1856
        let room_id = RoomId::parse(&room_id).context("room_id is not a valid room id")?;
1857
1858
        let via_servers = via_servers
1859
            .into_iter()
1860
            .map(ServerName::parse)
1861
            .collect::<Result<Vec<_>, _>>()
1862
            .context("at least one `via` server name is invalid")?;
1863
1864
        // The `into()` call below doesn't work if I do `(&room_id).into()`, so I let
1865
        // rustc win that one fight.
1866
        let room_id: &RoomId = &room_id;
1867
1868
        let room_preview = self.inner.get_room_preview(room_id.into(), via_servers).await?;
1869
1870
        Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
1871
    }
1872
1873
    /// Given a room alias, get the preview of a room, to interact with it.
1874
    pub async fn get_room_preview_from_room_alias(
1875
        &self,
1876
        room_alias: String,
1877
    ) -> Result<Arc<RoomPreview>, ClientError> {
1878
        let room_alias =
1879
            RoomAliasId::parse(&room_alias).context("room_alias is not a valid room alias")?;
1880
1881
        // The `into()` call below doesn't work if I do `(&room_id).into()`, so I let
1882
        // rustc win that one fight.
1883
        let room_alias: &RoomAliasId = &room_alias;
1884
1885
        let room_preview = self.inner.get_room_preview(room_alias.into(), Vec::new()).await?;
1886
1887
        Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
1888
    }
1889
1890
    /// Waits until an at least partially synced room is received, and returns
1891
    /// it.
1892
    ///
1893
    /// **Note: this function will loop endlessly until either it finds the room
1894
    /// or an externally set timeout happens.**
1895
    pub async fn await_room_remote_echo(&self, room_id: String) -> Result<Arc<Room>, ClientError> {
1896
        let room_id = RoomId::parse(room_id)?;
1897
        Ok(Arc::new(Room::new(
1898
            self.inner.await_room_remote_echo(&room_id).await,
1899
            self.utd_hook_manager.get().cloned(),
1900
        )))
1901
    }
1902
1903
    /// Lets the user know whether this is an `m.login.password` based
1904
    /// auth and if the account can actually be deactivated
1905
    pub fn can_deactivate_account(&self) -> bool {
1906
        matches!(self.inner.auth_api(), Some(AuthApi::Matrix(_)))
1907
    }
1908
1909
    /// Deactivate this account definitively.
1910
    /// Similarly to `encryption::reset_identity` this
1911
    /// will only work with password-based authentication (`m.login.password`)
1912
    ///
1913
    /// # Arguments
1914
    ///
1915
    /// * `auth_data` - This request uses the [User-Interactive Authentication
1916
    ///   API][uiaa]. The first request needs to set this to `None` and will
1917
    ///   always fail and the same request needs to be made but this time with
1918
    ///   some `auth_data` provided.
1919
    pub async fn deactivate_account(
1920
        &self,
1921
        auth_data: Option<AuthData>,
1922
        erase_data: bool,
1923
    ) -> Result<(), ClientError> {
1924
        if let Some(auth_data) = auth_data {
1925
            _ = self.inner.account().deactivate(None, Some(auth_data.into()), erase_data).await?;
1926
        } else {
1927
            _ = self.inner.account().deactivate(None, None, erase_data).await?;
1928
        }
1929
1930
        Ok(())
1931
    }
1932
1933
    /// Checks if a room alias is not in use yet.
1934
    ///
1935
    /// Returns:
1936
    /// - `Ok(true)` if the room alias is available.
1937
    /// - `Ok(false)` if it's not (the resolve alias request returned a `404`
1938
    ///   status code).
1939
    /// - An `Err` otherwise.
1940
    pub async fn is_room_alias_available(&self, alias: String) -> Result<bool, ClientError> {
1941
        let alias = RoomAliasId::parse(alias)?;
1942
        self.inner.is_room_alias_available(&alias).await.map_err(Into::into)
1943
    }
1944
1945
    /// Set the media retention policy.
1946
    pub async fn set_media_retention_policy(
1947
        &self,
1948
        policy: MediaRetentionPolicy,
1949
    ) -> Result<(), ClientError> {
1950
        let closure = async || -> Result<_, Error> {
1951
            let store = self.inner.media_store().lock().await?;
1952
            Ok(store.set_media_retention_policy(policy).await?)
1953
        };
1954
1955
        Ok(closure().await?)
1956
    }
1957
1958
    /// Clear all the non-critical caches for this Client instance.
1959
    ///
1960
    /// WARNING: This will clear all the caches, including the base store (state
1961
    /// store), so callers must make sure that the Client is at rest before
1962
    /// calling it.
1963
    ///
1964
    /// In particular, if a [`SyncService`] is running, it must be passed here
1965
    /// as a parameter, or stopped before calling this method. Ideally, the
1966
    /// send queues should have been disabled and must all be inactive (i.e.
1967
    /// not sending events); this method will disable them, but it might not
1968
    /// be enough if the queues are still processing events.
1969
    ///
1970
    /// After the method returns, the Client will be in an unstable
1971
    /// state, and it is required that the caller reinstantiates a new
1972
    /// Client instance, be it via dropping the previous and re-creating it,
1973
    /// restarting their application, or any other similar means.
1974
    ///
1975
    /// - This will get rid of the backing state store file, if provided.
1976
    /// - This will empty all the room's persisted event caches, so all rooms
1977
    ///   will start as if they were empty.
1978
    /// - This will empty the media cache according to the current media
1979
    ///   retention policy.
1980
    pub async fn clear_caches(
1981
        &self,
1982
        sync_service: Option<Arc<SyncService>>,
1983
    ) -> Result<(), ClientError> {
1984
        let closure = async || -> Result<_, ClientError> {
1985
            // First, make sure to expire sessions in the sync service.
1986
            if let Some(sync_service) = sync_service {
1987
                sync_service.inner.expire_sessions().await;
1988
            }
1989
1990
            // Disable the send queues, as they might read and write to the state store.
1991
            // Events being send might still be active, and cause errors if
1992
            // processing finishes, so this will only minimize damage. Since
1993
            // this method should only be called in exceptional cases, this has
1994
            // been deemed acceptable.
1995
            self.inner.send_queue().set_enabled(false).await;
1996
1997
            // Clean up the media cache according to the current media retention policy.
1998
            self.inner
1999
                .media_store()
2000
                .lock()
2001
                .await
2002
                .map_err(Error::from)?
2003
                .clean()
2004
                .await
2005
                .map_err(Error::from)?;
2006
2007
            // Clear all the room chunks. It's important to *not* call
2008
            // `EventCacheStore::clear_all_linked_chunks` here, because there might be live
2009
            // observers of the linked chunks, and that would cause some very bad state
2010
            // mismatch.
2011
            self.inner.event_cache().clear_all_rooms().await?;
2012
2013
            // Delete the state store file, if it exists.
2014
            #[cfg(feature = "sqlite")]
2015
            if let Some(store_path) = &self.store_path {
2016
                debug!("Removing the state store: {}", store_path.display());
2017
2018
                // The state store and the crypto store both live in the same store path, so we
2019
                // can't blindly delete the directory.
2020
                //
2021
                // Delete the state store SQLite file, as well as the write-ahead log (WAL) and
2022
                // shared-memory (SHM) files, if they exist.
2023
2024
                for file_name in [
2025
                    PathBuf::from(STATE_STORE_DATABASE_NAME),
2026
                    PathBuf::from(format!("{STATE_STORE_DATABASE_NAME}.wal")),
2027
                    PathBuf::from(format!("{STATE_STORE_DATABASE_NAME}.shm")),
2028
                ] {
2029
                    let file_path = store_path.join(file_name);
2030
                    if file_path.exists() {
2031
                        debug!("Removing file: {}", file_path.display());
2032
                        std::fs::remove_file(&file_path).map_err(|err| ClientError::Generic {
2033
                            msg: format!(
2034
                                "couldn't delete the state store file {}: {err}",
2035
                                file_path.display()
2036
                            ),
2037
                            details: None,
2038
                        })?;
2039
                    }
2040
                }
2041
            }
2042
2043
            Ok(())
2044
        };
2045
2046
        closure().await
2047
    }
2048
2049
    /// Checks if the server supports the report room API.
2050
    pub async fn is_report_room_api_supported(&self) -> Result<bool, ClientError> {
2051
        Ok(self.inner.server_versions().await?.contains(&ruma::api::MatrixVersion::V1_13))
2052
    }
2053
2054
    /// Checks if the server supports the LiveKit RTC focus for placing calls.
2055
    pub async fn is_livekit_rtc_supported(&self) -> Result<bool, ClientError> {
2056
        Ok(self
2057
            .inner
2058
            .rtc_foci()
2059
            .await?
2060
            .iter()
2061
            .any(|focus| matches!(focus, RtcTransport::LiveKit(_))))
2062
    }
2063
2064
    /// Get information about the homeserver's advertised map tile server, if
2065
    /// any.
2066
    ///
2067
    /// Reads the `tile_server` field of the matrix client well-known (MSC3488).
2068
    /// Uses the cached well-known when available, otherwise fetches it from the
2069
    /// homeserver.
2070
    pub async fn tile_server(&self) -> Option<matrix_sdk::TileServerInfo> {
2071
        self.inner.tile_server().await
2072
    }
2073
2074
    /// Checks if the server supports login using a QR code.
2075
    pub async fn is_login_with_qr_code_supported(&self) -> Result<bool, ClientError> {
2076
        Ok(matches!(self.inner.auth_api(), Some(AuthApi::OAuth(_)))
2077
            && self.inner.unstable_features().await?.contains(&ruma::api::FeatureFlag::Msc4108))
2078
    }
2079
2080
    /// Get server vendor information from the federation API.
2081
    ///
2082
    /// This method retrieves information about the server's name and version
2083
    /// by calling the `/_matrix/federation/v1/version` endpoint.
2084
    pub async fn server_vendor_info(&self) -> Result<matrix_sdk::ServerVendorInfo, ClientError> {
2085
        Ok(self.inner.server_vendor_info(None).await?)
2086
    }
2087
2088
    /// Subscribe to changes in the media preview configuration.
2089
    pub async fn subscribe_to_media_preview_config(
2090
        &self,
2091
        listener: Box<dyn MediaPreviewConfigListener>,
2092
    ) -> Result<Arc<TaskHandle>, ClientError> {
2093
        let (initial_value, stream) = self.inner.account().observe_media_preview_config().await?;
2094
        Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
2095
            // Send the initial value to the listener.
2096
            listener.on_change(initial_value.map(|config| config.into()));
2097
            // Listen for changes and notify the listener.
2098
            pin_mut!(stream);
2099
            while let Some(media_preview_config) = stream.next().await {
2100
                listener.on_change(Some(media_preview_config.into()));
2101
            }
2102
        }))))
2103
    }
2104
2105
    /// Set the media previews timeline display policy
2106
    pub async fn set_media_preview_display_policy(
2107
        &self,
2108
        policy: MediaPreviews,
2109
    ) -> Result<(), ClientError> {
2110
        self.inner.account().set_media_previews_display_policy(policy.into()).await?;
2111
        Ok(())
2112
    }
2113
2114
    /// Get the media previews timeline display policy
2115
    /// currently stored in the cache.
2116
    pub async fn get_media_preview_display_policy(
2117
        &self,
2118
    ) -> Result<Option<MediaPreviews>, ClientError> {
2119
        let configuration = self.inner.account().get_media_preview_config_event_content().await?;
2120
        match configuration {
2121
            Some(configuration) => Ok(configuration.media_previews.map(Into::into)),
2122
            None => Ok(None),
2123
        }
2124
    }
2125
2126
    /// Set the invite request avatars display policy
2127
    pub async fn set_invite_avatars_display_policy(
2128
        &self,
2129
        policy: InviteAvatars,
2130
    ) -> Result<(), ClientError> {
2131
        self.inner.account().set_invite_avatars_display_policy(policy.into()).await?;
2132
        Ok(())
2133
    }
2134
2135
    /// Get the invite request avatars display policy
2136
    /// currently stored in the cache.
2137
    pub async fn get_invite_avatars_display_policy(
2138
        &self,
2139
    ) -> Result<Option<InviteAvatars>, ClientError> {
2140
        let configuration = self.inner.account().get_media_preview_config_event_content().await?;
2141
        match configuration {
2142
            Some(configuration) => Ok(configuration.invite_avatars.map(Into::into)),
2143
            None => Ok(None),
2144
        }
2145
    }
2146
2147
    /// Fetch the media preview configuration from the server.
2148
    pub async fn fetch_media_preview_config(
2149
        &self,
2150
    ) -> Result<Option<MediaPreviewConfig>, ClientError> {
2151
        Ok(self.inner.account().fetch_media_preview_config_event_content().await?.map(Into::into))
2152
    }
2153
2154
    /// Gets the `max_upload_size` value from the homeserver, which controls the
2155
    /// max size a media upload request can have.
2156
    pub async fn get_max_media_upload_size(&self) -> Result<u64, ClientError> {
2157
        let max_upload_size = self.inner.load_or_fetch_max_upload_size().await?;
2158
        Ok(max_upload_size.into())
2159
    }
2160
2161
    /// Subscribe to [`RoomInfo`] updates given a provided [`RoomId`].
2162
    ///
2163
    /// This works even for rooms we haven't received yet, so we can subscribe
2164
    /// to this and wait until we receive updates from them when sync responses
2165
    /// are processed.
2166
    ///
2167
    /// Note this method should be used sparingly since using callback
2168
    /// interfaces is expensive, as well as keeping them alive for a long
2169
    /// time. Usages of this method should be short-lived and dropped as
2170
    /// soon as possible.
2171
    pub async fn subscribe_to_room_info(
2172
        &self,
2173
        room_id: String,
2174
        listener: Box<dyn RoomInfoListener>,
2175
    ) -> Result<Arc<TaskHandle>, ClientError> {
2176
        let room_id = RoomId::parse(room_id)?;
2177
2178
        // Emit the initial event, if present
2179
        if let Some(room) = self.inner.get_room(&room_id)
2180
            && let Ok(room_info) = RoomInfo::new(&room).await
2181
        {
2182
            listener.call(room_info);
2183
        }
2184
2185
        Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn({
2186
            let client = self.inner.clone();
2187
            let mut receiver = client.room_info_notable_update_receiver();
2188
            async move {
2189
                while let Ok(room_update) = receiver.recv().await {
2190
                    if room_update.room_id != room_id {
2191
                        continue;
2192
                    }
2193
2194
                    if let Some(room) = client.get_room(&room_id)
2195
                        && let Ok(room_info) = RoomInfo::new(&room).await
2196
                    {
2197
                        listener.call(room_info);
2198
                    }
2199
                }
2200
            }
2201
        }))))
2202
    }
2203
2204
    /// Whether to enable automatic backpagination under certain conditions
2205
    /// (e.g. when processing read receipts).
2206
    ///
2207
    /// This is an experimental feature, and might cause performance issues on
2208
    /// large accounts. Use with caution.
2209
    ///
2210
    /// This must be called after creating a client, but before subscribing to
2211
    /// the event cache (so, before spawning a sync service or a timeline).
2212
    pub fn enable_automatic_backpagination(&self) {
2213
        self.inner.event_cache().config_mut().experimental_auto_backpagination = true;
2214
    }
2215
2216
    pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
2217
        HomeserverCapabilities::new(self.inner.homeserver_capabilities())
2218
    }
2219
}
2220
2221
async fn notification_handler(
2222
    notification: Notification,
2223
    room: matrix_sdk::Room,
2224
    listener: Arc<Box<dyn SyncNotificationListener>>,
2225
) {
2226
    let room_id = room.room_id().to_string();
2227
2228
    // Extract information about the actions
2229
    let is_noisy = notification.actions.iter().any(|a| a.sound().is_some());
2230
    let has_mention = notification.actions.iter().any(|a| a.is_highlight());
2231
2232
    // Convert SDK actions to FFI type
2233
    let actions: Vec<crate::notification_settings::Action> =
2234
        notification.actions.into_iter().filter_map(|action| action.try_into().ok()).collect();
2235
2236
    // Convert SDK event to FFI type
2237
    let (sender, event, thread_id, raw_event) = match notification.event {
2238
        RawAnySyncOrStrippedTimelineEvent::Sync(raw) => {
2239
            let raw_event = raw.json().get().to_owned();
2240
            match raw.deserialize() {
2241
                Ok(deserialized) => {
2242
                    let sender = deserialized.sender().to_owned();
2243
                    let thread_id = if let AnySyncTimelineEvent::MessageLike(event) = &deserialized
2244
                        && let Some(AnyMessageLikeEventContent::RoomMessage(content)) =
2245
                            event.original_content()
2246
                        && let Some(Relation::Thread(thread)) = content.relates_to
2247
                    {
2248
                        Some(thread.event_id.to_string())
2249
                    } else {
2250
                        None
2251
                    };
2252
                    let event = NotificationEvent::Timeline {
2253
                        event: Arc::new(crate::event::TimelineEvent(Box::new(deserialized))),
2254
                    };
2255
                    (sender, event, thread_id, raw_event)
2256
                }
2257
                Err(err) => {
2258
                    tracing::warn!("Failed to deserialize timeline event: {err}");
2259
                    return;
2260
                }
2261
            }
2262
        }
2263
        RawAnySyncOrStrippedTimelineEvent::Stripped(raw) => {
2264
            let raw_event = raw.json().get().to_owned();
2265
            match raw.deserialize() {
2266
                Ok(deserialized) => {
2267
                    let sender = deserialized.sender().to_owned();
2268
                    let event = NotificationEvent::Invite { sender: sender.to_string() };
2269
                    let thread_id = None;
2270
                    (sender, event, thread_id, raw_event)
2271
                }
2272
                Err(err) => {
2273
                    tracing::warn!("Failed to deserialize stripped state event: {err}");
2274
                    return;
2275
                }
2276
            }
2277
        }
2278
    };
2279
2280
    // Compile sender info
2281
    let sender = room.get_member_no_sync(&sender).await.ok().flatten();
2282
    let sender_info = if let Some(sender) = sender.as_ref() {
2283
        NotificationSenderInfo {
2284
            display_name: sender.display_name().map(|name| name.to_owned()),
2285
            avatar_url: sender.avatar_url().map(|uri| uri.to_string()),
2286
            is_name_ambiguous: sender.name_ambiguous(),
2287
        }
2288
    } else {
2289
        NotificationSenderInfo { display_name: None, avatar_url: None, is_name_ambiguous: false }
2290
    };
2291
2292
    // Compile room info
2293
    let display_name = match room.display_name().await {
2294
        Ok(name) => name.to_string(),
2295
        Err(err) => {
2296
            tracing::warn!("Failed to calculate the room's display name: {err}");
2297
            return;
2298
        }
2299
    };
2300
    let is_direct = match room.is_direct().await {
2301
        Ok(is_direct) => is_direct,
2302
        Err(err) => {
2303
            tracing::warn!("Failed to determine if room is direct or not: {err}");
2304
            return;
2305
        }
2306
    };
2307
    let room_info = NotificationRoomInfo {
2308
        display_name,
2309
        avatar_url: room.avatar_url().map(Into::into),
2310
        canonical_alias: room.canonical_alias().map(Into::into),
2311
        topic: room.topic(),
2312
        join_rule: room.join_rule().map(TryInto::try_into).transpose().ok().flatten(),
2313
        joined_members_count: room.joined_members_count(),
2314
        service_members: room
2315
            .service_members()
2316
            .unwrap_or_default()
2317
            .iter()
2318
            .map(ToString::to_string)
2319
            .collect(),
2320
        active_service_members_count: room
2321
            .update_active_service_members()
2322
            .await
2323
            .ok()
2324
            .flatten()
2325
            .unwrap_or_default()
2326
            .len() as u64,
2327
        is_encrypted: Some(room.encryption_state().is_encrypted()),
2328
        is_direct,
2329
        is_space: room.is_space(),
2330
        is_dm: room.compute_is_dm().await.ok().unwrap_or_default(),
2331
    };
2332
2333
    listener.on_notification(
2334
        NotificationItem {
2335
            event,
2336
            raw_event,
2337
            sender_info,
2338
            room_info,
2339
            is_noisy: Some(is_noisy),
2340
            has_mention: Some(has_mention),
2341
            thread_id,
2342
            actions: Some(actions),
2343
        },
2344
        room_id,
2345
    );
2346
}
2347
2348
#[cfg(feature = "experimental-element-recent-emojis")]
2349
mod recent_emoji {
2350
    use crate::{client::Client, error::ClientError};
2351
2352
    /// Represents an emoji recently used for reactions.
2353
    #[derive(Debug, uniffi::Record)]
2354
    pub struct RecentEmoji {
2355
        /// The actual emoji text representation.
2356
        pub emoji: String,
2357
        /// The number of times this emoji has been used for reactions.
2358
        pub count: u64,
2359
    }
2360
2361
    #[matrix_sdk_ffi_macros::export]
2362
    impl Client {
2363
        /// Adds a recently used emoji to the list and uploads the updated
2364
        /// `io.element.recent_emoji` content to the global account data.
2365
        pub async fn add_recent_emoji(&self, emoji: String) -> Result<(), ClientError> {
2366
            Ok(self.inner.account().add_recent_emoji(&emoji).await?)
2367
        }
2368
2369
        /// Gets the list of recently used emojis from the
2370
        /// `io.element.recent_emoji` global account data.
2371
        pub async fn get_recent_emojis(&self) -> Result<Vec<RecentEmoji>, ClientError> {
2372
            Ok(self
2373
                .inner
2374
                .account()
2375
                .get_recent_emojis(false)
2376
                .await?
2377
                .into_iter()
2378
                .map(|(emoji, count)| RecentEmoji { emoji, count: count.into() })
2379
                .collect::<Vec<RecentEmoji>>())
2380
        }
2381
    }
2382
}
2383
2384
#[matrix_sdk_ffi_macros::export(callback_interface)]
2385
pub trait MediaPreviewConfigListener: SyncOutsideWasm + SendOutsideWasm {
2386
    fn on_change(&self, media_preview_config: Option<MediaPreviewConfig>);
2387
}
2388
2389
#[matrix_sdk_ffi_macros::export(callback_interface)]
2390
pub trait IgnoredUsersListener: SyncOutsideWasm + SendOutsideWasm {
2391
    fn call(&self, ignored_user_ids: Vec<String>);
2392
}
2393
2394
#[derive(uniffi::Enum)]
2395
pub enum NotificationProcessSetup {
2396
    MultipleProcesses,
2397
    SingleProcess { sync_service: Arc<SyncService> },
2398
}
2399
2400
impl From<NotificationProcessSetup> for MatrixNotificationProcessSetup {
2401
    fn from(value: NotificationProcessSetup) -> Self {
2402
        match value {
2403
            NotificationProcessSetup::MultipleProcesses => {
2404
                MatrixNotificationProcessSetup::MultipleProcesses
2405
            }
2406
            NotificationProcessSetup::SingleProcess { sync_service } => {
2407
                MatrixNotificationProcessSetup::SingleProcess {
2408
                    sync_service: sync_service.inner.clone(),
2409
                }
2410
            }
2411
        }
2412
    }
2413
}
2414
2415
/// Information about a room, that was resolved from a room alias.
2416
#[derive(uniffi::Record)]
2417
pub struct ResolvedRoomAlias {
2418
    /// The room ID that the alias resolved to.
2419
    pub room_id: String,
2420
    /// A list of servers that can be used to find the room by its room ID.
2421
    pub servers: Vec<String>,
2422
}
2423
2424
impl From<get_alias::v3::Response> for ResolvedRoomAlias {
2425
    fn from(value: get_alias::v3::Response) -> Self {
2426
        Self {
2427
            room_id: value.room_id.to_string(),
2428
            servers: value.servers.iter().map(ToString::to_string).collect(),
2429
        }
2430
    }
2431
}
2432
2433
#[derive(uniffi::Record)]
2434
pub struct SearchUsersResults {
2435
    pub results: Vec<UserProfile>,
2436
    pub limited: bool,
2437
}
2438
2439
impl From<search_users::v3::Response> for SearchUsersResults {
2440
    fn from(value: search_users::v3::Response) -> Self {
2441
        let results: Vec<UserProfile> = value.results.iter().map(UserProfile::from).collect();
2442
        SearchUsersResults { results, limited: value.limited }
2443
    }
2444
}
2445
2446
#[derive(uniffi::Record)]
2447
pub struct UserProfile {
2448
    pub user_id: String,
2449
    pub display_name: Option<String>,
2450
    pub avatar_url: Option<String>,
2451
}
2452
2453
impl UserProfile {
2454
    /// Fetch the profile for the given user ID, using the given [`Account`]
2455
    /// API.
2456
    pub(crate) async fn fetch(account: &Account, user_id: &UserId) -> Result<Self, ClientError> {
2457
        let response = account.fetch_user_profile_of(user_id).await?;
2458
        let display_name = response.get_static::<DisplayName>()?;
2459
        let avatar_url = response.get_static::<AvatarUrl>()?.map(|url| url.to_string());
2460
2461
        Ok(UserProfile { user_id: user_id.to_string(), display_name, avatar_url })
2462
    }
2463
}
2464
2465
impl From<&search_users::v3::User> for UserProfile {
2466
    fn from(value: &search_users::v3::User) -> Self {
2467
        UserProfile {
2468
            user_id: value.user_id.to_string(),
2469
            display_name: value.display_name.clone(),
2470
            avatar_url: value.avatar_url.as_ref().map(|url| url.to_string()),
2471
        }
2472
    }
2473
}
2474
2475
impl Client {
2476
    fn process_session_change(&self, session_change: SessionChange) {
2477
        if let Some(delegate_data) = self.delegate_data.get() {
2478
            debug!("Applying session change: {session_change:?}");
2479
            let delegate = delegate_data.delegate.clone();
2480
            get_runtime_handle().spawn_blocking(move || match session_change {
2481
                SessionChange::UnknownToken(unknown_token) => {
2482
                    delegate.did_receive_auth_error(unknown_token.soft_logout);
2483
                }
2484
                SessionChange::TokensRefreshed => {}
2485
            });
2486
        } else {
2487
            debug!(
2488
                "No client delegate found, session change couldn't be applied: {session_change:?}"
2489
            );
2490
        }
2491
    }
2492
2493
    fn retrieve_session(
2494
        session_delegate: Arc<dyn ClientSessionDelegate>,
2495
        user_id: &UserId,
2496
    ) -> anyhow::Result<SessionTokens> {
2497
        Ok(session_delegate.retrieve_session_from_keychain(user_id.to_string())?.into_tokens())
2498
    }
2499
2500
    fn session_inner(client: matrix_sdk::Client) -> Result<Session, ClientError> {
2501
        let auth_api = client.auth_api().context("Missing authentication API")?;
2502
2503
        let homeserver_url = client.homeserver().into();
2504
        let sliding_sync_version = client.sliding_sync_version();
2505
2506
        Session::new(auth_api, homeserver_url, sliding_sync_version.into())
2507
    }
2508
2509
    fn save_session(
2510
        session_delegate: Arc<dyn ClientSessionDelegate>,
2511
        client: matrix_sdk::Client,
2512
    ) -> anyhow::Result<()> {
2513
        let session = Self::session_inner(client)?;
2514
        session_delegate.save_session_in_keychain(session);
2515
        Ok(())
2516
    }
2517
}
2518
2519
/// Configure how many rooms will be restored when restoring the session with
2520
/// [`Client::restore_session_with`].
2521
///
2522
/// Please, see the documentation of [`matrix_sdk::store::RoomLoadSettings`] to
2523
/// learn more.
2524
#[derive(uniffi::Enum)]
2525
pub enum RoomLoadSettings {
2526
    /// Load all rooms from the `StateStore` into the in-memory state store
2527
    /// `BaseStateStore`.
2528
    All,
2529
2530
    /// Load a single room from the `StateStore` into the in-memory state
2531
    /// store `BaseStateStore`.
2532
    ///
2533
    /// Please, be careful with this option. Read the documentation of
2534
    /// [`RoomLoadSettings`].
2535
    One { room_id: String },
2536
}
2537
2538
impl TryInto<SdkRoomLoadSettings> for RoomLoadSettings {
2539
    type Error = String;
2540
2541
    fn try_into(self) -> Result<SdkRoomLoadSettings, Self::Error> {
2542
        Ok(match self {
2543
            Self::All => SdkRoomLoadSettings::All,
2544
            Self::One { room_id } => {
2545
                SdkRoomLoadSettings::One(RoomId::parse(room_id).map_err(|error| error.to_string())?)
2546
            }
2547
        })
2548
    }
2549
}
2550
2551
#[derive(uniffi::Record)]
2552
pub struct NotificationPowerLevels {
2553
    pub room: i32,
2554
}
2555
2556
impl From<NotificationPowerLevels> for ruma::power_levels::NotificationPowerLevels {
2557
    fn from(value: NotificationPowerLevels) -> Self {
2558
        let mut notification_power_levels = Self::new();
2559
        notification_power_levels.room = value.room.into();
2560
        notification_power_levels
2561
    }
2562
}
2563
2564
#[derive(uniffi::Record)]
2565
pub struct PowerLevels {
2566
    pub users_default: Option<i32>,
2567
    pub events_default: Option<i32>,
2568
    pub state_default: Option<i32>,
2569
    pub ban: Option<i32>,
2570
    pub kick: Option<i32>,
2571
    pub redact: Option<i32>,
2572
    pub invite: Option<i32>,
2573
    pub notifications: Option<NotificationPowerLevels>,
2574
    pub users: HashMap<String, i32>,
2575
    pub events: HashMap<String, i32>,
2576
}
2577
2578
impl From<PowerLevels> for RoomPowerLevelsContentOverride {
2579
    fn from(value: PowerLevels) -> Self {
2580
        let mut power_levels = RoomPowerLevelsContentOverride::default();
2581
        power_levels.users_default = value.users_default.map(Into::into);
2582
        power_levels.state_default = value.state_default.map(Into::into);
2583
        power_levels.events_default = value.events_default.map(Into::into);
2584
        power_levels.ban = value.ban.map(Into::into);
2585
        power_levels.kick = value.kick.map(Into::into);
2586
        power_levels.redact = value.redact.map(Into::into);
2587
        power_levels.invite = value.invite.map(Into::into);
2588
        power_levels.notifications = value.notifications.map(Into::into).unwrap_or_default();
2589
        power_levels.users = value
2590
            .users
2591
            .iter()
2592
            .filter_map(|(user_id, power_level)| match UserId::parse(user_id) {
2593
                Ok(id) => Some((id, (*power_level).into())),
2594
                Err(e) => {
2595
                    error!(user_id, "Skipping invalid user ID, error: {e}");
2596
                    None
2597
                }
2598
            })
2599
            .collect();
2600
        power_levels.events = value
2601
            .events
2602
            .iter()
2603
            .map(|(event_type, power_level)| {
2604
                let event_type: ruma::events::TimelineEventType = event_type.as_str().into();
2605
                (event_type, (*power_level).into())
2606
            })
2607
            .collect();
2608
        power_levels
2609
    }
2610
}
2611
2612
#[derive(uniffi::Record)]
2613
pub struct CreateRoomParameters {
2614
    pub name: Option<String>,
2615
    #[uniffi(default = None)]
2616
    pub topic: Option<String>,
2617
    pub is_encrypted: bool,
2618
    #[uniffi(default = false)]
2619
    pub is_direct: bool,
2620
    pub visibility: RoomVisibility,
2621
    pub preset: RoomPreset,
2622
    #[uniffi(default = None)]
2623
    pub invite: Option<Vec<String>>,
2624
    #[uniffi(default = None)]
2625
    pub avatar: Option<String>,
2626
    #[uniffi(default = None)]
2627
    pub power_level_content_override: Option<PowerLevels>,
2628
    #[uniffi(default = None)]
2629
    pub join_rule_override: Option<JoinRule>,
2630
    #[uniffi(default = None)]
2631
    pub history_visibility_override: Option<RoomHistoryVisibility>,
2632
    #[uniffi(default = None)]
2633
    pub canonical_alias: Option<String>,
2634
    #[uniffi(default = false)]
2635
    pub is_space: bool,
2636
}
2637
2638
impl TryFrom<CreateRoomParameters> for create_room::v3::Request {
2639
    type Error = ClientError;
2640
2641
    fn try_from(value: CreateRoomParameters) -> Result<create_room::v3::Request, Self::Error> {
2642
        let mut request = create_room::v3::Request::new();
2643
        request.name = value.name;
2644
        request.topic = value.topic;
2645
        request.is_direct = value.is_direct;
2646
        request.visibility = value.visibility.into();
2647
        request.preset = Some(value.preset.into());
2648
        request.room_alias_name = value.canonical_alias;
2649
        request.invite = match value.invite {
2650
            Some(invite) => invite
2651
                .iter()
2652
                .filter_map(|user_id| match UserId::parse(user_id) {
2653
                    Ok(id) => Some(id),
2654
                    Err(e) => {
2655
                        error!(user_id, "Skipping invalid user ID, error: {e}");
2656
                        None
2657
                    }
2658
                })
2659
                .collect(),
2660
            None => vec![],
2661
        };
2662
2663
        let mut initial_state: Vec<Raw<AnyInitialStateEvent>> = vec![];
2664
2665
        if value.is_encrypted {
2666
            let content =
2667
                RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
2668
            initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2669
        }
2670
2671
        if let Some(url) = value.avatar {
2672
            let mut content = RoomAvatarEventContent::new();
2673
            content.url = Some(url.into());
2674
            initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2675
        }
2676
2677
        if let Some(join_rule_override) = value.join_rule_override {
2678
            let content = RoomJoinRulesEventContent::new(join_rule_override.try_into()?);
2679
            initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2680
        }
2681
2682
        if let Some(history_visibility_override) = value.history_visibility_override {
2683
            let content =
2684
                RoomHistoryVisibilityEventContent::new(history_visibility_override.try_into()?);
2685
            initial_state.push(InitialStateEvent::with_empty_state_key(content).to_raw_any());
2686
        }
2687
2688
        request.initial_state = initial_state;
2689
2690
        if value.is_space {
2691
            let mut creation_content = CreationContent::new();
2692
            creation_content.room_type = Some(RoomType::Space);
2693
            request.creation_content = Some(Raw::new(&creation_content)?);
2694
        }
2695
2696
        if let Some(power_levels) = value.power_level_content_override {
2697
            match Raw::<RoomPowerLevelsContentOverride>::new(&power_levels.into()) {
2698
                Ok(power_levels) => {
2699
                    request.power_level_content_override = Some(power_levels);
2700
                }
2701
                Err(e) => {
2702
                    return Err(ClientError::Generic {
2703
                        msg: format!("Failed to serialize power levels, error: {e}"),
2704
                        details: Some(format!("{e:?}")),
2705
                    });
2706
                }
2707
            }
2708
        }
2709
2710
        Ok(request)
2711
    }
2712
}
2713
2714
#[derive(uniffi::Enum)]
2715
pub enum RoomVisibility {
2716
    /// Indicates that the room will be shown in the published room list.
2717
    Public,
2718
2719
    /// Indicates that the room will not be shown in the published room list.
2720
    Private,
2721
2722
    /// A custom value that's not present in the spec.
2723
    Custom { value: String },
2724
}
2725
2726
impl From<RoomVisibility> for Visibility {
2727
    fn from(value: RoomVisibility) -> Self {
2728
        match value {
2729
            RoomVisibility::Public => Self::Public,
2730
            RoomVisibility::Private => Self::Private,
2731
            RoomVisibility::Custom { value } => value.as_str().into(),
2732
        }
2733
    }
2734
}
2735
2736
impl From<Visibility> for RoomVisibility {
2737
    fn from(value: Visibility) -> Self {
2738
        match value {
2739
            Visibility::Public => Self::Public,
2740
            Visibility::Private => Self::Private,
2741
            _ => Self::Custom { value: value.as_str().to_owned() },
2742
        }
2743
    }
2744
}
2745
2746
#[derive(uniffi::Enum)]
2747
#[allow(clippy::enum_variant_names)]
2748
pub enum RoomPreset {
2749
    /// `join_rules` is set to `invite` and `history_visibility` is set to
2750
    /// `shared`.
2751
    PrivateChat,
2752
2753
    /// `join_rules` is set to `public` and `history_visibility` is set to
2754
    /// `shared`.
2755
    PublicChat,
2756
2757
    /// Same as `PrivateChat`, but all initial invitees get the same power level
2758
    /// as the creator.
2759
    TrustedPrivateChat,
2760
}
2761
2762
impl From<RoomPreset> for create_room::v3::RoomPreset {
2763
    fn from(value: RoomPreset) -> Self {
2764
        match value {
2765
            RoomPreset::PrivateChat => Self::PrivateChat,
2766
            RoomPreset::PublicChat => Self::PublicChat,
2767
            RoomPreset::TrustedPrivateChat => Self::TrustedPrivateChat,
2768
        }
2769
    }
2770
}
2771
2772
#[derive(uniffi::Record)]
2773
pub struct Session {
2774
    // Same fields as the Session type in matrix-sdk, just simpler types
2775
    /// The access token used for this session.
2776
    pub access_token: String,
2777
    /// The token used for [refreshing the access token], if any.
2778
    ///
2779
    /// [refreshing the access token]: https://spec.matrix.org/v1.3/client-server-api/#refreshing-access-tokens
2780
    pub refresh_token: Option<String>,
2781
    /// The user the access token was issued for.
2782
    pub user_id: String,
2783
    /// The ID of the client device.
2784
    pub device_id: String,
2785
2786
    // FFI-only fields (for now)
2787
    /// The URL for the homeserver used for this session.
2788
    pub homeserver_url: String,
2789
    /// Additional data for this session if OpenID Connect was used for
2790
    /// authentication.
2791
    pub oauth_data: Option<String>,
2792
    /// The sliding sync version used for this session.
2793
    pub sliding_sync_version: SlidingSyncVersion,
2794
}
2795
2796
impl Session {
2797
    fn new(
2798
        auth_api: AuthApi,
2799
        homeserver_url: String,
2800
        sliding_sync_version: SlidingSyncVersion,
2801
    ) -> Result<Session, ClientError> {
2802
        match auth_api {
2803
            // Build the session from the regular Matrix Auth Session.
2804
            AuthApi::Matrix(a) => {
2805
                let matrix_sdk::authentication::matrix::MatrixSession {
2806
                    meta: matrix_sdk::SessionMeta { user_id, device_id },
2807
                    tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2808
                } = a.session().context("Missing session")?;
2809
2810
                Ok(Session {
2811
                    access_token,
2812
                    refresh_token,
2813
                    user_id: user_id.to_string(),
2814
                    device_id: device_id.to_string(),
2815
                    homeserver_url,
2816
                    oauth_data: None,
2817
                    sliding_sync_version,
2818
                })
2819
            }
2820
            // Build the session from the OAuth UserSession.
2821
            AuthApi::OAuth(api) => {
2822
                let matrix_sdk::authentication::oauth::UserSession {
2823
                    meta: matrix_sdk::SessionMeta { user_id, device_id },
2824
                    tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2825
                } = api.user_session().context("Missing session")?;
2826
                let client_id = api.client_id().context("OAuth client ID is missing.")?.clone();
2827
                let oauth_data = OAuthSessionData { client_id };
2828
2829
                let oauth_data_string = serde_json::to_string(&oauth_data).ok();
2830
                Ok(Session {
2831
                    access_token,
2832
                    refresh_token,
2833
                    user_id: user_id.to_string(),
2834
                    device_id: device_id.to_string(),
2835
                    homeserver_url,
2836
                    oauth_data: oauth_data_string,
2837
                    sliding_sync_version,
2838
                })
2839
            }
2840
            _ => Err(anyhow!("Unknown authentication API").into()),
2841
        }
2842
    }
2843
2844
    fn into_tokens(self) -> matrix_sdk::SessionTokens {
2845
        SessionTokens { access_token: self.access_token, refresh_token: self.refresh_token }
2846
    }
2847
}
2848
2849
impl TryFrom<Session> for AuthSession {
2850
    type Error = ClientError;
2851
    fn try_from(value: Session) -> Result<Self, Self::Error> {
2852
        let Session {
2853
            access_token,
2854
            refresh_token,
2855
            user_id,
2856
            device_id,
2857
            homeserver_url: _,
2858
            oauth_data: oauth_data_string,
2859
            sliding_sync_version: _,
2860
        } = value;
2861
2862
        if let Some(oauth_data_string) = oauth_data_string {
2863
            // Create an OAuth Session.
2864
            let oauth_data = serde_json::from_str::<OAuthSessionData>(&oauth_data_string)?;
2865
2866
            let user_session = matrix_sdk::authentication::oauth::UserSession {
2867
                meta: matrix_sdk::SessionMeta {
2868
                    user_id: user_id.try_into()?,
2869
                    device_id: device_id.into(),
2870
                },
2871
                tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2872
            };
2873
2874
            let session = OAuthSession { client_id: oauth_data.client_id, user: user_session };
2875
2876
            Ok(AuthSession::OAuth(session.into()))
2877
        } else {
2878
            // Create a regular Matrix Session.
2879
            let session = matrix_sdk::authentication::matrix::MatrixSession {
2880
                meta: matrix_sdk::SessionMeta {
2881
                    user_id: user_id.try_into()?,
2882
                    device_id: device_id.into(),
2883
                },
2884
                tokens: matrix_sdk::SessionTokens { access_token, refresh_token },
2885
            };
2886
2887
            Ok(AuthSession::Matrix(session))
2888
        }
2889
    }
2890
}
2891
2892
/// Represents a client registration against an OAuth authentication issuer.
2893
#[derive(Serialize, Deserialize)]
2894
pub(crate) struct OAuthSessionData {
2895
    client_id: ClientId,
2896
}
2897
2898
#[derive(uniffi::Enum)]
2899
pub enum AccountManagementAction {
2900
    Profile,
2901
    DevicesList,
2902
    DeviceView { device_id: String },
2903
    DeviceDelete { device_id: String },
2904
    AccountDeactivate,
2905
    CrossSigningReset,
2906
}
2907
2908
impl<'a> From<&'a AccountManagementAction> for AccountManagementActionData<'a> {
2909
    fn from(value: &'a AccountManagementAction) -> Self {
2910
        match value {
2911
            AccountManagementAction::Profile => Self::Profile,
2912
            AccountManagementAction::DevicesList => Self::DevicesList,
2913
            AccountManagementAction::DeviceView { device_id } => {
2914
                Self::DeviceView(DeviceViewData::new(device_id.as_str().into()))
2915
            }
2916
            AccountManagementAction::DeviceDelete { device_id } => {
2917
                Self::DeviceDelete(DeviceDeleteData::new(device_id.as_str().into()))
2918
            }
2919
            AccountManagementAction::AccountDeactivate => Self::AccountDeactivate,
2920
            AccountManagementAction::CrossSigningReset => Self::CrossSigningReset,
2921
        }
2922
    }
2923
}
2924
2925
#[matrix_sdk_ffi_macros::export]
2926
fn gen_transaction_id() -> String {
2927
    TransactionId::new().to_string()
2928
}
2929
2930
/// A file handle that takes ownership of a media file on disk. When the handle
2931
/// is dropped, the file will be removed from the disk.
2932
#[derive(uniffi::Object)]
2933
pub struct MediaFileHandle {
2934
    #[cfg(not(target_family = "wasm"))]
2935
    inner: std::sync::RwLock<Option<SdkMediaFileHandle>>,
2936
}
2937
2938
impl MediaFileHandle {
2939
    #[cfg(not(target_family = "wasm"))]
2940
    fn new(handle: SdkMediaFileHandle) -> Self {
2941
        Self { inner: std::sync::RwLock::new(Some(handle)) }
2942
    }
2943
}
2944
2945
#[matrix_sdk_ffi_macros::export]
2946
impl MediaFileHandle {
2947
    /// Get the media file's path.
2948
    pub fn path(&self) -> Result<String, ClientError> {
2949
        #[cfg(not(target_family = "wasm"))]
2950
        return Ok(self
2951
            .inner
2952
            .read()
2953
            .unwrap()
2954
            .as_ref()
2955
            .context("MediaFileHandle must not be used after calling persist")?
2956
            .path()
2957
            .to_str()
2958
            .unwrap()
2959
            .to_owned());
2960
        #[cfg(target_family = "wasm")]
2961
        Err(ClientError::Generic {
2962
            msg: "MediaFileHandle.path() is not supported on WASM targets".to_string(),
2963
            details: None,
2964
        })
2965
    }
2966
2967
    pub fn persist(&self, path: String) -> Result<bool, ClientError> {
2968
        #[cfg(not(target_family = "wasm"))]
2969
        {
2970
            let mut guard = self.inner.write().unwrap();
2971
            Ok(
2972
                match guard
2973
                    .take()
2974
                    .context("MediaFileHandle was already persisted")?
2975
                    .persist(path.as_ref())
2976
                {
2977
                    Ok(_) => true,
2978
                    Err(e) => {
2979
                        *guard = Some(e.file);
2980
                        false
2981
                    }
2982
                },
2983
            )
2984
        }
2985
        #[cfg(target_family = "wasm")]
2986
        Err(ClientError::Generic {
2987
            msg: "MediaFileHandle.persist() is not supported on WASM targets".to_string(),
2988
            details: None,
2989
        })
2990
    }
2991
}
2992
2993
#[derive(Clone, uniffi::Enum)]
2994
pub enum SlidingSyncVersion {
2995
    None,
2996
    Native,
2997
}
2998
2999
impl From<SdkSlidingSyncVersion> for SlidingSyncVersion {
3000
    fn from(value: SdkSlidingSyncVersion) -> Self {
3001
        match value {
3002
            SdkSlidingSyncVersion::None => Self::None,
3003
            SdkSlidingSyncVersion::Native => Self::Native,
3004
        }
3005
    }
3006
}
3007
3008
impl TryFrom<SlidingSyncVersion> for SdkSlidingSyncVersion {
3009
    type Error = ClientError;
3010
3011
    fn try_from(value: SlidingSyncVersion) -> Result<Self, Self::Error> {
3012
        Ok(match value {
3013
            SlidingSyncVersion::None => Self::None,
3014
            SlidingSyncVersion::Native => Self::Native,
3015
        })
3016
    }
3017
}
3018
3019
#[derive(Clone, uniffi::Enum)]
3020
pub enum OAuthPrompt {
3021
    /// The Authorization Server should prompt the End-User to create a user
3022
    /// account.
3023
    ///
3024
    /// Defined in [Initiating User Registration via OpenID Connect](https://openid.net/specs/openid-connect-prompt-create-1_0.html).
3025
    Create,
3026
3027
    /// The Authorization Server should prompt the End-User for
3028
    /// reauthentication.
3029
    Login,
3030
3031
    /// The Authorization Server should prompt the End-User for consent before
3032
    /// returning information to the Client.
3033
    Consent,
3034
3035
    /// An unknown value.
3036
    Unknown { value: String },
3037
}
3038
3039
impl From<RumaOAuthPrompt> for OAuthPrompt {
3040
    fn from(value: RumaOAuthPrompt) -> Self {
3041
        match value {
3042
            RumaOAuthPrompt::Create => Self::Create,
3043
            value => match value.as_str() {
3044
                "consent" => Self::Consent,
3045
                "login" => Self::Login,
3046
                _ => Self::Unknown { value: value.to_string() },
3047
            },
3048
        }
3049
    }
3050
}
3051
3052
impl From<OAuthPrompt> for RumaOAuthPrompt {
3053
    fn from(value: OAuthPrompt) -> Self {
3054
        match value {
3055
            OAuthPrompt::Create => Self::Create,
3056
            OAuthPrompt::Consent => Self::from("consent"),
3057
            OAuthPrompt::Login => Self::from("login"),
3058
            OAuthPrompt::Unknown { value } => value.into(),
3059
        }
3060
    }
3061
}
3062
3063
/// The rule used for users wishing to join this room.
3064
#[derive(Debug, Clone, uniffi::Enum)]
3065
pub enum JoinRule {
3066
    /// Anyone can join the room without any prior action.
3067
    Public,
3068
3069
    /// A user who wishes to join the room must first receive an invite to the
3070
    /// room from someone already inside of the room.
3071
    Invite,
3072
3073
    /// Users can join the room if they are invited, or they can request an
3074
    /// invite to the room.
3075
    ///
3076
    /// They can be allowed (invited) or denied (kicked/banned) access.
3077
    Knock,
3078
3079
    /// Reserved but not yet implemented by the Matrix specification.
3080
    Private,
3081
3082
    /// Users can join the room if they are invited, or if they meet any of the
3083
    /// conditions described in a set of [`AllowRule`]s.
3084
    Restricted { rules: Vec<AllowRule> },
3085
3086
    /// Users can join the room if they are invited, or if they meet any of the
3087
    /// conditions described in a set of [`AllowRule`]s, or they can request
3088
    /// an invite to the room.
3089
    KnockRestricted { rules: Vec<AllowRule> },
3090
3091
    /// A custom join rule, up for interpretation by the consumer.
3092
    Custom {
3093
        /// The string representation for this custom rule.
3094
        repr: String,
3095
    },
3096
}
3097
3098
/// An allow rule which defines a condition that allows joining a room.
3099
#[derive(Debug, Clone, uniffi::Enum)]
3100
pub enum AllowRule {
3101
    /// Only a member of the `room_id` Room can join the one this rule is used
3102
    /// in.
3103
    RoomMembership { room_id: String },
3104
3105
    /// A custom allow rule implementation, containing its JSON representation
3106
    /// as a `String`.
3107
    Custom { json: String },
3108
}
3109
3110
impl TryFrom<JoinRule> for RumaJoinRule {
3111
    type Error = ClientError;
3112
3113
    fn try_from(value: JoinRule) -> Result<Self, Self::Error> {
3114
        match value {
3115
            JoinRule::Public => Ok(Self::Public),
3116
            JoinRule::Invite => Ok(Self::Invite),
3117
            JoinRule::Knock => Ok(Self::Knock),
3118
            JoinRule::Private => Ok(Self::Private),
3119
            JoinRule::Restricted { rules } => {
3120
                let rules = ruma_allow_rules_from_ffi(rules)?;
3121
                Ok(Self::Restricted(ruma::events::room::join_rules::Restricted::new(rules)))
3122
            }
3123
            JoinRule::KnockRestricted { rules } => {
3124
                let rules = ruma_allow_rules_from_ffi(rules)?;
3125
                Ok(Self::KnockRestricted(ruma::events::room::join_rules::Restricted::new(rules)))
3126
            }
3127
            JoinRule::Custom { repr } => Ok(serde_json::from_str(&repr)?),
3128
        }
3129
    }
3130
}
3131
3132
fn ruma_allow_rules_from_ffi(value: Vec<AllowRule>) -> Result<Vec<RumaAllowRule>, ClientError> {
3133
    let mut ret = Vec::with_capacity(value.len());
3134
    for rule in value {
3135
        let rule: Result<RumaAllowRule, ClientError> = rule.try_into();
3136
        match rule {
3137
            Ok(rule) => ret.push(rule),
3138
            Err(error) => return Err(error),
3139
        }
3140
    }
3141
    Ok(ret)
3142
}
3143
3144
impl TryFrom<AllowRule> for RumaAllowRule {
3145
    type Error = ClientError;
3146
3147
    fn try_from(value: AllowRule) -> Result<Self, Self::Error> {
3148
        match value {
3149
            AllowRule::RoomMembership { room_id } => {
3150
                let room_id = RoomId::parse(room_id)?;
3151
                Ok(Self::RoomMembership(ruma::room::RoomMembership::new(room_id)))
3152
            }
3153
            AllowRule::Custom { json } => Ok(serde_json::from_str(&json)?),
3154
        }
3155
    }
3156
}
3157
3158
impl TryFrom<RumaJoinRule> for JoinRule {
3159
    type Error = String;
3160
    fn try_from(value: RumaJoinRule) -> Result<Self, Self::Error> {
3161
        match value {
3162
            RumaJoinRule::Knock => Ok(JoinRule::Knock),
3163
            RumaJoinRule::Public => Ok(JoinRule::Public),
3164
            RumaJoinRule::Private => Ok(JoinRule::Private),
3165
            RumaJoinRule::KnockRestricted(restricted) => {
3166
                let rules = restricted.allow.into_iter().map(TryInto::try_into).collect::<Result<
3167
                    Vec<_>,
3168
                    Self::Error,
3169
                >>(
3170
                )?;
3171
                Ok(JoinRule::KnockRestricted { rules })
3172
            }
3173
            RumaJoinRule::Restricted(restricted) => {
3174
                let rules = restricted.allow.into_iter().map(TryInto::try_into).collect::<Result<
3175
                    Vec<_>,
3176
                    Self::Error,
3177
                >>(
3178
                )?;
3179
                Ok(JoinRule::Restricted { rules })
3180
            }
3181
            RumaJoinRule::Invite => Ok(JoinRule::Invite),
3182
            RumaJoinRule::_Custom(_) => Ok(JoinRule::Custom { repr: value.as_str().to_owned() }),
3183
            _ => Err(format!("Unknown JoinRule: {value:?}")),
3184
        }
3185
    }
3186
}
3187
3188
impl TryFrom<RumaAllowRule> for AllowRule {
3189
    type Error = String;
3190
    fn try_from(value: RumaAllowRule) -> Result<Self, Self::Error> {
3191
        match value {
3192
            RumaAllowRule::RoomMembership(membership) => {
3193
                Ok(AllowRule::RoomMembership { room_id: membership.room_id.to_string() })
3194
            }
3195
            RumaAllowRule::_Custom(repr) => {
3196
                let json = serde_json::to_string(&repr)
3197
                    .map_err(|e| format!("Couldn't serialize custom AllowRule: {e:?}"))?;
3198
                Ok(Self::Custom { json })
3199
            }
3200
            _ => Err(format!("Invalid AllowRule: {value:?}")),
3201
        }
3202
    }
3203
}
3204
3205
/// Contains the disk size of the different stores, if known. It won't be
3206
/// available for in-memory stores.
3207
#[derive(Debug, Clone, uniffi::Record)]
3208
pub struct StoreSizes {
3209
    /// The size of the CryptoStore.
3210
    crypto_store: Option<u64>,
3211
    /// The size of the StateStore.
3212
    state_store: Option<u64>,
3213
    /// The size of the EventCacheStore.
3214
    event_cache_store: Option<u64>,
3215
    /// The size of the MediaStore.
3216
    media_store: Option<u64>,
3217
}
3218
3219
impl From<matrix_sdk::StoreSizes> for StoreSizes {
3220
    fn from(value: matrix_sdk::StoreSizes) -> Self {
3221
        Self {
3222
            crypto_store: value.crypto_store.map(|v| v as u64),
3223
            state_store: value.state_store.map(|v| v as u64),
3224
            event_cache_store: value.event_cache_store.map(|v| v as u64),
3225
            media_store: value.media_store.map(|v| v as u64),
3226
        }
3227
    }
3228
}
3229
3230
#[derive(uniffi::Object)]
3231
pub struct HomeserverCapabilities {
3232
    inner: matrix_sdk::HomeserverCapabilities,
3233
}
3234
3235
impl HomeserverCapabilities {
3236
    pub(crate) fn new(capabilities: matrix_sdk::HomeserverCapabilities) -> Self {
3237
        Self { inner: capabilities }
3238
    }
3239
}
3240
3241
#[matrix_sdk_ffi_macros::export]
3242
impl HomeserverCapabilities {
3243
    pub async fn refresh(&self) -> Result<(), ClientError> {
3244
        Ok(self.inner.refresh().await?)
3245
    }
3246
3247
    pub async fn can_change_password(&self) -> Result<bool, ClientError> {
3248
        Ok(self.inner.can_change_password().await?)
3249
    }
3250
3251
    pub async fn can_change_displayname(&self) -> Result<bool, ClientError> {
3252
        Ok(self.inner.can_change_displayname().await?)
3253
    }
3254
3255
    pub async fn can_change_avatar(&self) -> Result<bool, ClientError> {
3256
        Ok(self.inner.can_change_avatar().await?)
3257
    }
3258
3259
    pub async fn can_change_thirdparty_ids(&self) -> Result<bool, ClientError> {
3260
        Ok(self.inner.can_change_thirdparty_ids().await?)
3261
    }
3262
3263
    pub async fn can_get_login_token(&self) -> Result<bool, ClientError> {
3264
        Ok(self.inner.can_get_login_token().await?)
3265
    }
3266
3267
    pub async fn extended_profile_fields(&self) -> Result<ExtendedProfileFields, ClientError> {
3268
        let profile_fields = self.inner.extended_profile_fields().await?;
3269
        Ok(ExtendedProfileFields {
3270
            enabled: profile_fields.enabled,
3271
            allowed: profile_fields
3272
                .allowed
3273
                .unwrap_or_default()
3274
                .iter()
3275
                .map(ToString::to_string)
3276
                .collect(),
3277
            disallowed: profile_fields
3278
                .disallowed
3279
                .unwrap_or_default()
3280
                .iter()
3281
                .map(ToString::to_string)
3282
                .collect(),
3283
        })
3284
    }
3285
3286
    pub async fn forgets_room_when_leaving(&self) -> Result<bool, ClientError> {
3287
        Ok(self.inner.forgets_room_when_leaving().await?)
3288
    }
3289
}
3290
3291
#[derive(uniffi::Record)]
3292
pub struct ExtendedProfileFields {
3293
    pub enabled: bool,
3294
    pub allowed: Vec<String>,
3295
    pub disallowed: Vec<String>,
3296
}
3297
3298
#[cfg(test)]
3299
mod tests {
3300
    use std::time::Duration;
3301
3302
    use ruma::{
3303
        ServerName,
3304
        api::client::room::{Visibility, create_room},
3305
        authentication::TokenType,
3306
        events::StateEventType,
3307
        room::RoomType,
3308
    };
3309
3310
    use crate::{
3311
        client::{CreateRoomParameters, JoinRule, OpenIdToken, RoomPreset, RoomVisibility},
3312
        room::RoomHistoryVisibility,
3313
    };
3314
3315
    #[test]
3316
    fn test_create_room_parameters_mapping() {
3317
        let params = CreateRoomParameters {
3318
            name: Some("A room".to_owned()),
3319
            topic: Some("A topic".to_owned()),
3320
            is_encrypted: true,
3321
            is_direct: true,
3322
            visibility: RoomVisibility::Public,
3323
            preset: RoomPreset::PublicChat,
3324
            invite: Some(vec!["@user:example.com".to_owned()]),
3325
            avatar: Some("http://example.com/avatar.jpg".to_owned()),
3326
            power_level_content_override: None,
3327
            join_rule_override: Some(JoinRule::Knock),
3328
            history_visibility_override: Some(RoomHistoryVisibility::Shared),
3329
            canonical_alias: Some("#a-room:example.com".to_owned()),
3330
            is_space: true,
3331
        };
3332
3333
        let request: create_room::v3::Request =
3334
            params.try_into().expect("CreateRoomParameters couldn't be transformed into a Request");
3335
        let initial_state = request
3336
            .initial_state
3337
            .iter()
3338
            .map(|raw| raw.deserialize().expect("Initial state event failed to deserialize"))
3339
            .collect::<Vec<_>>();
3340
3341
        assert_eq!(request.name, Some("A room".to_owned()));
3342
        assert_eq!(request.topic, Some("A topic".to_owned()));
3343
        assert!(initial_state.iter().any(|e| e.event_type() == StateEventType::RoomEncryption));
3344
        assert!(request.is_direct);
3345
        assert_eq!(request.visibility, Visibility::Public);
3346
        assert_eq!(request.preset, Some(create_room::v3::RoomPreset::PublicChat));
3347
        assert_eq!(request.invite.len(), 1);
3348
        assert!(initial_state.iter().any(|e| e.event_type() == StateEventType::RoomAvatar));
3349
        assert!(initial_state.iter().any(|e| e.event_type() == StateEventType::RoomJoinRules));
3350
        assert!(
3351
            initial_state.iter().any(|e| e.event_type() == StateEventType::RoomHistoryVisibility)
3352
        );
3353
        assert_eq!(request.room_alias_name, Some("#a-room:example.com".to_owned()));
3354
3355
        let room_type = request
3356
            .creation_content
3357
            .expect("Creation content is missing")
3358
            .deserialize()
3359
            .expect("Creation content can't be deserialized")
3360
            .room_type;
3361
        assert_eq!(room_type, Some(RoomType::Space));
3362
    }
3363
3364
    #[test]
3365
    fn test_openid_token_mapping() {
3366
        let response = ruma::api::client::account::request_openid_token::v3::Response::new(
3367
            "open-id-token".to_owned(),
3368
            TokenType::Bearer,
3369
            ServerName::parse("example.com").expect("valid server name"),
3370
            Duration::from_secs(3_600),
3371
        );
3372
3373
        let token: OpenIdToken = response.into();
3374
3375
        assert_eq!(token.access_token, "open-id-token");
3376
        assert_eq!(token.token_type, "Bearer");
3377
        assert_eq!(token.matrix_server_name, "example.com");
3378
        assert_eq!(token.expires_in_seconds, 3_600);
3379
    }
3380
3381
    /// Dropping an FFI [`Client`] on a non-tokio thread must not panic.
3382
    ///
3383
    /// Regression test: `Client.inner` is wrapped in [`AsyncRuntimeDropped`]
3384
    /// precisely because dropping a sqlite-backed [`MatrixClient`] outside a
3385
    /// tokio runtime context causes `SyncWrapper<rusqlite::Connection>::drop`
3386
    /// to call `tokio::task::spawn_blocking`, which panics and triggers
3387
    /// SIGABRT. This test guards against accidentally removing that wrapper.
3388
    #[cfg(not(target_family = "wasm"))]
3389
    #[tokio::test]
3390
    async fn client_drop_on_non_tokio_thread_does_not_panic() {
3391
        use std::time::Duration;
3392
3393
        use matrix_sdk::{Client, config::RequestConfig};
3394
        use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
3395
        use tempfile::tempdir;
3396
3397
        let dir = tempdir().unwrap();
3398
3399
        // SingleProcess lock avoids the session-delegate requirement in
3400
        // `Client::new`, keeping the test self-contained.
3401
        let sdk_client = Client::builder()
3402
            .homeserver_url("https://example.com")
3403
            .request_config(RequestConfig::new().disable_retry())
3404
            .sqlite_store(dir.path(), None)
3405
            .cross_process_store_config(CrossProcessLockConfig::SingleProcess)
3406
            .build()
3407
            .await
3408
            .unwrap();
3409
3410
        // Allow background init tasks to complete; after this the only strong
3411
        // reference to `ClientInner` is the local `sdk_client` variable.
3412
        tokio::time::sleep(Duration::from_secs(1)).await;
3413
3414
        let ffi_client = super::Client::new(sdk_client.clone(), None, None).await.unwrap();
3415
3416
        // Dropping `sdk_client` leaves `ffi_client` as the sole Arc holder of
3417
        // `ClientInner` — mirroring what happens when the last JS reference to
3418
        // a Client is garbage-collected on the Hermes JS thread.
3419
        drop(sdk_client);
3420
3421
        // Simulate Hermes GC on the JS thread (a non-tokio thread).
3422
        // Without `AsyncRuntimeDropped` wrapping `Client.inner` this SIGABRT.
3423
        std::thread::spawn(move || drop(ffi_client))
3424
            .join()
3425
            .expect("Client::drop panicked on a non-tokio thread");
3426
    }
3427
}