atagen

macltok

public
Star0
Watch0
6db49c0ecf71cdcf132041e6ca6e135c711a0b33
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/account.rs

62fe32759529atagen2026-09-26 08:50 UTC1
#[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC2
use std::path::PathBuf;
f12377bce9b9atagen2026-09-26 12:32 UTC3
#[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC4
use std::sync::atomic::{AtomicBool, Ordering};
713205a95af2atagen2026-09-26 09:24 UTC5
use std::{collections::HashMap, sync::Arc};
96fdf90aacd6atagen2026-09-08 08:24 UTC6
96fdf90aacd6atagen2026-09-08 08:24 UTC7
use matrix_sdk::{
f12377bce9b9atagen2026-09-26 12:32 UTC8
    Client, ClientBuilder, SendOutsideWasm, SessionChange, SessionMeta, SyncOutsideWasm,
f12377bce9b9atagen2026-09-26 12:32 UTC9
    ThreadingSupport,
96fdf90aacd6atagen2026-09-08 08:24 UTC10
    authentication::{
96fdf90aacd6atagen2026-09-08 08:24 UTC11
        AuthSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC12
        matrix::MatrixSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC13
        oauth::{
96fdf90aacd6atagen2026-09-08 08:24 UTC14
            ClientId, ClientRegistrationData, CsrfToken, OAuthSession, UserSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC15
            registration::{ApplicationType, ClientMetadata, Localized, OAuthGrantType},
96fdf90aacd6atagen2026-09-08 08:24 UTC16
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC17
    },
62fe32759529atagen2026-09-26 08:50 UTC18
    cross_process_lock::CrossProcessLockConfig,
96fdf90aacd6atagen2026-09-08 08:24 UTC19
    encryption::{BackupDownloadStrategy, EncryptionSettings},
6f1d6591c10datagen2026-09-26 09:52 UTC20
    ruma::{OwnedDeviceId, OwnedUserId, serde::Raw},
96fdf90aacd6atagen2026-09-08 08:24 UTC21
};
96fdf90aacd6atagen2026-09-08 08:24 UTC22
use serde::{Deserialize, Serialize};
96fdf90aacd6atagen2026-09-08 08:24 UTC23
use thiserror::Error;
f12377bce9b9atagen2026-09-26 12:32 UTC24
use tokio::sync::{broadcast, mpsc};
96fdf90aacd6atagen2026-09-08 08:24 UTC25
use url::Url;
96fdf90aacd6atagen2026-09-08 08:24 UTC26
713205a95af2atagen2026-09-26 09:24 UTC27
use crate::{
f12377bce9b9atagen2026-09-26 12:32 UTC28
    ClientHandle, Engine, EngineConfig, EngineError, ErrorKind, EventStream, SessionStore,
f12377bce9b9atagen2026-09-26 12:32 UTC29
    engine::Persistence, error::SourceError,
713205a95af2atagen2026-09-26 09:24 UTC30
};
96fdf90aacd6atagen2026-09-08 08:24 UTC31
f12377bce9b9atagen2026-09-26 12:32 UTC32
/// a logged in client and the `SessionStore` its record lives in. the engine it builds
f12377bce9b9atagen2026-09-26 12:32 UTC33
/// saves every token rotation there, unless told `without_persistence`
96fdf90aacd6atagen2026-09-08 08:24 UTC34
pub struct Session {
96fdf90aacd6atagen2026-09-08 08:24 UTC35
    client: Client,
f12377bce9b9atagen2026-09-26 12:32 UTC36
    tracking: Tracking,
f12377bce9b9atagen2026-09-26 12:32 UTC37
}
f12377bce9b9atagen2026-09-26 12:32 UTC38
f12377bce9b9atagen2026-09-26 12:32 UTC39
/// taken as soon as the client exists, since the sdk can refresh before there's an engine
f12377bce9b9atagen2026-09-26 12:32 UTC40
struct Tracking {
f12377bce9b9atagen2026-09-26 12:32 UTC41
    session_changes: broadcast::Receiver<SessionChange>,
f12377bce9b9atagen2026-09-26 12:32 UTC42
    persistence: Option<Persistence>,
f12377bce9b9atagen2026-09-26 12:32 UTC43
    #[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC44
    inline: Arc<AtomicBool>,
96fdf90aacd6atagen2026-09-08 08:24 UTC45
}
96fdf90aacd6atagen2026-09-08 08:24 UTC46
31ce2453c82fatagen2026-09-24 13:07 UTC47
#[non_exhaustive]
96fdf90aacd6atagen2026-09-08 08:24 UTC48
pub struct PasswordLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC49
    pub homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC50
    pub username: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC51
    pub password: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC52
    pub device_display_name: Option<String>,
fa9114fcf75fatagen2026-09-24 12:51 UTC53
    /// set after a soft logout so the store's keys still belong to us
fa9114fcf75fatagen2026-09-24 12:51 UTC54
    pub device_id: Option<OwnedDeviceId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC55
}
96fdf90aacd6atagen2026-09-08 08:24 UTC56
31ce2453c82fatagen2026-09-24 13:07 UTC57
impl PasswordLogin {
31ce2453c82fatagen2026-09-24 13:07 UTC58
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC59
    pub fn new(
31ce2453c82fatagen2026-09-24 13:07 UTC60
        homeserver: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC61
        username: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC62
        password: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC63
    ) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC64
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC65
            homeserver: homeserver.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC66
            username: username.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC67
            password: password.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC68
            device_display_name: None,
31ce2453c82fatagen2026-09-24 13:07 UTC69
            device_id: None,
31ce2453c82fatagen2026-09-24 13:07 UTC70
        }
31ce2453c82fatagen2026-09-24 13:07 UTC71
    }
31ce2453c82fatagen2026-09-24 13:07 UTC72
31ce2453c82fatagen2026-09-24 13:07 UTC73
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC74
    pub fn with_device_display_name(mut self, name: impl Into<String>) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC75
        self.device_display_name = Some(name.into());
31ce2453c82fatagen2026-09-24 13:07 UTC76
        self
31ce2453c82fatagen2026-09-24 13:07 UTC77
    }
31ce2453c82fatagen2026-09-24 13:07 UTC78
31ce2453c82fatagen2026-09-24 13:07 UTC79
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC80
    pub fn with_device_id(mut self, device_id: OwnedDeviceId) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC81
        self.device_id = Some(device_id);
31ce2453c82fatagen2026-09-24 13:07 UTC82
        self
31ce2453c82fatagen2026-09-24 13:07 UTC83
    }
31ce2453c82fatagen2026-09-24 13:07 UTC84
}
31ce2453c82fatagen2026-09-24 13:07 UTC85
31ce2453c82fatagen2026-09-24 13:07 UTC86
#[non_exhaustive]
96fdf90aacd6atagen2026-09-08 08:24 UTC87
pub struct OAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC88
    pub homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC89
    pub redirect_uri: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC90
    pub client_uri: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC91
    pub client_name: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC92
    pub client_id: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC93
    pub device_id: Option<OwnedDeviceId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC94
    pub application_type: OAuthApplicationType,
96fdf90aacd6atagen2026-09-08 08:24 UTC95
}
96fdf90aacd6atagen2026-09-08 08:24 UTC96
31ce2453c82fatagen2026-09-24 13:07 UTC97
impl OAuthLogin {
31ce2453c82fatagen2026-09-24 13:07 UTC98
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC99
    pub fn new(
31ce2453c82fatagen2026-09-24 13:07 UTC100
        homeserver: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC101
        redirect_uri: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC102
        client_uri: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC103
        application_type: OAuthApplicationType,
31ce2453c82fatagen2026-09-24 13:07 UTC104
    ) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC105
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC106
            homeserver: homeserver.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC107
            redirect_uri: redirect_uri.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC108
            client_uri: client_uri.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC109
            client_name: None,
31ce2453c82fatagen2026-09-24 13:07 UTC110
            client_id: None,
31ce2453c82fatagen2026-09-24 13:07 UTC111
            device_id: None,
31ce2453c82fatagen2026-09-24 13:07 UTC112
            application_type,
31ce2453c82fatagen2026-09-24 13:07 UTC113
        }
31ce2453c82fatagen2026-09-24 13:07 UTC114
    }
31ce2453c82fatagen2026-09-24 13:07 UTC115
31ce2453c82fatagen2026-09-24 13:07 UTC116
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC117
    pub fn with_client_name(mut self, name: impl Into<String>) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC118
        self.client_name = Some(name.into());
31ce2453c82fatagen2026-09-24 13:07 UTC119
        self
31ce2453c82fatagen2026-09-24 13:07 UTC120
    }
31ce2453c82fatagen2026-09-24 13:07 UTC121
31ce2453c82fatagen2026-09-24 13:07 UTC122
    /// a statically registered client id, skipping dynamic registration
31ce2453c82fatagen2026-09-24 13:07 UTC123
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC124
    pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC125
        self.client_id = Some(client_id.into());
31ce2453c82fatagen2026-09-24 13:07 UTC126
        self
31ce2453c82fatagen2026-09-24 13:07 UTC127
    }
31ce2453c82fatagen2026-09-24 13:07 UTC128
31ce2453c82fatagen2026-09-24 13:07 UTC129
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC130
    pub fn with_device_id(mut self, device_id: OwnedDeviceId) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC131
        self.device_id = Some(device_id);
31ce2453c82fatagen2026-09-24 13:07 UTC132
        self
31ce2453c82fatagen2026-09-24 13:07 UTC133
    }
31ce2453c82fatagen2026-09-24 13:07 UTC134
}
31ce2453c82fatagen2026-09-24 13:07 UTC135
96fdf90aacd6atagen2026-09-08 08:24 UTC136
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31ce2453c82fatagen2026-09-24 13:07 UTC137
#[non_exhaustive]
96fdf90aacd6atagen2026-09-08 08:24 UTC138
pub enum OAuthApplicationType {
96fdf90aacd6atagen2026-09-08 08:24 UTC139
    Native,
96fdf90aacd6atagen2026-09-08 08:24 UTC140
    Web,
96fdf90aacd6atagen2026-09-08 08:24 UTC141
}
96fdf90aacd6atagen2026-09-08 08:24 UTC142
96fdf90aacd6atagen2026-09-08 08:24 UTC143
pub struct PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC144
    client: Client,
713205a95af2atagen2026-09-26 09:24 UTC145
    sessions: Arc<dyn SessionStore>,
f12377bce9b9atagen2026-09-26 12:32 UTC146
    tracking: Option<Tracking>,
96fdf90aacd6atagen2026-09-08 08:24 UTC147
    authorization_url: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC148
    state: Option<CsrfToken>,
96fdf90aacd6atagen2026-09-08 08:24 UTC149
}
96fdf90aacd6atagen2026-09-08 08:24 UTC150
96fdf90aacd6atagen2026-09-08 08:24 UTC151
impl PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC152
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC153
    pub fn authorization_url(&self) -> &str {
96fdf90aacd6atagen2026-09-08 08:24 UTC154
        &self.authorization_url
96fdf90aacd6atagen2026-09-08 08:24 UTC155
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC156
96fdf90aacd6atagen2026-09-08 08:24 UTC157
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC158
    pub fn registered_client_id(&self) -> String {
96fdf90aacd6atagen2026-09-08 08:24 UTC159
        self.client
96fdf90aacd6atagen2026-09-08 08:24 UTC160
            .oauth()
96fdf90aacd6atagen2026-09-08 08:24 UTC161
            .client_id()
96fdf90aacd6atagen2026-09-08 08:24 UTC162
            .expect("OAuth client was registered before authorization")
96fdf90aacd6atagen2026-09-08 08:24 UTC163
            .as_str()
96fdf90aacd6atagen2026-09-08 08:24 UTC164
            .to_owned()
96fdf90aacd6atagen2026-09-08 08:24 UTC165
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC166
96fdf90aacd6atagen2026-09-08 08:24 UTC167
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC168
    pub const fn is_pending(&self) -> bool {
96fdf90aacd6atagen2026-09-08 08:24 UTC169
        self.state.is_some()
96fdf90aacd6atagen2026-09-08 08:24 UTC170
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC171
96fdf90aacd6atagen2026-09-08 08:24 UTC172
    pub async fn finish(&mut self, callback_url: &str) -> Result<Session, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC173
        let callback_url = Url::parse(callback_url)
96fdf90aacd6atagen2026-09-08 08:24 UTC174
            .map_err(|error| account_error("parsing the OAuth callback URL", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC175
        let state = self
96fdf90aacd6atagen2026-09-08 08:24 UTC176
            .state
96fdf90aacd6atagen2026-09-08 08:24 UTC177
            .take()
96fdf90aacd6atagen2026-09-08 08:24 UTC178
            .ok_or(AccountError::OAuthCallbackAlreadySubmitted)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC179
        let oauth = self.client.oauth();
96fdf90aacd6atagen2026-09-08 08:24 UTC180
        if let Err(error) = oauth.finish_login(callback_url.into()).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC181
            oauth.abort_login(&state).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC182
            return Err(account_error("OAuth login", error));
96fdf90aacd6atagen2026-09-08 08:24 UTC183
        }
f12377bce9b9atagen2026-09-26 12:32 UTC184
        let tracking = self
f12377bce9b9atagen2026-09-26 12:32 UTC185
            .tracking
f12377bce9b9atagen2026-09-26 12:32 UTC186
            .take()
f12377bce9b9atagen2026-09-26 12:32 UTC187
            .ok_or(AccountError::OAuthCallbackAlreadySubmitted)?;
f12377bce9b9atagen2026-09-26 12:32 UTC188
        Session::saved(self.client.clone(), &*self.sessions, tracking).await
96fdf90aacd6atagen2026-09-08 08:24 UTC189
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC190
96fdf90aacd6atagen2026-09-08 08:24 UTC191
    pub async fn abort(mut self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC192
        if let Some(state) = self.state.take() {
96fdf90aacd6atagen2026-09-08 08:24 UTC193
            self.client.oauth().abort_login(&state).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC194
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC195
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC196
}
96fdf90aacd6atagen2026-09-08 08:24 UTC197
96fdf90aacd6atagen2026-09-08 08:24 UTC198
pub struct StoreConfig {
62fe32759529atagen2026-09-26 08:50 UTC199
    location: StoreLocation,
96fdf90aacd6atagen2026-09-08 08:24 UTC200
    passphrase: String,
62fe32759529atagen2026-09-26 08:50 UTC201
    lock_holder: Option<String>,
62fe32759529atagen2026-09-26 08:50 UTC202
}
62fe32759529atagen2026-09-26 08:50 UTC203
62fe32759529atagen2026-09-26 08:50 UTC204
enum StoreLocation {
62fe32759529atagen2026-09-26 08:50 UTC205
    #[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC206
    Sqlite(PathBuf),
62fe32759529atagen2026-09-26 08:50 UTC207
    #[cfg(feature = "indexeddb")]
62fe32759529atagen2026-09-26 08:50 UTC208
    IndexedDb(String),
96fdf90aacd6atagen2026-09-08 08:24 UTC209
}
96fdf90aacd6atagen2026-09-08 08:24 UTC210
96fdf90aacd6atagen2026-09-08 08:24 UTC211
impl StoreConfig {
62fe32759529atagen2026-09-26 08:50 UTC212
    #[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC213
    #[must_use]
62fe32759529atagen2026-09-26 08:50 UTC214
    pub fn sqlite(path: impl Into<PathBuf>, passphrase: impl Into<String>) -> Self {
62fe32759529atagen2026-09-26 08:50 UTC215
        Self {
62fe32759529atagen2026-09-26 08:50 UTC216
            location: StoreLocation::Sqlite(path.into()),
62fe32759529atagen2026-09-26 08:50 UTC217
            passphrase: passphrase.into(),
62fe32759529atagen2026-09-26 08:50 UTC218
            lock_holder: None,
62fe32759529atagen2026-09-26 08:50 UTC219
        }
62fe32759529atagen2026-09-26 08:50 UTC220
    }
62fe32759529atagen2026-09-26 08:50 UTC221
62fe32759529atagen2026-09-26 08:50 UTC222
    #[cfg(feature = "indexeddb")]
96fdf90aacd6atagen2026-09-08 08:24 UTC223
    #[must_use]
62fe32759529atagen2026-09-26 08:50 UTC224
    pub fn indexeddb(name: impl Into<String>, passphrase: impl Into<String>) -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC225
        Self {
62fe32759529atagen2026-09-26 08:50 UTC226
            location: StoreLocation::IndexedDb(name.into()),
96fdf90aacd6atagen2026-09-08 08:24 UTC227
            passphrase: passphrase.into(),
62fe32759529atagen2026-09-26 08:50 UTC228
            lock_holder: None,
96fdf90aacd6atagen2026-09-08 08:24 UTC229
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC230
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC231
62fe32759529atagen2026-09-26 08:50 UTC232
    /// the sdk names every client `main`, so two tabs on one store would both think they hold its lock
96fdf90aacd6atagen2026-09-08 08:24 UTC233
    #[must_use]
62fe32759529atagen2026-09-26 08:50 UTC234
    pub fn with_lock_holder(mut self, holder: impl Into<String>) -> Self {
62fe32759529atagen2026-09-26 08:50 UTC235
        self.lock_holder = Some(holder.into());
62fe32759529atagen2026-09-26 08:50 UTC236
        self
96fdf90aacd6atagen2026-09-08 08:24 UTC237
    }
713205a95af2atagen2026-09-26 09:24 UTC238
713205a95af2atagen2026-09-26 09:24 UTC239
    /// delete the store's databases, once nothing has them open
713205a95af2atagen2026-09-26 09:24 UTC240
    pub async fn delete(&self) -> Result<(), AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC241
        match &self.location {
713205a95af2atagen2026-09-26 09:24 UTC242
            #[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC243
            StoreLocation::Sqlite(path) => crate::session_store::delete_sqlite_store(path)
713205a95af2atagen2026-09-26 09:24 UTC244
                .map_err(|error| account_error("deleting the Matrix store", error)),
713205a95af2atagen2026-09-26 09:24 UTC245
            #[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC246
            StoreLocation::IndexedDb(name) => crate::session_store::delete_indexeddb_store(name)
713205a95af2atagen2026-09-26 09:24 UTC247
                .await
6db49c0ecf71atagen2026-09-26 12:41 UTC248
                .map_err(|error| error.into_account_error("deleting the Matrix store")),
713205a95af2atagen2026-09-26 09:24 UTC249
        }
713205a95af2atagen2026-09-26 09:24 UTC250
    }
713205a95af2atagen2026-09-26 09:24 UTC251
713205a95af2atagen2026-09-26 09:24 UTC252
    pub(crate) fn passphrase(&self) -> &str {
713205a95af2atagen2026-09-26 09:24 UTC253
        &self.passphrase
713205a95af2atagen2026-09-26 09:24 UTC254
    }
713205a95af2atagen2026-09-26 09:24 UTC255
713205a95af2atagen2026-09-26 09:24 UTC256
    #[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC257
    pub(crate) fn indexeddb_name(&self) -> &str {
713205a95af2atagen2026-09-26 09:24 UTC258
        let StoreLocation::IndexedDb(name) = &self.location;
713205a95af2atagen2026-09-26 09:24 UTC259
        name
713205a95af2atagen2026-09-26 09:24 UTC260
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC261
}
96fdf90aacd6atagen2026-09-08 08:24 UTC262
f12377bce9b9atagen2026-09-26 12:32 UTC263
/// Serializable authentication secrets. A `SessionStore` keeps it, and the engine
f12377bce9b9atagen2026-09-26 12:32 UTC264
/// replaces it there on every token rotation.
96fdf90aacd6atagen2026-09-08 08:24 UTC265
#[derive(Clone, Deserialize, Serialize)]
96fdf90aacd6atagen2026-09-08 08:24 UTC266
pub struct SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC267
    homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC268
    authentication: AuthenticationRecord,
96fdf90aacd6atagen2026-09-08 08:24 UTC269
}
96fdf90aacd6atagen2026-09-08 08:24 UTC270
96fdf90aacd6atagen2026-09-08 08:24 UTC271
#[derive(Clone, Deserialize, Serialize)]
96fdf90aacd6atagen2026-09-08 08:24 UTC272
#[serde(tag = "type", content = "session", rename_all = "snake_case")]
96fdf90aacd6atagen2026-09-08 08:24 UTC273
enum AuthenticationRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC274
    Matrix(MatrixSession),
96fdf90aacd6atagen2026-09-08 08:24 UTC275
    OAuth {
96fdf90aacd6atagen2026-09-08 08:24 UTC276
        client_id: ClientId,
96fdf90aacd6atagen2026-09-08 08:24 UTC277
        user: UserSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC278
    },
6f1d6591c10datagen2026-09-26 09:52 UTC279
    SoftLoggedOut {
6f1d6591c10datagen2026-09-26 09:52 UTC280
        meta: SessionMeta,
6f1d6591c10datagen2026-09-26 09:52 UTC281
        client_id: Option<ClientId>,
6f1d6591c10datagen2026-09-26 09:52 UTC282
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC283
}
96fdf90aacd6atagen2026-09-08 08:24 UTC284
96fdf90aacd6atagen2026-09-08 08:24 UTC285
impl SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC286
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC287
    pub fn homeserver(&self) -> &str {
96fdf90aacd6atagen2026-09-08 08:24 UTC288
        &self.homeserver
96fdf90aacd6atagen2026-09-08 08:24 UTC289
    }
6f1d6591c10datagen2026-09-26 09:52 UTC290
6f1d6591c10datagen2026-09-26 09:52 UTC291
    pub(crate) fn soft_logged_out(self) -> Self {
6f1d6591c10datagen2026-09-26 09:52 UTC292
        let authentication = match self.authentication {
6f1d6591c10datagen2026-09-26 09:52 UTC293
            AuthenticationRecord::Matrix(session) => AuthenticationRecord::SoftLoggedOut {
6f1d6591c10datagen2026-09-26 09:52 UTC294
                meta: session.meta,
6f1d6591c10datagen2026-09-26 09:52 UTC295
                client_id: None,
6f1d6591c10datagen2026-09-26 09:52 UTC296
            },
6f1d6591c10datagen2026-09-26 09:52 UTC297
            AuthenticationRecord::OAuth { client_id, user } => {
6f1d6591c10datagen2026-09-26 09:52 UTC298
                AuthenticationRecord::SoftLoggedOut {
6f1d6591c10datagen2026-09-26 09:52 UTC299
                    meta: user.meta,
6f1d6591c10datagen2026-09-26 09:52 UTC300
                    client_id: Some(client_id),
6f1d6591c10datagen2026-09-26 09:52 UTC301
                }
6f1d6591c10datagen2026-09-26 09:52 UTC302
            }
6f1d6591c10datagen2026-09-26 09:52 UTC303
            soft @ AuthenticationRecord::SoftLoggedOut { .. } => soft,
6f1d6591c10datagen2026-09-26 09:52 UTC304
        };
6f1d6591c10datagen2026-09-26 09:52 UTC305
        Self {
6f1d6591c10datagen2026-09-26 09:52 UTC306
            homeserver: self.homeserver,
6f1d6591c10datagen2026-09-26 09:52 UTC307
            authentication,
6f1d6591c10datagen2026-09-26 09:52 UTC308
        }
6f1d6591c10datagen2026-09-26 09:52 UTC309
    }
6f1d6591c10datagen2026-09-26 09:52 UTC310
}
6f1d6591c10datagen2026-09-26 09:52 UTC311
6f1d6591c10datagen2026-09-26 09:52 UTC312
pub enum Restore {
6f1d6591c10datagen2026-09-26 09:52 UTC313
    Resumed(Session),
6f1d6591c10datagen2026-09-26 09:52 UTC314
    SoftLoggedOut(SoftLogout),
6f1d6591c10datagen2026-09-26 09:52 UTC315
}
6f1d6591c10datagen2026-09-26 09:52 UTC316
6f1d6591c10datagen2026-09-26 09:52 UTC317
/// who to log in again as. the store keeps this device's e2ee keys, so a login
6f1d6591c10datagen2026-09-26 09:52 UTC318
/// as any other device would orphan them
6f1d6591c10datagen2026-09-26 09:52 UTC319
#[derive(Clone, Debug)]
6f1d6591c10datagen2026-09-26 09:52 UTC320
#[non_exhaustive]
6f1d6591c10datagen2026-09-26 09:52 UTC321
pub struct SoftLogout {
6f1d6591c10datagen2026-09-26 09:52 UTC322
    pub homeserver: String,
6f1d6591c10datagen2026-09-26 09:52 UTC323
    pub user_id: OwnedUserId,
6f1d6591c10datagen2026-09-26 09:52 UTC324
    pub device_id: OwnedDeviceId,
6f1d6591c10datagen2026-09-26 09:52 UTC325
    /// set when the login was OAuth, for `OAuthLogin::with_client_id`
6f1d6591c10datagen2026-09-26 09:52 UTC326
    pub client_id: Option<String>,
6f1d6591c10datagen2026-09-26 09:52 UTC327
}
6f1d6591c10datagen2026-09-26 09:52 UTC328
6f1d6591c10datagen2026-09-26 09:52 UTC329
impl SoftLogout {
6f1d6591c10datagen2026-09-26 09:52 UTC330
    #[must_use]
6f1d6591c10datagen2026-09-26 09:52 UTC331
    pub fn password_login(&self, password: impl Into<String>) -> PasswordLogin {
6f1d6591c10datagen2026-09-26 09:52 UTC332
        PasswordLogin::new(self.homeserver.clone(), self.user_id.as_str(), password)
6f1d6591c10datagen2026-09-26 09:52 UTC333
            .with_device_id(self.device_id.clone())
6f1d6591c10datagen2026-09-26 09:52 UTC334
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC335
}
96fdf90aacd6atagen2026-09-08 08:24 UTC336
96fdf90aacd6atagen2026-09-08 08:24 UTC337
#[derive(Debug, Error)]
96fdf90aacd6atagen2026-09-08 08:24 UTC338
pub enum AccountError {
96fdf90aacd6atagen2026-09-08 08:24 UTC339
    #[error("{context} failed")]
96fdf90aacd6atagen2026-09-08 08:24 UTC340
    Operation {
96fdf90aacd6atagen2026-09-08 08:24 UTC341
        context: &'static str,
96fdf90aacd6atagen2026-09-08 08:24 UTC342
        #[source]
62fe32759529atagen2026-09-26 08:50 UTC343
        source: Box<SourceError>,
96fdf90aacd6atagen2026-09-08 08:24 UTC344
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC345
    #[error("the homeserver returned no authenticated session")]
96fdf90aacd6atagen2026-09-08 08:24 UTC346
    MissingSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC347
    #[error("the SDK returned an unsupported authentication session")]
96fdf90aacd6atagen2026-09-08 08:24 UTC348
    UnsupportedAuthentication,
96fdf90aacd6atagen2026-09-08 08:24 UTC349
    #[error("the OAuth callback was already submitted")]
96fdf90aacd6atagen2026-09-08 08:24 UTC350
    OAuthCallbackAlreadySubmitted,
6db49c0ecf71atagen2026-09-26 12:41 UTC351
    /// an IndexedDB delete is waiting on the store's other connections. it goes through
6db49c0ecf71atagen2026-09-26 12:41 UTC352
    /// once they close, and opening the store again waits behind it
6db49c0ecf71atagen2026-09-26 12:41 UTC353
    #[error("the store is still open elsewhere, it will be deleted once that closes")]
6db49c0ecf71atagen2026-09-26 12:41 UTC354
    StoreDeletePending,
96fdf90aacd6atagen2026-09-08 08:24 UTC355
    #[error(transparent)]
96fdf90aacd6atagen2026-09-08 08:24 UTC356
    Engine(#[from] EngineError),
96fdf90aacd6atagen2026-09-08 08:24 UTC357
}
96fdf90aacd6atagen2026-09-08 08:24 UTC358
96fdf90aacd6atagen2026-09-08 08:24 UTC359
impl Session {
96fdf90aacd6atagen2026-09-08 08:24 UTC360
    pub async fn login_password(
96fdf90aacd6atagen2026-09-08 08:24 UTC361
        login: PasswordLogin,
f12377bce9b9atagen2026-09-26 12:32 UTC362
        sessions: Arc<dyn SessionStore>,
96fdf90aacd6atagen2026-09-08 08:24 UTC363
    ) -> Result<Self, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC364
        let client = matrix_client_builder(sessions.store())
96fdf90aacd6atagen2026-09-08 08:24 UTC365
            .server_name_or_homeserver_url(login.homeserver.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC366
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC367
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC368
            .map_err(|error| account_error("building the Matrix client", error))?;
f12377bce9b9atagen2026-09-26 12:32 UTC369
        let tracking = Tracking::start(&client, sessions.clone())?;
96fdf90aacd6atagen2026-09-08 08:24 UTC370
        let mut request = client
96fdf90aacd6atagen2026-09-08 08:24 UTC371
            .matrix_auth()
96fdf90aacd6atagen2026-09-08 08:24 UTC372
            .login_username(login.username.trim(), &login.password)
96fdf90aacd6atagen2026-09-08 08:24 UTC373
            .request_refresh_token();
96fdf90aacd6atagen2026-09-08 08:24 UTC374
        if let Some(name) = login.device_display_name.as_deref() {
96fdf90aacd6atagen2026-09-08 08:24 UTC375
            request = request.initial_device_display_name(name);
96fdf90aacd6atagen2026-09-08 08:24 UTC376
        }
fa9114fcf75fatagen2026-09-24 12:51 UTC377
        if let Some(device_id) = login.device_id.as_deref() {
fa9114fcf75fatagen2026-09-24 12:51 UTC378
            request = request.device_id(device_id.as_str());
fa9114fcf75fatagen2026-09-24 12:51 UTC379
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC380
        request
96fdf90aacd6atagen2026-09-08 08:24 UTC381
            .send()
96fdf90aacd6atagen2026-09-08 08:24 UTC382
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC383
            .map_err(|error| account_error("password login", error))?;
f12377bce9b9atagen2026-09-26 12:32 UTC384
        Self::saved(client, &*sessions, tracking).await
96fdf90aacd6atagen2026-09-08 08:24 UTC385
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC386
96fdf90aacd6atagen2026-09-08 08:24 UTC387
    pub async fn begin_oauth(
96fdf90aacd6atagen2026-09-08 08:24 UTC388
        login: OAuthLogin,
713205a95af2atagen2026-09-26 09:24 UTC389
        sessions: Arc<dyn SessionStore>,
96fdf90aacd6atagen2026-09-08 08:24 UTC390
    ) -> Result<PendingOAuthLogin, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC391
        let redirect_uri = Url::parse(login.redirect_uri.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC392
            .map_err(|error| account_error("parsing the OAuth redirect URI", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC393
        let client_uri = Url::parse(login.client_uri.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC394
            .map_err(|error| account_error("parsing the OAuth client URI", error))?;
713205a95af2atagen2026-09-26 09:24 UTC395
        let client = matrix_client_builder(sessions.store())
96fdf90aacd6atagen2026-09-08 08:24 UTC396
            .server_name_or_homeserver_url(login.homeserver.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC397
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC398
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC399
            .map_err(|error| account_error("building the Matrix client", error))?;
f12377bce9b9atagen2026-09-26 12:32 UTC400
        let tracking = Tracking::start(&client, sessions.clone())?;
96fdf90aacd6atagen2026-09-08 08:24 UTC401
        let application_type = match login.application_type {
96fdf90aacd6atagen2026-09-08 08:24 UTC402
            OAuthApplicationType::Native => ApplicationType::Native,
96fdf90aacd6atagen2026-09-08 08:24 UTC403
            OAuthApplicationType::Web => ApplicationType::Web,
96fdf90aacd6atagen2026-09-08 08:24 UTC404
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC405
        let mut metadata = ClientMetadata::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC406
            application_type,
96fdf90aacd6atagen2026-09-08 08:24 UTC407
            vec![OAuthGrantType::AuthorizationCode {
96fdf90aacd6atagen2026-09-08 08:24 UTC408
                redirect_uris: vec![redirect_uri.clone()],
96fdf90aacd6atagen2026-09-08 08:24 UTC409
            }],
96fdf90aacd6atagen2026-09-08 08:24 UTC410
            Localized::new(client_uri, []),
96fdf90aacd6atagen2026-09-08 08:24 UTC411
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC412
        metadata.client_name = login.client_name.map(|name| Localized::new(name, []));
96fdf90aacd6atagen2026-09-08 08:24 UTC413
        let metadata = Raw::new(&metadata)
96fdf90aacd6atagen2026-09-08 08:24 UTC414
            .map_err(|error| account_error("serializing OAuth client metadata", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC415
        let mut registration = ClientRegistrationData::new(metadata);
96fdf90aacd6atagen2026-09-08 08:24 UTC416
        if let Some(client_id) = login.client_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC417
            registration.static_registrations = Some(HashMap::from([(
96fdf90aacd6atagen2026-09-08 08:24 UTC418
                client.homeserver(),
96fdf90aacd6atagen2026-09-08 08:24 UTC419
                ClientId::new(client_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC420
            )]));
96fdf90aacd6atagen2026-09-08 08:24 UTC421
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC422
        let authorization = client
96fdf90aacd6atagen2026-09-08 08:24 UTC423
            .oauth()
96fdf90aacd6atagen2026-09-08 08:24 UTC424
            .login(redirect_uri, login.device_id, Some(registration), None)
96fdf90aacd6atagen2026-09-08 08:24 UTC425
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC426
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC427
            .map_err(|error| account_error("starting OAuth login", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC428
        Ok(PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC429
            client,
713205a95af2atagen2026-09-26 09:24 UTC430
            sessions,
f12377bce9b9atagen2026-09-26 12:32 UTC431
            tracking: Some(tracking),
96fdf90aacd6atagen2026-09-08 08:24 UTC432
            authorization_url: authorization.url.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC433
            state: Some(authorization.state),
96fdf90aacd6atagen2026-09-08 08:24 UTC434
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC435
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC436
713205a95af2atagen2026-09-26 09:24 UTC437
    /// Restore the record `sessions` holds, or `None` when it holds none.
f12377bce9b9atagen2026-09-26 12:32 UTC438
    /// The caller must also handle rejection and explicit logout.
f12377bce9b9atagen2026-09-26 12:32 UTC439
    pub async fn restore(sessions: Arc<dyn SessionStore>) -> Result<Option<Restore>, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC440
        let Some(record) = sessions.load().await? else {
713205a95af2atagen2026-09-26 09:24 UTC441
            return Ok(None);
713205a95af2atagen2026-09-26 09:24 UTC442
        };
713205a95af2atagen2026-09-26 09:24 UTC443
        let authentication = match record.authentication {
96fdf90aacd6atagen2026-09-08 08:24 UTC444
            AuthenticationRecord::Matrix(session) => AuthSession::Matrix(session),
96fdf90aacd6atagen2026-09-08 08:24 UTC445
            AuthenticationRecord::OAuth { client_id, user } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC446
                OAuthSession { client_id, user }.into()
96fdf90aacd6atagen2026-09-08 08:24 UTC447
            }
6f1d6591c10datagen2026-09-26 09:52 UTC448
            AuthenticationRecord::SoftLoggedOut { meta, client_id } => {
6f1d6591c10datagen2026-09-26 09:52 UTC449
                return Ok(Some(Restore::SoftLoggedOut(SoftLogout {
6f1d6591c10datagen2026-09-26 09:52 UTC450
                    homeserver: record.homeserver,
6f1d6591c10datagen2026-09-26 09:52 UTC451
                    user_id: meta.user_id,
6f1d6591c10datagen2026-09-26 09:52 UTC452
                    device_id: meta.device_id,
6f1d6591c10datagen2026-09-26 09:52 UTC453
                    client_id: client_id.map(|client_id| client_id.as_str().to_owned()),
6f1d6591c10datagen2026-09-26 09:52 UTC454
                })));
6f1d6591c10datagen2026-09-26 09:52 UTC455
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC456
        };
6f1d6591c10datagen2026-09-26 09:52 UTC457
        let client = matrix_client_builder(sessions.store())
6f1d6591c10datagen2026-09-26 09:52 UTC458
            .homeserver_url(&record.homeserver)
6f1d6591c10datagen2026-09-26 09:52 UTC459
            .build()
6f1d6591c10datagen2026-09-26 09:52 UTC460
            .await
6f1d6591c10datagen2026-09-26 09:52 UTC461
            .map_err(|error| account_error("reopening the Matrix store", error))?;
f12377bce9b9atagen2026-09-26 12:32 UTC462
        let tracking = Tracking::start(&client, sessions)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC463
        client
96fdf90aacd6atagen2026-09-08 08:24 UTC464
            .restore_session(authentication)
96fdf90aacd6atagen2026-09-08 08:24 UTC465
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC466
            .map_err(|error| account_error("restoring the authenticated session", error))?;
f12377bce9b9atagen2026-09-26 12:32 UTC467
        Ok(Some(Restore::Resumed(Self { client, tracking })))
f12377bce9b9atagen2026-09-26 12:32 UTC468
    }
f12377bce9b9atagen2026-09-26 12:32 UTC469
f12377bce9b9atagen2026-09-26 12:32 UTC470
    /// leave the record to the host, which then saves `ClientHandle::session_record()`
f12377bce9b9atagen2026-09-26 12:32 UTC471
    /// on every `SessionTokensChanged`
f12377bce9b9atagen2026-09-26 12:32 UTC472
    #[must_use]
f12377bce9b9atagen2026-09-26 12:32 UTC473
    pub fn without_persistence(mut self) -> Self {
f12377bce9b9atagen2026-09-26 12:32 UTC474
        self.tracking.persistence = None;
f12377bce9b9atagen2026-09-26 12:32 UTC475
        #[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC476
        self.tracking.inline.store(false, Ordering::Release);
f12377bce9b9atagen2026-09-26 12:32 UTC477
        self
96fdf90aacd6atagen2026-09-08 08:24 UTC478
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC479
96fdf90aacd6atagen2026-09-08 08:24 UTC480
    pub async fn build(
96fdf90aacd6atagen2026-09-08 08:24 UTC481
        self,
96fdf90aacd6atagen2026-09-08 08:24 UTC482
        config: EngineConfig,
96fdf90aacd6atagen2026-09-08 08:24 UTC483
    ) -> Result<(Engine, ClientHandle, EventStream), AccountError> {
f12377bce9b9atagen2026-09-26 12:32 UTC484
        Engine::with_persistence(
f12377bce9b9atagen2026-09-26 12:32 UTC485
            self.client,
f12377bce9b9atagen2026-09-26 12:32 UTC486
            config,
f12377bce9b9atagen2026-09-26 12:32 UTC487
            self.tracking.session_changes,
f12377bce9b9atagen2026-09-26 12:32 UTC488
            self.tracking.persistence,
f12377bce9b9atagen2026-09-26 12:32 UTC489
        )
f12377bce9b9atagen2026-09-26 12:32 UTC490
        .await
f12377bce9b9atagen2026-09-26 12:32 UTC491
        .map_err(AccountError::from)
96fdf90aacd6atagen2026-09-08 08:24 UTC492
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC493
f12377bce9b9atagen2026-09-26 12:32 UTC494
    async fn saved(
f12377bce9b9atagen2026-09-26 12:32 UTC495
        client: Client,
f12377bce9b9atagen2026-09-26 12:32 UTC496
        sessions: &dyn SessionStore,
f12377bce9b9atagen2026-09-26 12:32 UTC497
        tracking: Tracking,
f12377bce9b9atagen2026-09-26 12:32 UTC498
    ) -> Result<Self, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC499
        sessions.save(&session_record(&client)?).await?;
f12377bce9b9atagen2026-09-26 12:32 UTC500
        Ok(Self { client, tracking })
f12377bce9b9atagen2026-09-26 12:32 UTC501
    }
f12377bce9b9atagen2026-09-26 12:32 UTC502
}
f12377bce9b9atagen2026-09-26 12:32 UTC503
f12377bce9b9atagen2026-09-26 12:32 UTC504
impl Tracking {
f12377bce9b9atagen2026-09-26 12:32 UTC505
    fn start(client: &Client, sessions: Arc<dyn SessionStore>) -> Result<Self, AccountError> {
f12377bce9b9atagen2026-09-26 12:32 UTC506
        let session_changes = client.subscribe_to_session_changes();
f12377bce9b9atagen2026-09-26 12:32 UTC507
        #[cfg_attr(target_family = "wasm", allow(unused_variables))]
f12377bce9b9atagen2026-09-26 12:32 UTC508
        let (results, inline_results) = mpsc::unbounded_channel();
f12377bce9b9atagen2026-09-26 12:32 UTC509
        #[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC510
        let inline = save_inline(client, sessions.clone(), results)?;
f12377bce9b9atagen2026-09-26 12:32 UTC511
        Ok(Self {
f12377bce9b9atagen2026-09-26 12:32 UTC512
            session_changes,
f12377bce9b9atagen2026-09-26 12:32 UTC513
            persistence: Some(Persistence {
f12377bce9b9atagen2026-09-26 12:32 UTC514
                save: Box::new(move |client| {
f12377bce9b9atagen2026-09-26 12:32 UTC515
                    let sessions = sessions.clone();
f12377bce9b9atagen2026-09-26 12:32 UTC516
                    Box::pin(async move {
f12377bce9b9atagen2026-09-26 12:32 UTC517
                        let record = session_record(&client).map_err(save_failed)?;
f12377bce9b9atagen2026-09-26 12:32 UTC518
                        sessions.save(&record).await.map_err(save_failed)
f12377bce9b9atagen2026-09-26 12:32 UTC519
                    })
f12377bce9b9atagen2026-09-26 12:32 UTC520
                }),
f12377bce9b9atagen2026-09-26 12:32 UTC521
                inline: inline_results,
f12377bce9b9atagen2026-09-26 12:32 UTC522
            }),
f12377bce9b9atagen2026-09-26 12:32 UTC523
            #[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC524
            inline,
f12377bce9b9atagen2026-09-26 12:32 UTC525
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC526
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC527
}
96fdf90aacd6atagen2026-09-08 08:24 UTC528
f12377bce9b9atagen2026-09-26 12:32 UTC529
/// the sdk calls this inside its refresh, after it has the new tokens and before it uses
f12377bce9b9atagen2026-09-26 12:32 UTC530
/// them, so a store that writes there and then can't lose a refresh token to a crash
f12377bce9b9atagen2026-09-26 12:32 UTC531
#[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC532
fn save_inline(
f12377bce9b9atagen2026-09-26 12:32 UTC533
    client: &Client,
f12377bce9b9atagen2026-09-26 12:32 UTC534
    sessions: Arc<dyn SessionStore>,
f12377bce9b9atagen2026-09-26 12:32 UTC535
    results: mpsc::UnboundedSender<Result<(), crate::Error>>,
f12377bce9b9atagen2026-09-26 12:32 UTC536
) -> Result<Arc<AtomicBool>, AccountError> {
f12377bce9b9atagen2026-09-26 12:32 UTC537
    let enabled = Arc::new(AtomicBool::new(true));
f12377bce9b9atagen2026-09-26 12:32 UTC538
    let saving = enabled.clone();
f12377bce9b9atagen2026-09-26 12:32 UTC539
    client
f12377bce9b9atagen2026-09-26 12:32 UTC540
        .set_session_callbacks(
f12377bce9b9atagen2026-09-26 12:32 UTC541
            // only asked for with the cross process refresh lock, which we never enable
f12377bce9b9atagen2026-09-26 12:32 UTC542
            Box::new(|client| {
f12377bce9b9atagen2026-09-26 12:32 UTC543
                client
f12377bce9b9atagen2026-09-26 12:32 UTC544
                    .session_tokens()
f12377bce9b9atagen2026-09-26 12:32 UTC545
                    .ok_or_else(|| AccountError::MissingSession.into())
f12377bce9b9atagen2026-09-26 12:32 UTC546
            }),
f12377bce9b9atagen2026-09-26 12:32 UTC547
            Box::new(move |client| {
f12377bce9b9atagen2026-09-26 12:32 UTC548
                if !saving.load(Ordering::Acquire) {
f12377bce9b9atagen2026-09-26 12:32 UTC549
                    return Ok(());
f12377bce9b9atagen2026-09-26 12:32 UTC550
                }
f12377bce9b9atagen2026-09-26 12:32 UTC551
                let record = session_record(&client)?;
f12377bce9b9atagen2026-09-26 12:32 UTC552
                if let Some(result) = sessions.save_blocking(&record) {
f12377bce9b9atagen2026-09-26 12:32 UTC553
                    let _ = results.send(result.map_err(save_failed));
f12377bce9b9atagen2026-09-26 12:32 UTC554
                }
f12377bce9b9atagen2026-09-26 12:32 UTC555
                Ok(())
f12377bce9b9atagen2026-09-26 12:32 UTC556
            }),
f12377bce9b9atagen2026-09-26 12:32 UTC557
        )
f12377bce9b9atagen2026-09-26 12:32 UTC558
        .map_err(|error| account_error("registering the session callbacks", error))?;
f12377bce9b9atagen2026-09-26 12:32 UTC559
    Ok(enabled)
f12377bce9b9atagen2026-09-26 12:32 UTC560
}
f12377bce9b9atagen2026-09-26 12:32 UTC561
f12377bce9b9atagen2026-09-26 12:32 UTC562
fn save_failed(error: AccountError) -> crate::Error {
f12377bce9b9atagen2026-09-26 12:32 UTC563
    let message = format!("saving the session record failed, {error}");
f12377bce9b9atagen2026-09-26 12:32 UTC564
    crate::Error::with_source(ErrorKind::Store, message, error)
f12377bce9b9atagen2026-09-26 12:32 UTC565
}
f12377bce9b9atagen2026-09-26 12:32 UTC566
96fdf90aacd6atagen2026-09-08 08:24 UTC567
pub(crate) fn session_record(client: &Client) -> Result<SessionRecord, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC568
    let authentication = match client.session().ok_or(AccountError::MissingSession)? {
96fdf90aacd6atagen2026-09-08 08:24 UTC569
        AuthSession::Matrix(session) => AuthenticationRecord::Matrix(session),
96fdf90aacd6atagen2026-09-08 08:24 UTC570
        AuthSession::OAuth(session) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC571
            let OAuthSession { client_id, user } = *session;
96fdf90aacd6atagen2026-09-08 08:24 UTC572
            AuthenticationRecord::OAuth { client_id, user }
96fdf90aacd6atagen2026-09-08 08:24 UTC573
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC574
        _ => return Err(AccountError::UnsupportedAuthentication),
96fdf90aacd6atagen2026-09-08 08:24 UTC575
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC576
    Ok(SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC577
        homeserver: client.homeserver().to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC578
        authentication,
96fdf90aacd6atagen2026-09-08 08:24 UTC579
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC580
}
96fdf90aacd6atagen2026-09-08 08:24 UTC581
96fdf90aacd6atagen2026-09-08 08:24 UTC582
fn encryption_settings() -> EncryptionSettings {
96fdf90aacd6atagen2026-09-08 08:24 UTC583
    EncryptionSettings {
96fdf90aacd6atagen2026-09-08 08:24 UTC584
        auto_enable_cross_signing: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC585
        backup_download_strategy: BackupDownloadStrategy::OneShot,
96fdf90aacd6atagen2026-09-08 08:24 UTC586
        auto_enable_backups: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC587
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC588
}
96fdf90aacd6atagen2026-09-08 08:24 UTC589
96fdf90aacd6atagen2026-09-08 08:24 UTC590
fn matrix_client_builder(store: &StoreConfig) -> ClientBuilder {
62fe32759529atagen2026-09-26 08:50 UTC591
    let mut builder = Client::builder()
96fdf90aacd6atagen2026-09-08 08:24 UTC592
        .with_encryption_settings(encryption_settings())
96fdf90aacd6atagen2026-09-08 08:24 UTC593
        .with_threading_support(ThreadingSupport::Enabled {
96fdf90aacd6atagen2026-09-08 08:24 UTC594
            with_subscriptions: false,
96fdf90aacd6atagen2026-09-08 08:24 UTC595
        })
62fe32759529atagen2026-09-26 08:50 UTC596
        .handle_refresh_tokens();
62fe32759529atagen2026-09-26 08:50 UTC597
    if let Some(holder) = &store.lock_holder {
62fe32759529atagen2026-09-26 08:50 UTC598
        builder = builder.cross_process_store_config(CrossProcessLockConfig::multi_process(holder));
62fe32759529atagen2026-09-26 08:50 UTC599
    }
62fe32759529atagen2026-09-26 08:50 UTC600
    match &store.location {
62fe32759529atagen2026-09-26 08:50 UTC601
        #[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC602
        StoreLocation::Sqlite(path) => builder.sqlite_store(path, Some(&store.passphrase)),
62fe32759529atagen2026-09-26 08:50 UTC603
        #[cfg(feature = "indexeddb")]
62fe32759529atagen2026-09-26 08:50 UTC604
        StoreLocation::IndexedDb(name) => builder.indexeddb_store(name, Some(&store.passphrase)),
62fe32759529atagen2026-09-26 08:50 UTC605
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC606
}
96fdf90aacd6atagen2026-09-08 08:24 UTC607
713205a95af2atagen2026-09-26 09:24 UTC608
pub(crate) fn account_error(
96fdf90aacd6atagen2026-09-08 08:24 UTC609
    context: &'static str,
62fe32759529atagen2026-09-26 08:50 UTC610
    source: impl std::error::Error + SendOutsideWasm + SyncOutsideWasm + 'static,
96fdf90aacd6atagen2026-09-08 08:24 UTC611
) -> AccountError {
96fdf90aacd6atagen2026-09-08 08:24 UTC612
    AccountError::Operation {
96fdf90aacd6atagen2026-09-08 08:24 UTC613
        context,
96fdf90aacd6atagen2026-09-08 08:24 UTC614
        source: Box::new(source),
96fdf90aacd6atagen2026-09-08 08:24 UTC615
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC616
}