atagen

macltok

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

Blame: src/session_store.rs

713205a95af2atagen2026-09-26 09:24 UTC1
#[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC2
use std::path::{Path, PathBuf};
713205a95af2atagen2026-09-26 09:24 UTC3
713205a95af2atagen2026-09-26 09:24 UTC4
use matrix_sdk::{BoxFuture, SendOutsideWasm, SyncOutsideWasm};
713205a95af2atagen2026-09-26 09:24 UTC5
use matrix_sdk_store_encryption::StoreCipher;
713205a95af2atagen2026-09-26 09:24 UTC6
713205a95af2atagen2026-09-26 09:24 UTC7
use crate::{AccountError, SessionRecord, StoreConfig, account::account_error};
713205a95af2atagen2026-09-26 09:24 UTC8
713205a95af2atagen2026-09-26 09:24 UTC9
#[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC10
type RecordLocation = PathBuf;
713205a95af2atagen2026-09-26 09:24 UTC11
#[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC12
type RecordLocation = String;
713205a95af2atagen2026-09-26 09:24 UTC13
6db49c0ecf71atagen2026-09-26 12:41 UTC14
#[cfg(not(target_family = "wasm"))]
6db49c0ecf71atagen2026-09-26 12:41 UTC15
type Lock<T> = std::sync::Mutex<T>;
6db49c0ecf71atagen2026-09-26 12:41 UTC16
#[cfg(target_family = "wasm")]
6db49c0ecf71atagen2026-09-26 12:41 UTC17
type Lock<T> = tokio::sync::Mutex<T>;
6db49c0ecf71atagen2026-09-26 12:41 UTC18
713205a95af2atagen2026-09-26 09:24 UTC19
/// one login's record and the matrix store it belongs to. the engine rotates refresh tokens
713205a95af2atagen2026-09-26 09:24 UTC20
/// and each works once, so a `save` has to land before the next refresh or the login is gone
713205a95af2atagen2026-09-26 09:24 UTC21
pub trait SessionStore: SendOutsideWasm + SyncOutsideWasm {
713205a95af2atagen2026-09-26 09:24 UTC22
    fn store(&self) -> &StoreConfig;
713205a95af2atagen2026-09-26 09:24 UTC23
713205a95af2atagen2026-09-26 09:24 UTC24
    fn load(&self) -> BoxFuture<'_, Result<Option<SessionRecord>, AccountError>>;
713205a95af2atagen2026-09-26 09:24 UTC25
713205a95af2atagen2026-09-26 09:24 UTC26
    /// replace the saved record, all or nothing
713205a95af2atagen2026-09-26 09:24 UTC27
    fn save<'a>(&'a self, record: &'a SessionRecord) -> BoxFuture<'a, Result<(), AccountError>>;
713205a95af2atagen2026-09-26 09:24 UTC28
f12377bce9b9atagen2026-09-26 12:32 UTC29
    /// `save`, but without awaiting. the sdk calls it from inside a refresh, before the new
f12377bce9b9atagen2026-09-26 12:32 UTC30
    /// refresh token is ever used, so a store that can write synchronously closes the gap
f12377bce9b9atagen2026-09-26 12:32 UTC31
    /// a crash would otherwise lose the login through. `None` leaves the refresh to `save`
f12377bce9b9atagen2026-09-26 12:32 UTC32
    #[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC33
    fn save_blocking(&self, _record: &SessionRecord) -> Option<Result<(), AccountError>> {
f12377bce9b9atagen2026-09-26 12:32 UTC34
        None
f12377bce9b9atagen2026-09-26 12:32 UTC35
    }
f12377bce9b9atagen2026-09-26 12:32 UTC36
6f1d6591c10datagen2026-09-26 09:52 UTC37
    /// swap the record for one that only says who to log in again as, keeping the store.
6f1d6591c10datagen2026-09-26 09:52 UTC38
    /// `Session::restore` then answers `Restore::SoftLoggedOut`, even after a restart
6f1d6591c10datagen2026-09-26 09:52 UTC39
    fn soft_log_out(&self) -> BoxFuture<'_, Result<(), AccountError>>;
713205a95af2atagen2026-09-26 09:24 UTC40
713205a95af2atagen2026-09-26 09:24 UTC41
    /// drop the record and delete the store, once nothing has the store open
713205a95af2atagen2026-09-26 09:24 UTC42
    fn discard(&self) -> BoxFuture<'_, Result<(), AccountError>>;
713205a95af2atagen2026-09-26 09:24 UTC43
}
713205a95af2atagen2026-09-26 09:24 UTC44
713205a95af2atagen2026-09-26 09:24 UTC45
/// keeps the record sealed with the store's passphrase, in a file natively and in an
713205a95af2atagen2026-09-26 09:24 UTC46
/// IndexedDB database of its own in a browser
713205a95af2atagen2026-09-26 09:24 UTC47
pub struct EncryptedSessionStore {
713205a95af2atagen2026-09-26 09:24 UTC48
    record: RecordLocation,
713205a95af2atagen2026-09-26 09:24 UTC49
    store: StoreConfig,
6db49c0ecf71atagen2026-09-26 12:41 UTC50
    sealer: Lock<Option<Sealer>>,
713205a95af2atagen2026-09-26 09:24 UTC51
}
713205a95af2atagen2026-09-26 09:24 UTC52
713205a95af2atagen2026-09-26 09:24 UTC53
struct Sealer {
713205a95af2atagen2026-09-26 09:24 UTC54
    cipher: StoreCipher,
713205a95af2atagen2026-09-26 09:24 UTC55
    exported: Vec<u8>,
713205a95af2atagen2026-09-26 09:24 UTC56
}
713205a95af2atagen2026-09-26 09:24 UTC57
713205a95af2atagen2026-09-26 09:24 UTC58
impl EncryptedSessionStore {
713205a95af2atagen2026-09-26 09:24 UTC59
    #[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC60
    #[must_use]
713205a95af2atagen2026-09-26 09:24 UTC61
    pub fn file(record: impl Into<PathBuf>, store: StoreConfig) -> Self {
713205a95af2atagen2026-09-26 09:24 UTC62
        Self::new(record.into(), store)
713205a95af2atagen2026-09-26 09:24 UTC63
    }
713205a95af2atagen2026-09-26 09:24 UTC64
713205a95af2atagen2026-09-26 09:24 UTC65
    /// the record lives beside the store, in `<name>::macltok-session`
713205a95af2atagen2026-09-26 09:24 UTC66
    #[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC67
    #[must_use]
713205a95af2atagen2026-09-26 09:24 UTC68
    pub fn indexeddb(store: StoreConfig) -> Self {
713205a95af2atagen2026-09-26 09:24 UTC69
        let record = format!("{}::macltok-session", store.indexeddb_name());
713205a95af2atagen2026-09-26 09:24 UTC70
        Self::new(record, store)
713205a95af2atagen2026-09-26 09:24 UTC71
    }
713205a95af2atagen2026-09-26 09:24 UTC72
713205a95af2atagen2026-09-26 09:24 UTC73
    fn new(record: RecordLocation, store: StoreConfig) -> Self {
713205a95af2atagen2026-09-26 09:24 UTC74
        Self {
713205a95af2atagen2026-09-26 09:24 UTC75
            record,
713205a95af2atagen2026-09-26 09:24 UTC76
            store,
6db49c0ecf71atagen2026-09-26 12:41 UTC77
            sealer: Lock::new(None),
713205a95af2atagen2026-09-26 09:24 UTC78
        }
713205a95af2atagen2026-09-26 09:24 UTC79
    }
713205a95af2atagen2026-09-26 09:24 UTC80
6db49c0ecf71atagen2026-09-26 12:41 UTC81
    fn open(
6db49c0ecf71atagen2026-09-26 12:41 UTC82
        &self,
6db49c0ecf71atagen2026-09-26 12:41 UTC83
        sealer: &mut Option<Sealer>,
6db49c0ecf71atagen2026-09-26 12:41 UTC84
        sealed: Option<Vec<u8>>,
6db49c0ecf71atagen2026-09-26 12:41 UTC85
    ) -> Result<Option<SessionRecord>, AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC86
        let Some(sealed) = sealed else {
6db49c0ecf71atagen2026-09-26 12:41 UTC87
            return Ok(None);
6db49c0ecf71atagen2026-09-26 12:41 UTC88
        };
6db49c0ecf71atagen2026-09-26 12:41 UTC89
        let (opened, record) = unseal(self.store.passphrase(), &sealed)?;
6db49c0ecf71atagen2026-09-26 12:41 UTC90
        *sealer = Some(opened);
6db49c0ecf71atagen2026-09-26 12:41 UTC91
        Ok(Some(record))
6f1d6591c10datagen2026-09-26 09:52 UTC92
    }
6f1d6591c10datagen2026-09-26 09:52 UTC93
6db49c0ecf71atagen2026-09-26 12:41 UTC94
    fn seal(
6db49c0ecf71atagen2026-09-26 12:41 UTC95
        &self,
6db49c0ecf71atagen2026-09-26 12:41 UTC96
        sealer: &mut Option<Sealer>,
6db49c0ecf71atagen2026-09-26 12:41 UTC97
        record: &SessionRecord,
6db49c0ecf71atagen2026-09-26 12:41 UTC98
    ) -> Result<Vec<u8>, AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC99
        let sealer = match sealer {
6db49c0ecf71atagen2026-09-26 12:41 UTC100
            Some(sealer) => sealer,
6db49c0ecf71atagen2026-09-26 12:41 UTC101
            empty => empty.insert(Sealer::new(self.store.passphrase())?),
6db49c0ecf71atagen2026-09-26 12:41 UTC102
        };
6db49c0ecf71atagen2026-09-26 12:41 UTC103
        sealer.seal(record)
6f1d6591c10datagen2026-09-26 09:52 UTC104
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC105
}
6f1d6591c10datagen2026-09-26 09:52 UTC106
6db49c0ecf71atagen2026-09-26 12:41 UTC107
#[cfg(not(target_family = "wasm"))]
6db49c0ecf71atagen2026-09-26 12:41 UTC108
impl EncryptedSessionStore {
6db49c0ecf71atagen2026-09-26 12:41 UTC109
    fn lock(&self) -> std::sync::MutexGuard<'_, Option<Sealer>> {
6db49c0ecf71atagen2026-09-26 12:41 UTC110
        self.sealer
6db49c0ecf71atagen2026-09-26 12:41 UTC111
            .lock()
6db49c0ecf71atagen2026-09-26 12:41 UTC112
            .unwrap_or_else(std::sync::PoisonError::into_inner)
6db49c0ecf71atagen2026-09-26 12:41 UTC113
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC114
6db49c0ecf71atagen2026-09-26 12:41 UTC115
    fn read(&self, sealer: &mut Option<Sealer>) -> Result<Option<SessionRecord>, AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC116
        let sealed = backend::read(&self.record)
6db49c0ecf71atagen2026-09-26 12:41 UTC117
            .map_err(|error| account_error("reading the session record", error))?;
6db49c0ecf71atagen2026-09-26 12:41 UTC118
        self.open(sealer, sealed)
6db49c0ecf71atagen2026-09-26 12:41 UTC119
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC120
6db49c0ecf71atagen2026-09-26 12:41 UTC121
    fn write(
6db49c0ecf71atagen2026-09-26 12:41 UTC122
        &self,
6db49c0ecf71atagen2026-09-26 12:41 UTC123
        sealer: &mut Option<Sealer>,
6db49c0ecf71atagen2026-09-26 12:41 UTC124
        record: &SessionRecord,
6db49c0ecf71atagen2026-09-26 12:41 UTC125
    ) -> Result<(), AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC126
        let sealed = self.seal(sealer, record)?;
6db49c0ecf71atagen2026-09-26 12:41 UTC127
        backend::write(&self.record, &sealed)
6db49c0ecf71atagen2026-09-26 12:41 UTC128
            .map_err(|error| account_error("writing the session record", error))
6db49c0ecf71atagen2026-09-26 12:41 UTC129
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC130
6db49c0ecf71atagen2026-09-26 12:41 UTC131
    fn load_record(&self) -> Result<Option<SessionRecord>, AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC132
        self.read(&mut self.lock())
6db49c0ecf71atagen2026-09-26 12:41 UTC133
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC134
6db49c0ecf71atagen2026-09-26 12:41 UTC135
    fn save_record(&self, record: &SessionRecord) -> Result<(), AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC136
        self.write(&mut self.lock(), record)
6db49c0ecf71atagen2026-09-26 12:41 UTC137
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC138
6db49c0ecf71atagen2026-09-26 12:41 UTC139
    fn soft_log_out_record(&self) -> Result<(), AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC140
        let mut sealer = self.lock();
6db49c0ecf71atagen2026-09-26 12:41 UTC141
        match self.read(&mut sealer)? {
6db49c0ecf71atagen2026-09-26 12:41 UTC142
            Some(record) => self.write(&mut sealer, &record.soft_logged_out()),
6f1d6591c10datagen2026-09-26 09:52 UTC143
            None => Ok(()),
6f1d6591c10datagen2026-09-26 09:52 UTC144
        }
6f1d6591c10datagen2026-09-26 09:52 UTC145
    }
6f1d6591c10datagen2026-09-26 09:52 UTC146
6db49c0ecf71atagen2026-09-26 12:41 UTC147
    fn forget_record(&self) -> Result<(), AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC148
        let mut sealer = self.lock();
6f1d6591c10datagen2026-09-26 09:52 UTC149
        backend::remove(&self.record)
6f1d6591c10datagen2026-09-26 09:52 UTC150
            .map_err(|error| account_error("removing the session record", error))?;
6f1d6591c10datagen2026-09-26 09:52 UTC151
        *sealer = None;
6db49c0ecf71atagen2026-09-26 12:41 UTC152
        Ok(())
6db49c0ecf71atagen2026-09-26 12:41 UTC153
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC154
}
6db49c0ecf71atagen2026-09-26 12:41 UTC155
6db49c0ecf71atagen2026-09-26 12:41 UTC156
#[cfg(not(target_family = "wasm"))]
6db49c0ecf71atagen2026-09-26 12:41 UTC157
impl SessionStore for EncryptedSessionStore {
6db49c0ecf71atagen2026-09-26 12:41 UTC158
    fn store(&self) -> &StoreConfig {
6db49c0ecf71atagen2026-09-26 12:41 UTC159
        &self.store
6f1d6591c10datagen2026-09-26 09:52 UTC160
    }
6f1d6591c10datagen2026-09-26 09:52 UTC161
6db49c0ecf71atagen2026-09-26 12:41 UTC162
    fn load(&self) -> BoxFuture<'_, Result<Option<SessionRecord>, AccountError>> {
6db49c0ecf71atagen2026-09-26 12:41 UTC163
        Box::pin(async move { self.load_record() })
6db49c0ecf71atagen2026-09-26 12:41 UTC164
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC165
6db49c0ecf71atagen2026-09-26 12:41 UTC166
    fn save<'a>(&'a self, record: &'a SessionRecord) -> BoxFuture<'a, Result<(), AccountError>> {
6db49c0ecf71atagen2026-09-26 12:41 UTC167
        Box::pin(async move { self.save_record(record) })
6db49c0ecf71atagen2026-09-26 12:41 UTC168
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC169
6db49c0ecf71atagen2026-09-26 12:41 UTC170
    /// blocks the calling worker for the write and its fsyncs, about a millisecond
6db49c0ecf71atagen2026-09-26 12:41 UTC171
    fn save_blocking(&self, record: &SessionRecord) -> Option<Result<(), AccountError>> {
6db49c0ecf71atagen2026-09-26 12:41 UTC172
        Some(self.save_record(record))
6db49c0ecf71atagen2026-09-26 12:41 UTC173
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC174
6db49c0ecf71atagen2026-09-26 12:41 UTC175
    fn soft_log_out(&self) -> BoxFuture<'_, Result<(), AccountError>> {
6db49c0ecf71atagen2026-09-26 12:41 UTC176
        Box::pin(async move { self.soft_log_out_record() })
6db49c0ecf71atagen2026-09-26 12:41 UTC177
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC178
6db49c0ecf71atagen2026-09-26 12:41 UTC179
    fn discard(&self) -> BoxFuture<'_, Result<(), AccountError>> {
6db49c0ecf71atagen2026-09-26 12:41 UTC180
        Box::pin(async move {
6db49c0ecf71atagen2026-09-26 12:41 UTC181
            self.forget_record()?;
6db49c0ecf71atagen2026-09-26 12:41 UTC182
            self.store.delete().await
6db49c0ecf71atagen2026-09-26 12:41 UTC183
        })
6db49c0ecf71atagen2026-09-26 12:41 UTC184
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC185
}
6db49c0ecf71atagen2026-09-26 12:41 UTC186
6db49c0ecf71atagen2026-09-26 12:41 UTC187
#[cfg(target_family = "wasm")]
6db49c0ecf71atagen2026-09-26 12:41 UTC188
impl EncryptedSessionStore {
6f1d6591c10datagen2026-09-26 09:52 UTC189
    async fn read(
6f1d6591c10datagen2026-09-26 09:52 UTC190
        &self,
6f1d6591c10datagen2026-09-26 09:52 UTC191
        sealer: &mut Option<Sealer>,
6f1d6591c10datagen2026-09-26 09:52 UTC192
    ) -> Result<Option<SessionRecord>, AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC193
        let sealed = backend::read(&self.record)
713205a95af2atagen2026-09-26 09:24 UTC194
            .await
6db49c0ecf71atagen2026-09-26 12:41 UTC195
            .map_err(|error| account_error("reading the session record", error))?;
6db49c0ecf71atagen2026-09-26 12:41 UTC196
        self.open(sealer, sealed)
713205a95af2atagen2026-09-26 09:24 UTC197
    }
713205a95af2atagen2026-09-26 09:24 UTC198
6f1d6591c10datagen2026-09-26 09:52 UTC199
    async fn write(
6f1d6591c10datagen2026-09-26 09:52 UTC200
        &self,
6f1d6591c10datagen2026-09-26 09:52 UTC201
        sealer: &mut Option<Sealer>,
6f1d6591c10datagen2026-09-26 09:52 UTC202
        record: &SessionRecord,
6f1d6591c10datagen2026-09-26 09:52 UTC203
    ) -> Result<(), AccountError> {
f12377bce9b9atagen2026-09-26 12:32 UTC204
        let sealed = self.seal(sealer, record)?;
f12377bce9b9atagen2026-09-26 12:32 UTC205
        backend::write(&self.record, &sealed)
f12377bce9b9atagen2026-09-26 12:32 UTC206
            .await
f12377bce9b9atagen2026-09-26 12:32 UTC207
            .map_err(|error| account_error("writing the session record", error))
f12377bce9b9atagen2026-09-26 12:32 UTC208
    }
f12377bce9b9atagen2026-09-26 12:32 UTC209
6db49c0ecf71atagen2026-09-26 12:41 UTC210
    async fn load_record(&self) -> Result<Option<SessionRecord>, AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC211
        self.read(&mut *self.sealer.lock().await).await
f12377bce9b9atagen2026-09-26 12:32 UTC212
    }
f12377bce9b9atagen2026-09-26 12:32 UTC213
6db49c0ecf71atagen2026-09-26 12:41 UTC214
    async fn save_record(&self, record: &SessionRecord) -> Result<(), AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC215
        self.write(&mut *self.sealer.lock().await, record).await
6db49c0ecf71atagen2026-09-26 12:41 UTC216
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC217
6db49c0ecf71atagen2026-09-26 12:41 UTC218
    async fn soft_log_out_record(&self) -> Result<(), AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC219
        let mut sealer = self.sealer.lock().await;
6db49c0ecf71atagen2026-09-26 12:41 UTC220
        match self.read(&mut sealer).await? {
6db49c0ecf71atagen2026-09-26 12:41 UTC221
            Some(record) => self.write(&mut sealer, &record.soft_logged_out()).await,
6db49c0ecf71atagen2026-09-26 12:41 UTC222
            None => Ok(()),
6db49c0ecf71atagen2026-09-26 12:41 UTC223
        }
6db49c0ecf71atagen2026-09-26 12:41 UTC224
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC225
6db49c0ecf71atagen2026-09-26 12:41 UTC226
    async fn discard_all(&self) -> Result<(), AccountError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC227
        let mut sealer = self.sealer.lock().await;
6db49c0ecf71atagen2026-09-26 12:41 UTC228
        backend::remove(&self.record)
6db49c0ecf71atagen2026-09-26 12:41 UTC229
            .await
6db49c0ecf71atagen2026-09-26 12:41 UTC230
            .map_err(|error| error.into_account_error("removing the session record"))?;
6db49c0ecf71atagen2026-09-26 12:41 UTC231
        *sealer = None;
6db49c0ecf71atagen2026-09-26 12:41 UTC232
        self.store.delete().await
713205a95af2atagen2026-09-26 09:24 UTC233
    }
713205a95af2atagen2026-09-26 09:24 UTC234
}
713205a95af2atagen2026-09-26 09:24 UTC235
6db49c0ecf71atagen2026-09-26 12:41 UTC236
#[cfg(target_family = "wasm")]
713205a95af2atagen2026-09-26 09:24 UTC237
impl SessionStore for EncryptedSessionStore {
713205a95af2atagen2026-09-26 09:24 UTC238
    fn store(&self) -> &StoreConfig {
713205a95af2atagen2026-09-26 09:24 UTC239
        &self.store
713205a95af2atagen2026-09-26 09:24 UTC240
    }
713205a95af2atagen2026-09-26 09:24 UTC241
713205a95af2atagen2026-09-26 09:24 UTC242
    fn load(&self) -> BoxFuture<'_, Result<Option<SessionRecord>, AccountError>> {
713205a95af2atagen2026-09-26 09:24 UTC243
        Box::pin(self.load_record())
713205a95af2atagen2026-09-26 09:24 UTC244
    }
713205a95af2atagen2026-09-26 09:24 UTC245
713205a95af2atagen2026-09-26 09:24 UTC246
    fn save<'a>(&'a self, record: &'a SessionRecord) -> BoxFuture<'a, Result<(), AccountError>> {
713205a95af2atagen2026-09-26 09:24 UTC247
        Box::pin(self.save_record(record))
713205a95af2atagen2026-09-26 09:24 UTC248
    }
713205a95af2atagen2026-09-26 09:24 UTC249
6f1d6591c10datagen2026-09-26 09:52 UTC250
    fn soft_log_out(&self) -> BoxFuture<'_, Result<(), AccountError>> {
6f1d6591c10datagen2026-09-26 09:52 UTC251
        Box::pin(self.soft_log_out_record())
713205a95af2atagen2026-09-26 09:24 UTC252
    }
713205a95af2atagen2026-09-26 09:24 UTC253
713205a95af2atagen2026-09-26 09:24 UTC254
    fn discard(&self) -> BoxFuture<'_, Result<(), AccountError>> {
713205a95af2atagen2026-09-26 09:24 UTC255
        Box::pin(self.discard_all())
713205a95af2atagen2026-09-26 09:24 UTC256
    }
713205a95af2atagen2026-09-26 09:24 UTC257
}
713205a95af2atagen2026-09-26 09:24 UTC258
713205a95af2atagen2026-09-26 09:24 UTC259
impl Sealer {
713205a95af2atagen2026-09-26 09:24 UTC260
    fn new(passphrase: &str) -> Result<Self, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC261
        let cipher = StoreCipher::new()
713205a95af2atagen2026-09-26 09:24 UTC262
            .map_err(|error| account_error("creating the session record cipher", error))?;
713205a95af2atagen2026-09-26 09:24 UTC263
        let exported = cipher
713205a95af2atagen2026-09-26 09:24 UTC264
            .export(passphrase)
713205a95af2atagen2026-09-26 09:24 UTC265
            .map_err(|error| account_error("sealing the session record cipher", error))?;
713205a95af2atagen2026-09-26 09:24 UTC266
        Ok(Self { cipher, exported })
713205a95af2atagen2026-09-26 09:24 UTC267
    }
713205a95af2atagen2026-09-26 09:24 UTC268
713205a95af2atagen2026-09-26 09:24 UTC269
    fn seal(&self, record: &SessionRecord) -> Result<Vec<u8>, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC270
        let value = self
713205a95af2atagen2026-09-26 09:24 UTC271
            .cipher
713205a95af2atagen2026-09-26 09:24 UTC272
            .encrypt_value(record)
713205a95af2atagen2026-09-26 09:24 UTC273
            .map_err(|error| account_error("sealing the session record", error))?;
713205a95af2atagen2026-09-26 09:24 UTC274
        let length = u32::try_from(self.exported.len())
713205a95af2atagen2026-09-26 09:24 UTC275
            .expect("an exported store cipher is a few hundred bytes");
713205a95af2atagen2026-09-26 09:24 UTC276
        let mut sealed = Vec::with_capacity(4 + self.exported.len() + value.len());
713205a95af2atagen2026-09-26 09:24 UTC277
        sealed.extend_from_slice(&length.to_be_bytes());
713205a95af2atagen2026-09-26 09:24 UTC278
        sealed.extend_from_slice(&self.exported);
713205a95af2atagen2026-09-26 09:24 UTC279
        sealed.extend_from_slice(&value);
713205a95af2atagen2026-09-26 09:24 UTC280
        Ok(sealed)
713205a95af2atagen2026-09-26 09:24 UTC281
    }
713205a95af2atagen2026-09-26 09:24 UTC282
}
713205a95af2atagen2026-09-26 09:24 UTC283
713205a95af2atagen2026-09-26 09:24 UTC284
fn unseal(passphrase: &str, sealed: &[u8]) -> Result<(Sealer, SessionRecord), AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC285
    let truncated = || {
713205a95af2atagen2026-09-26 09:24 UTC286
        account_error(
713205a95af2atagen2026-09-26 09:24 UTC287
            "opening the session record",
713205a95af2atagen2026-09-26 09:24 UTC288
            std::io::Error::from(std::io::ErrorKind::UnexpectedEof),
713205a95af2atagen2026-09-26 09:24 UTC289
        )
713205a95af2atagen2026-09-26 09:24 UTC290
    };
713205a95af2atagen2026-09-26 09:24 UTC291
    let (length, rest) = sealed.split_first_chunk::<4>().ok_or_else(truncated)?;
713205a95af2atagen2026-09-26 09:24 UTC292
    let (exported, value) = rest
713205a95af2atagen2026-09-26 09:24 UTC293
        .split_at_checked(u32::from_be_bytes(*length) as usize)
713205a95af2atagen2026-09-26 09:24 UTC294
        .ok_or_else(truncated)?;
713205a95af2atagen2026-09-26 09:24 UTC295
    let cipher = StoreCipher::import(passphrase, exported)
713205a95af2atagen2026-09-26 09:24 UTC296
        .map_err(|error| account_error("opening the session record cipher", error))?;
713205a95af2atagen2026-09-26 09:24 UTC297
    let record = cipher
713205a95af2atagen2026-09-26 09:24 UTC298
        .decrypt_value(value)
713205a95af2atagen2026-09-26 09:24 UTC299
        .map_err(|error| account_error("opening the session record", error))?;
713205a95af2atagen2026-09-26 09:24 UTC300
    let sealer = Sealer {
713205a95af2atagen2026-09-26 09:24 UTC301
        cipher,
713205a95af2atagen2026-09-26 09:24 UTC302
        exported: exported.to_vec(),
713205a95af2atagen2026-09-26 09:24 UTC303
    };
713205a95af2atagen2026-09-26 09:24 UTC304
    Ok((sealer, record))
713205a95af2atagen2026-09-26 09:24 UTC305
}
713205a95af2atagen2026-09-26 09:24 UTC306
713205a95af2atagen2026-09-26 09:24 UTC307
#[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC308
mod backend {
713205a95af2atagen2026-09-26 09:24 UTC309
    use std::{
6db49c0ecf71atagen2026-09-26 12:41 UTC310
        ffi::OsString,
713205a95af2atagen2026-09-26 09:24 UTC311
        fs::{self, File, OpenOptions},
713205a95af2atagen2026-09-26 09:24 UTC312
        io::{self, ErrorKind, Write},
713205a95af2atagen2026-09-26 09:24 UTC313
        path::{Path, PathBuf},
6db49c0ecf71atagen2026-09-26 12:41 UTC314
        sync::atomic::{AtomicU64, Ordering},
713205a95af2atagen2026-09-26 09:24 UTC315
    };
713205a95af2atagen2026-09-26 09:24 UTC316
6db49c0ecf71atagen2026-09-26 12:41 UTC317
    static WRITES: AtomicU64 = AtomicU64::new(0);
6db49c0ecf71atagen2026-09-26 12:41 UTC318
6db49c0ecf71atagen2026-09-26 12:41 UTC319
    pub(super) fn read(path: &Path) -> io::Result<Option<Vec<u8>>> {
713205a95af2atagen2026-09-26 09:24 UTC320
        match fs::read(path) {
713205a95af2atagen2026-09-26 09:24 UTC321
            Ok(sealed) => Ok(Some(sealed)),
713205a95af2atagen2026-09-26 09:24 UTC322
            Err(error) if error.kind() == ErrorKind::NotFound => Ok(None),
713205a95af2atagen2026-09-26 09:24 UTC323
            Err(error) => Err(error),
713205a95af2atagen2026-09-26 09:24 UTC324
        }
713205a95af2atagen2026-09-26 09:24 UTC325
    }
713205a95af2atagen2026-09-26 09:24 UTC326
6db49c0ecf71atagen2026-09-26 12:41 UTC327
    pub(super) fn write(path: &Path, sealed: &[u8]) -> io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC328
        let parent = match path.parent() {
713205a95af2atagen2026-09-26 09:24 UTC329
            Some(parent) if !parent.as_os_str().is_empty() => parent,
713205a95af2atagen2026-09-26 09:24 UTC330
            _ => Path::new("."),
713205a95af2atagen2026-09-26 09:24 UTC331
        };
713205a95af2atagen2026-09-26 09:24 UTC332
        fs::create_dir_all(parent)?;
6db49c0ecf71atagen2026-09-26 12:41 UTC333
        let (temporary, mut file) = temporary_file(path)?;
6db49c0ecf71atagen2026-09-26 12:41 UTC334
        let written = replace(path, &temporary, &mut file, parent, sealed);
713205a95af2atagen2026-09-26 09:24 UTC335
        if written.is_err() {
713205a95af2atagen2026-09-26 09:24 UTC336
            let _ = fs::remove_file(&temporary);
713205a95af2atagen2026-09-26 09:24 UTC337
        }
713205a95af2atagen2026-09-26 09:24 UTC338
        written
713205a95af2atagen2026-09-26 09:24 UTC339
    }
713205a95af2atagen2026-09-26 09:24 UTC340
6db49c0ecf71atagen2026-09-26 12:41 UTC341
    fn temporary_file(path: &Path) -> io::Result<(PathBuf, File)> {
6db49c0ecf71atagen2026-09-26 12:41 UTC342
        loop {
6db49c0ecf71atagen2026-09-26 12:41 UTC343
            let mut name = OsString::from(path.as_os_str());
6db49c0ecf71atagen2026-09-26 12:41 UTC344
            let write = WRITES.fetch_add(1, Ordering::Relaxed);
6db49c0ecf71atagen2026-09-26 12:41 UTC345
            name.push(format!(".{}.{write}.tmp", std::process::id()));
6db49c0ecf71atagen2026-09-26 12:41 UTC346
            let temporary = PathBuf::from(name);
6db49c0ecf71atagen2026-09-26 12:41 UTC347
            match private_file(&temporary) {
6db49c0ecf71atagen2026-09-26 12:41 UTC348
                Ok(file) => return Ok((temporary, file)),
6db49c0ecf71atagen2026-09-26 12:41 UTC349
                Err(error) if error.kind() == ErrorKind::AlreadyExists => {}
6db49c0ecf71atagen2026-09-26 12:41 UTC350
                Err(error) => return Err(error),
6db49c0ecf71atagen2026-09-26 12:41 UTC351
            }
6db49c0ecf71atagen2026-09-26 12:41 UTC352
        }
6db49c0ecf71atagen2026-09-26 12:41 UTC353
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC354
6db49c0ecf71atagen2026-09-26 12:41 UTC355
    fn replace(
6db49c0ecf71atagen2026-09-26 12:41 UTC356
        path: &Path,
6db49c0ecf71atagen2026-09-26 12:41 UTC357
        temporary: &Path,
6db49c0ecf71atagen2026-09-26 12:41 UTC358
        file: &mut File,
6db49c0ecf71atagen2026-09-26 12:41 UTC359
        parent: &Path,
6db49c0ecf71atagen2026-09-26 12:41 UTC360
        sealed: &[u8],
6db49c0ecf71atagen2026-09-26 12:41 UTC361
    ) -> io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC362
        file.write_all(sealed)?;
713205a95af2atagen2026-09-26 09:24 UTC363
        file.sync_all()?;
713205a95af2atagen2026-09-26 09:24 UTC364
        fs::rename(temporary, path)?;
713205a95af2atagen2026-09-26 09:24 UTC365
        sync_directory(parent)
713205a95af2atagen2026-09-26 09:24 UTC366
    }
713205a95af2atagen2026-09-26 09:24 UTC367
6db49c0ecf71atagen2026-09-26 12:41 UTC368
    pub(super) fn remove(path: &Path) -> io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC369
        match fs::remove_file(path) {
713205a95af2atagen2026-09-26 09:24 UTC370
            Err(error) if error.kind() != ErrorKind::NotFound => Err(error),
713205a95af2atagen2026-09-26 09:24 UTC371
            _ => Ok(()),
713205a95af2atagen2026-09-26 09:24 UTC372
        }
713205a95af2atagen2026-09-26 09:24 UTC373
    }
713205a95af2atagen2026-09-26 09:24 UTC374
6db49c0ecf71atagen2026-09-26 12:41 UTC375
    /// `create_new` is `O_EXCL`, which posix says never follows a symlink, even a dangling one
713205a95af2atagen2026-09-26 09:24 UTC376
    #[cfg(unix)]
713205a95af2atagen2026-09-26 09:24 UTC377
    fn private_file(path: &Path) -> io::Result<File> {
713205a95af2atagen2026-09-26 09:24 UTC378
        use std::os::unix::fs::OpenOptionsExt;
713205a95af2atagen2026-09-26 09:24 UTC379
        OpenOptions::new()
713205a95af2atagen2026-09-26 09:24 UTC380
            .write(true)
6db49c0ecf71atagen2026-09-26 12:41 UTC381
            .create_new(true)
713205a95af2atagen2026-09-26 09:24 UTC382
            .mode(0o600)
713205a95af2atagen2026-09-26 09:24 UTC383
            .open(path)
713205a95af2atagen2026-09-26 09:24 UTC384
    }
713205a95af2atagen2026-09-26 09:24 UTC385
713205a95af2atagen2026-09-26 09:24 UTC386
    #[cfg(not(unix))]
713205a95af2atagen2026-09-26 09:24 UTC387
    fn private_file(path: &Path) -> io::Result<File> {
6db49c0ecf71atagen2026-09-26 12:41 UTC388
        OpenOptions::new().write(true).create_new(true).open(path)
713205a95af2atagen2026-09-26 09:24 UTC389
    }
713205a95af2atagen2026-09-26 09:24 UTC390
713205a95af2atagen2026-09-26 09:24 UTC391
    /// a rename isn't durable until its directory is synced
713205a95af2atagen2026-09-26 09:24 UTC392
    #[cfg(unix)]
713205a95af2atagen2026-09-26 09:24 UTC393
    fn sync_directory(path: &Path) -> io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC394
        File::open(path)?.sync_all()
713205a95af2atagen2026-09-26 09:24 UTC395
    }
713205a95af2atagen2026-09-26 09:24 UTC396
713205a95af2atagen2026-09-26 09:24 UTC397
    #[cfg(not(unix))]
713205a95af2atagen2026-09-26 09:24 UTC398
    fn sync_directory(_: &Path) -> io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC399
        Ok(())
713205a95af2atagen2026-09-26 09:24 UTC400
    }
713205a95af2atagen2026-09-26 09:24 UTC401
}
713205a95af2atagen2026-09-26 09:24 UTC402
713205a95af2atagen2026-09-26 09:24 UTC403
#[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC404
mod backend {
713205a95af2atagen2026-09-26 09:24 UTC405
    use indexed_db_futures::{
713205a95af2atagen2026-09-26 09:24 UTC406
        Build,
713205a95af2atagen2026-09-26 09:24 UTC407
        database::Database,
713205a95af2atagen2026-09-26 09:24 UTC408
        error::{Error, OpenDbError},
713205a95af2atagen2026-09-26 09:24 UTC409
        prelude::*,
713205a95af2atagen2026-09-26 09:24 UTC410
        transaction::{Transaction, TransactionMode},
713205a95af2atagen2026-09-26 09:24 UTC411
        typed_array::{Uint8Array, Uint8ArraySlice},
713205a95af2atagen2026-09-26 09:24 UTC412
    };
713205a95af2atagen2026-09-26 09:24 UTC413
713205a95af2atagen2026-09-26 09:24 UTC414
    const RECORDS: &str = "records";
713205a95af2atagen2026-09-26 09:24 UTC415
    const KEY: &str = "record";
713205a95af2atagen2026-09-26 09:24 UTC416
713205a95af2atagen2026-09-26 09:24 UTC417
    async fn open(name: &str) -> Result<Database, OpenDbError> {
713205a95af2atagen2026-09-26 09:24 UTC418
        Database::open(name)
713205a95af2atagen2026-09-26 09:24 UTC419
            .with_version(1u32)
713205a95af2atagen2026-09-26 09:24 UTC420
            .with_on_upgrade_needed(|_, transaction: &Transaction<'_>| -> Result<(), Error> {
713205a95af2atagen2026-09-26 09:24 UTC421
                transaction.db().create_object_store(RECORDS).build()?;
713205a95af2atagen2026-09-26 09:24 UTC422
                Ok(())
713205a95af2atagen2026-09-26 09:24 UTC423
            })
713205a95af2atagen2026-09-26 09:24 UTC424
            .build()?
713205a95af2atagen2026-09-26 09:24 UTC425
            .await
713205a95af2atagen2026-09-26 09:24 UTC426
    }
713205a95af2atagen2026-09-26 09:24 UTC427
6db49c0ecf71atagen2026-09-26 12:41 UTC428
    /// the crate's `Database` never closes itself on drop, and an open one blocks deleting it
713205a95af2atagen2026-09-26 09:24 UTC429
    pub(super) async fn read(name: &str) -> Result<Option<Vec<u8>>, OpenDbError> {
713205a95af2atagen2026-09-26 09:24 UTC430
        let database = open(name).await?;
6db49c0ecf71atagen2026-09-26 12:41 UTC431
        let sealed = get(&database).await;
6db49c0ecf71atagen2026-09-26 12:41 UTC432
        database.close();
6db49c0ecf71atagen2026-09-26 12:41 UTC433
        Ok(sealed?.map(Into::into))
6db49c0ecf71atagen2026-09-26 12:41 UTC434
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC435
6db49c0ecf71atagen2026-09-26 12:41 UTC436
    pub(super) async fn write(name: &str, sealed: &[u8]) -> Result<(), OpenDbError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC437
        let database = open(name).await?;
6db49c0ecf71atagen2026-09-26 12:41 UTC438
        let written = put(&database, sealed).await;
6db49c0ecf71atagen2026-09-26 12:41 UTC439
        database.close();
6db49c0ecf71atagen2026-09-26 12:41 UTC440
        Ok(written?)
6db49c0ecf71atagen2026-09-26 12:41 UTC441
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC442
6db49c0ecf71atagen2026-09-26 12:41 UTC443
    pub(super) async fn remove(name: &str) -> Result<(), super::DeleteError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC444
        super::delete_database(name).await
6db49c0ecf71atagen2026-09-26 12:41 UTC445
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC446
6db49c0ecf71atagen2026-09-26 12:41 UTC447
    async fn get(database: &Database) -> Result<Option<Uint8Array>, Error> {
6db49c0ecf71atagen2026-09-26 12:41 UTC448
        database
713205a95af2atagen2026-09-26 09:24 UTC449
            .transaction(RECORDS)
713205a95af2atagen2026-09-26 09:24 UTC450
            .with_mode(TransactionMode::Readonly)
713205a95af2atagen2026-09-26 09:24 UTC451
            .build()?
713205a95af2atagen2026-09-26 09:24 UTC452
            .object_store(RECORDS)?
713205a95af2atagen2026-09-26 09:24 UTC453
            .get(KEY)
6db49c0ecf71atagen2026-09-26 12:41 UTC454
            .await
713205a95af2atagen2026-09-26 09:24 UTC455
    }
713205a95af2atagen2026-09-26 09:24 UTC456
6db49c0ecf71atagen2026-09-26 12:41 UTC457
    async fn put(database: &Database, sealed: &[u8]) -> Result<(), Error> {
713205a95af2atagen2026-09-26 09:24 UTC458
        let transaction = database
713205a95af2atagen2026-09-26 09:24 UTC459
            .transaction(RECORDS)
713205a95af2atagen2026-09-26 09:24 UTC460
            .with_mode(TransactionMode::Readwrite)
713205a95af2atagen2026-09-26 09:24 UTC461
            .build()?;
713205a95af2atagen2026-09-26 09:24 UTC462
        transaction
713205a95af2atagen2026-09-26 09:24 UTC463
            .object_store(RECORDS)?
713205a95af2atagen2026-09-26 09:24 UTC464
            .put(Uint8ArraySlice::new(sealed))
713205a95af2atagen2026-09-26 09:24 UTC465
            .with_key(KEY)
713205a95af2atagen2026-09-26 09:24 UTC466
            .build()?;
6db49c0ecf71atagen2026-09-26 12:41 UTC467
        transaction.commit().await
713205a95af2atagen2026-09-26 09:24 UTC468
    }
713205a95af2atagen2026-09-26 09:24 UTC469
}
713205a95af2atagen2026-09-26 09:24 UTC470
713205a95af2atagen2026-09-26 09:24 UTC471
#[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC472
pub(crate) fn delete_sqlite_store(path: &Path) -> std::io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC473
    use std::{fs, io::ErrorKind};
713205a95af2atagen2026-09-26 09:24 UTC474
713205a95af2atagen2026-09-26 09:24 UTC475
    // matrix-sdk-sqlite 0.18's database names, which it keeps private
713205a95af2atagen2026-09-26 09:24 UTC476
    const DATABASES: [&str; 4] = [
713205a95af2atagen2026-09-26 09:24 UTC477
        "matrix-sdk-state.sqlite3",
713205a95af2atagen2026-09-26 09:24 UTC478
        "matrix-sdk-crypto.sqlite3",
713205a95af2atagen2026-09-26 09:24 UTC479
        "matrix-sdk-event-cache.sqlite3",
713205a95af2atagen2026-09-26 09:24 UTC480
        "matrix-sdk-media.sqlite3",
713205a95af2atagen2026-09-26 09:24 UTC481
    ];
713205a95af2atagen2026-09-26 09:24 UTC482
    for database in DATABASES {
713205a95af2atagen2026-09-26 09:24 UTC483
        for suffix in ["", "-wal", "-shm", "-journal"] {
713205a95af2atagen2026-09-26 09:24 UTC484
            match fs::remove_file(path.join(format!("{database}{suffix}"))) {
713205a95af2atagen2026-09-26 09:24 UTC485
                Err(error) if error.kind() != ErrorKind::NotFound => return Err(error),
713205a95af2atagen2026-09-26 09:24 UTC486
                _ => {}
713205a95af2atagen2026-09-26 09:24 UTC487
            }
713205a95af2atagen2026-09-26 09:24 UTC488
        }
713205a95af2atagen2026-09-26 09:24 UTC489
    }
a250c7fa43fbatagen2026-09-26 13:35 UTC490
    match fs::remove_file(path.join(crate::store_lock::LOCK_FILE)) {
a250c7fa43fbatagen2026-09-26 13:35 UTC491
        Err(error) if error.kind() != ErrorKind::NotFound => return Err(error),
a250c7fa43fbatagen2026-09-26 13:35 UTC492
        _ => {}
a250c7fa43fbatagen2026-09-26 13:35 UTC493
    }
713205a95af2atagen2026-09-26 09:24 UTC494
    match fs::remove_dir(path) {
713205a95af2atagen2026-09-26 09:24 UTC495
        Err(error) if error.kind() != ErrorKind::NotFound => Err(error),
713205a95af2atagen2026-09-26 09:24 UTC496
        _ => Ok(()),
713205a95af2atagen2026-09-26 09:24 UTC497
    }
713205a95af2atagen2026-09-26 09:24 UTC498
}
713205a95af2atagen2026-09-26 09:24 UTC499
713205a95af2atagen2026-09-26 09:24 UTC500
#[cfg(feature = "indexeddb")]
6db49c0ecf71atagen2026-09-26 12:41 UTC501
pub(crate) async fn delete_indexeddb_store(name: &str) -> Result<(), DeleteError> {
713205a95af2atagen2026-09-26 09:24 UTC502
    // the databases matrix-sdk-indexeddb 0.18 opens under one store name
713205a95af2atagen2026-09-26 09:24 UTC503
    for suffix in [
713205a95af2atagen2026-09-26 09:24 UTC504
        "",
713205a95af2atagen2026-09-26 09:24 UTC505
        "::matrix-sdk-state",
713205a95af2atagen2026-09-26 09:24 UTC506
        "::matrix-sdk-crypto",
713205a95af2atagen2026-09-26 09:24 UTC507
        "::matrix-sdk-crypto-meta",
713205a95af2atagen2026-09-26 09:24 UTC508
        "::event_cache",
713205a95af2atagen2026-09-26 09:24 UTC509
        "::media",
713205a95af2atagen2026-09-26 09:24 UTC510
    ] {
6db49c0ecf71atagen2026-09-26 12:41 UTC511
        delete_database(&format!("{name}{suffix}")).await?;
713205a95af2atagen2026-09-26 09:24 UTC512
    }
713205a95af2atagen2026-09-26 09:24 UTC513
    Ok(())
713205a95af2atagen2026-09-26 09:24 UTC514
}
6db49c0ecf71atagen2026-09-26 12:41 UTC515
6db49c0ecf71atagen2026-09-26 12:41 UTC516
#[cfg(feature = "indexeddb")]
6db49c0ecf71atagen2026-09-26 12:41 UTC517
#[derive(Debug, thiserror::Error)]
6db49c0ecf71atagen2026-09-26 12:41 UTC518
pub(crate) enum DeleteError {
6db49c0ecf71atagen2026-09-26 12:41 UTC519
    #[error("the database is still open somewhere, it goes once that closes")]
6db49c0ecf71atagen2026-09-26 12:41 UTC520
    Blocked,
6db49c0ecf71atagen2026-09-26 12:41 UTC521
    #[error("this global scope has no IndexedDB")]
6db49c0ecf71atagen2026-09-26 12:41 UTC522
    Unavailable,
6db49c0ecf71atagen2026-09-26 12:41 UTC523
    #[error("deleting the database failed, {0}")]
6db49c0ecf71atagen2026-09-26 12:41 UTC524
    Failed(String),
6db49c0ecf71atagen2026-09-26 12:41 UTC525
}
6db49c0ecf71atagen2026-09-26 12:41 UTC526
6db49c0ecf71atagen2026-09-26 12:41 UTC527
#[cfg(feature = "indexeddb")]
6db49c0ecf71atagen2026-09-26 12:41 UTC528
impl DeleteError {
6db49c0ecf71atagen2026-09-26 12:41 UTC529
    pub(crate) fn into_account_error(self, context: &'static str) -> AccountError {
6db49c0ecf71atagen2026-09-26 12:41 UTC530
        match self {
6db49c0ecf71atagen2026-09-26 12:41 UTC531
            Self::Blocked => AccountError::StoreDeletePending,
6db49c0ecf71atagen2026-09-26 12:41 UTC532
            error => account_error(context, error),
6db49c0ecf71atagen2026-09-26 12:41 UTC533
        }
6db49c0ecf71atagen2026-09-26 12:41 UTC534
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC535
}
6db49c0ecf71atagen2026-09-26 12:41 UTC536
6db49c0ecf71atagen2026-09-26 12:41 UTC537
/// matrix_indexed_db_futures 0.7 ignores `blocked` on a delete, which then waits forever
6db49c0ecf71atagen2026-09-26 12:41 UTC538
#[cfg(feature = "indexeddb")]
6db49c0ecf71atagen2026-09-26 12:41 UTC539
async fn delete_database(name: &str) -> Result<(), DeleteError> {
6db49c0ecf71atagen2026-09-26 12:41 UTC540
    use wasm_bindgen::{JsCast, JsValue};
6db49c0ecf71atagen2026-09-26 12:41 UTC541
6db49c0ecf71atagen2026-09-26 12:41 UTC542
    const BLOCKED: &str = "blocked";
6db49c0ecf71atagen2026-09-26 12:41 UTC543
    let global = js_sys::global();
6db49c0ecf71atagen2026-09-26 12:41 UTC544
    let factory = if let Some(window) = global.dyn_ref::<web_sys::Window>() {
6db49c0ecf71atagen2026-09-26 12:41 UTC545
        window.indexed_db()
6db49c0ecf71atagen2026-09-26 12:41 UTC546
    } else if let Some(worker) = global.dyn_ref::<web_sys::WorkerGlobalScope>() {
6db49c0ecf71atagen2026-09-26 12:41 UTC547
        worker.indexed_db()
6db49c0ecf71atagen2026-09-26 12:41 UTC548
    } else {
6db49c0ecf71atagen2026-09-26 12:41 UTC549
        return Err(DeleteError::Unavailable);
6db49c0ecf71atagen2026-09-26 12:41 UTC550
    };
6db49c0ecf71atagen2026-09-26 12:41 UTC551
    let factory = factory.ok().flatten().ok_or(DeleteError::Unavailable)?;
6db49c0ecf71atagen2026-09-26 12:41 UTC552
    let request = factory
6db49c0ecf71atagen2026-09-26 12:41 UTC553
        .delete_database(name)
6db49c0ecf71atagen2026-09-26 12:41 UTC554
        .map_err(|error| DeleteError::Failed(format!("{error:?}")))?;
6db49c0ecf71atagen2026-09-26 12:41 UTC555
    let settled = js_sys::Promise::new(&mut |resolve, reject| {
6db49c0ecf71atagen2026-09-26 12:41 UTC556
        request.set_onsuccess(Some(&resolve));
6db49c0ecf71atagen2026-09-26 12:41 UTC557
        request.set_onerror(Some(&reject));
6db49c0ecf71atagen2026-09-26 12:41 UTC558
        let blocked = reject.bind1(&JsValue::NULL, &JsValue::from_str(BLOCKED));
6db49c0ecf71atagen2026-09-26 12:41 UTC559
        request.set_onblocked(Some(blocked.unchecked_ref()));
6db49c0ecf71atagen2026-09-26 12:41 UTC560
    });
6db49c0ecf71atagen2026-09-26 12:41 UTC561
    match wasm_bindgen_futures::JsFuture::from(settled).await {
6db49c0ecf71atagen2026-09-26 12:41 UTC562
        Ok(_) => Ok(()),
6db49c0ecf71atagen2026-09-26 12:41 UTC563
        Err(reason) if reason.as_string().as_deref() == Some(BLOCKED) => Err(DeleteError::Blocked),
6db49c0ecf71atagen2026-09-26 12:41 UTC564
        Err(_) => Err(DeleteError::Failed(format!("{:?}", request.error()))),
6db49c0ecf71atagen2026-09-26 12:41 UTC565
    }
6db49c0ecf71atagen2026-09-26 12:41 UTC566
}