atagen

macltok

public
Star0
Watch0
7b376d620a3d030cadb0437821b25ca7136e4995
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
use tokio::sync::Mutex;
713205a95af2atagen2026-09-26 09:24 UTC7
713205a95af2atagen2026-09-26 09:24 UTC8
use crate::{AccountError, SessionRecord, StoreConfig, account::account_error};
713205a95af2atagen2026-09-26 09:24 UTC9
713205a95af2atagen2026-09-26 09:24 UTC10
#[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC11
type RecordLocation = PathBuf;
713205a95af2atagen2026-09-26 09:24 UTC12
#[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC13
type RecordLocation = String;
713205a95af2atagen2026-09-26 09:24 UTC14
713205a95af2atagen2026-09-26 09:24 UTC15
/// one login's record and the matrix store it belongs to. the engine rotates refresh tokens
713205a95af2atagen2026-09-26 09:24 UTC16
/// and each works once, so a `save` has to land before the next refresh or the login is gone
713205a95af2atagen2026-09-26 09:24 UTC17
pub trait SessionStore: SendOutsideWasm + SyncOutsideWasm {
713205a95af2atagen2026-09-26 09:24 UTC18
    fn store(&self) -> &StoreConfig;
713205a95af2atagen2026-09-26 09:24 UTC19
713205a95af2atagen2026-09-26 09:24 UTC20
    fn load(&self) -> BoxFuture<'_, Result<Option<SessionRecord>, AccountError>>;
713205a95af2atagen2026-09-26 09:24 UTC21
713205a95af2atagen2026-09-26 09:24 UTC22
    /// replace the saved record, all or nothing
713205a95af2atagen2026-09-26 09:24 UTC23
    fn save<'a>(&'a self, record: &'a SessionRecord) -> BoxFuture<'a, Result<(), AccountError>>;
713205a95af2atagen2026-09-26 09:24 UTC24
f12377bce9b9atagen2026-09-26 12:32 UTC25
    /// `save`, but without awaiting. the sdk calls it from inside a refresh, before the new
f12377bce9b9atagen2026-09-26 12:32 UTC26
    /// refresh token is ever used, so a store that can write synchronously closes the gap
f12377bce9b9atagen2026-09-26 12:32 UTC27
    /// a crash would otherwise lose the login through. `None` leaves the refresh to `save`
f12377bce9b9atagen2026-09-26 12:32 UTC28
    #[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC29
    fn save_blocking(&self, _record: &SessionRecord) -> Option<Result<(), AccountError>> {
f12377bce9b9atagen2026-09-26 12:32 UTC30
        None
f12377bce9b9atagen2026-09-26 12:32 UTC31
    }
f12377bce9b9atagen2026-09-26 12:32 UTC32
6f1d6591c10datagen2026-09-26 09:52 UTC33
    /// swap the record for one that only says who to log in again as, keeping the store.
6f1d6591c10datagen2026-09-26 09:52 UTC34
    /// `Session::restore` then answers `Restore::SoftLoggedOut`, even after a restart
6f1d6591c10datagen2026-09-26 09:52 UTC35
    fn soft_log_out(&self) -> BoxFuture<'_, Result<(), AccountError>>;
713205a95af2atagen2026-09-26 09:24 UTC36
713205a95af2atagen2026-09-26 09:24 UTC37
    /// drop the record and delete the store, once nothing has the store open
713205a95af2atagen2026-09-26 09:24 UTC38
    fn discard(&self) -> BoxFuture<'_, Result<(), AccountError>>;
713205a95af2atagen2026-09-26 09:24 UTC39
}
713205a95af2atagen2026-09-26 09:24 UTC40
713205a95af2atagen2026-09-26 09:24 UTC41
/// keeps the record sealed with the store's passphrase, in a file natively and in an
713205a95af2atagen2026-09-26 09:24 UTC42
/// IndexedDB database of its own in a browser
713205a95af2atagen2026-09-26 09:24 UTC43
pub struct EncryptedSessionStore {
713205a95af2atagen2026-09-26 09:24 UTC44
    record: RecordLocation,
713205a95af2atagen2026-09-26 09:24 UTC45
    store: StoreConfig,
713205a95af2atagen2026-09-26 09:24 UTC46
    sealer: Mutex<Option<Sealer>>,
713205a95af2atagen2026-09-26 09:24 UTC47
}
713205a95af2atagen2026-09-26 09:24 UTC48
713205a95af2atagen2026-09-26 09:24 UTC49
struct Sealer {
713205a95af2atagen2026-09-26 09:24 UTC50
    cipher: StoreCipher,
713205a95af2atagen2026-09-26 09:24 UTC51
    exported: Vec<u8>,
713205a95af2atagen2026-09-26 09:24 UTC52
}
713205a95af2atagen2026-09-26 09:24 UTC53
713205a95af2atagen2026-09-26 09:24 UTC54
impl EncryptedSessionStore {
713205a95af2atagen2026-09-26 09:24 UTC55
    #[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC56
    #[must_use]
713205a95af2atagen2026-09-26 09:24 UTC57
    pub fn file(record: impl Into<PathBuf>, store: StoreConfig) -> Self {
713205a95af2atagen2026-09-26 09:24 UTC58
        Self::new(record.into(), store)
713205a95af2atagen2026-09-26 09:24 UTC59
    }
713205a95af2atagen2026-09-26 09:24 UTC60
713205a95af2atagen2026-09-26 09:24 UTC61
    /// the record lives beside the store, in `<name>::macltok-session`
713205a95af2atagen2026-09-26 09:24 UTC62
    #[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC63
    #[must_use]
713205a95af2atagen2026-09-26 09:24 UTC64
    pub fn indexeddb(store: StoreConfig) -> Self {
713205a95af2atagen2026-09-26 09:24 UTC65
        let record = format!("{}::macltok-session", store.indexeddb_name());
713205a95af2atagen2026-09-26 09:24 UTC66
        Self::new(record, store)
713205a95af2atagen2026-09-26 09:24 UTC67
    }
713205a95af2atagen2026-09-26 09:24 UTC68
713205a95af2atagen2026-09-26 09:24 UTC69
    fn new(record: RecordLocation, store: StoreConfig) -> Self {
713205a95af2atagen2026-09-26 09:24 UTC70
        Self {
713205a95af2atagen2026-09-26 09:24 UTC71
            record,
713205a95af2atagen2026-09-26 09:24 UTC72
            store,
713205a95af2atagen2026-09-26 09:24 UTC73
            sealer: Mutex::new(None),
713205a95af2atagen2026-09-26 09:24 UTC74
        }
713205a95af2atagen2026-09-26 09:24 UTC75
    }
713205a95af2atagen2026-09-26 09:24 UTC76
713205a95af2atagen2026-09-26 09:24 UTC77
    async fn load_record(&self) -> Result<Option<SessionRecord>, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC78
        let mut sealer = self.sealer.lock().await;
6f1d6591c10datagen2026-09-26 09:52 UTC79
        self.read(&mut sealer).await
6f1d6591c10datagen2026-09-26 09:52 UTC80
    }
6f1d6591c10datagen2026-09-26 09:52 UTC81
6f1d6591c10datagen2026-09-26 09:52 UTC82
    async fn save_record(&self, record: &SessionRecord) -> Result<(), AccountError> {
6f1d6591c10datagen2026-09-26 09:52 UTC83
        let mut sealer = self.sealer.lock().await;
6f1d6591c10datagen2026-09-26 09:52 UTC84
        self.write(&mut sealer, record).await
6f1d6591c10datagen2026-09-26 09:52 UTC85
    }
6f1d6591c10datagen2026-09-26 09:52 UTC86
6f1d6591c10datagen2026-09-26 09:52 UTC87
    async fn soft_log_out_record(&self) -> Result<(), AccountError> {
6f1d6591c10datagen2026-09-26 09:52 UTC88
        let mut sealer = self.sealer.lock().await;
6f1d6591c10datagen2026-09-26 09:52 UTC89
        match self.read(&mut sealer).await? {
6f1d6591c10datagen2026-09-26 09:52 UTC90
            Some(record) => self.write(&mut sealer, &record.soft_logged_out()).await,
6f1d6591c10datagen2026-09-26 09:52 UTC91
            None => Ok(()),
6f1d6591c10datagen2026-09-26 09:52 UTC92
        }
6f1d6591c10datagen2026-09-26 09:52 UTC93
    }
6f1d6591c10datagen2026-09-26 09:52 UTC94
6f1d6591c10datagen2026-09-26 09:52 UTC95
    async fn discard_all(&self) -> Result<(), AccountError> {
6f1d6591c10datagen2026-09-26 09:52 UTC96
        let mut sealer = self.sealer.lock().await;
6f1d6591c10datagen2026-09-26 09:52 UTC97
        backend::remove(&self.record)
6f1d6591c10datagen2026-09-26 09:52 UTC98
            .await
6f1d6591c10datagen2026-09-26 09:52 UTC99
            .map_err(|error| account_error("removing the session record", error))?;
6f1d6591c10datagen2026-09-26 09:52 UTC100
        *sealer = None;
6f1d6591c10datagen2026-09-26 09:52 UTC101
        self.store.delete().await
6f1d6591c10datagen2026-09-26 09:52 UTC102
    }
6f1d6591c10datagen2026-09-26 09:52 UTC103
6f1d6591c10datagen2026-09-26 09:52 UTC104
    async fn read(
6f1d6591c10datagen2026-09-26 09:52 UTC105
        &self,
6f1d6591c10datagen2026-09-26 09:52 UTC106
        sealer: &mut Option<Sealer>,
6f1d6591c10datagen2026-09-26 09:52 UTC107
    ) -> Result<Option<SessionRecord>, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC108
        let Some(sealed) = backend::read(&self.record)
713205a95af2atagen2026-09-26 09:24 UTC109
            .await
713205a95af2atagen2026-09-26 09:24 UTC110
            .map_err(|error| account_error("reading the session record", error))?
713205a95af2atagen2026-09-26 09:24 UTC111
        else {
713205a95af2atagen2026-09-26 09:24 UTC112
            return Ok(None);
713205a95af2atagen2026-09-26 09:24 UTC113
        };
713205a95af2atagen2026-09-26 09:24 UTC114
        let (opened, record) = unseal(self.store.passphrase(), &sealed)?;
713205a95af2atagen2026-09-26 09:24 UTC115
        *sealer = Some(opened);
713205a95af2atagen2026-09-26 09:24 UTC116
        Ok(Some(record))
713205a95af2atagen2026-09-26 09:24 UTC117
    }
713205a95af2atagen2026-09-26 09:24 UTC118
6f1d6591c10datagen2026-09-26 09:52 UTC119
    async fn write(
6f1d6591c10datagen2026-09-26 09:52 UTC120
        &self,
6f1d6591c10datagen2026-09-26 09:52 UTC121
        sealer: &mut Option<Sealer>,
6f1d6591c10datagen2026-09-26 09:52 UTC122
        record: &SessionRecord,
6f1d6591c10datagen2026-09-26 09:52 UTC123
    ) -> Result<(), AccountError> {
f12377bce9b9atagen2026-09-26 12:32 UTC124
        let sealed = self.seal(sealer, record)?;
f12377bce9b9atagen2026-09-26 12:32 UTC125
        backend::write(&self.record, &sealed)
f12377bce9b9atagen2026-09-26 12:32 UTC126
            .await
f12377bce9b9atagen2026-09-26 12:32 UTC127
            .map_err(|error| account_error("writing the session record", error))
f12377bce9b9atagen2026-09-26 12:32 UTC128
    }
f12377bce9b9atagen2026-09-26 12:32 UTC129
f12377bce9b9atagen2026-09-26 12:32 UTC130
    fn seal(
f12377bce9b9atagen2026-09-26 12:32 UTC131
        &self,
f12377bce9b9atagen2026-09-26 12:32 UTC132
        sealer: &mut Option<Sealer>,
f12377bce9b9atagen2026-09-26 12:32 UTC133
        record: &SessionRecord,
f12377bce9b9atagen2026-09-26 12:32 UTC134
    ) -> Result<Vec<u8>, AccountError> {
6f1d6591c10datagen2026-09-26 09:52 UTC135
        let sealer = match sealer {
713205a95af2atagen2026-09-26 09:24 UTC136
            Some(sealer) => sealer,
713205a95af2atagen2026-09-26 09:24 UTC137
            empty => empty.insert(Sealer::new(self.store.passphrase())?),
713205a95af2atagen2026-09-26 09:24 UTC138
        };
f12377bce9b9atagen2026-09-26 12:32 UTC139
        sealer.seal(record)
f12377bce9b9atagen2026-09-26 12:32 UTC140
    }
f12377bce9b9atagen2026-09-26 12:32 UTC141
f12377bce9b9atagen2026-09-26 12:32 UTC142
    /// the lock is only ever held around one small write, so losing the race to it just
f12377bce9b9atagen2026-09-26 12:32 UTC143
    /// hands this refresh to the async save
f12377bce9b9atagen2026-09-26 12:32 UTC144
    #[cfg(feature = "sqlite")]
f12377bce9b9atagen2026-09-26 12:32 UTC145
    fn save_now(&self, record: &SessionRecord) -> Option<Result<(), AccountError>> {
f12377bce9b9atagen2026-09-26 12:32 UTC146
        let mut sealer = self.sealer.try_lock().ok()?;
f12377bce9b9atagen2026-09-26 12:32 UTC147
        Some(self.seal(&mut sealer, record).and_then(|sealed| {
f12377bce9b9atagen2026-09-26 12:32 UTC148
            backend::write_blocking(&self.record, &sealed)
f12377bce9b9atagen2026-09-26 12:32 UTC149
                .map_err(|error| account_error("writing the session record", error))
f12377bce9b9atagen2026-09-26 12:32 UTC150
        }))
713205a95af2atagen2026-09-26 09:24 UTC151
    }
713205a95af2atagen2026-09-26 09:24 UTC152
}
713205a95af2atagen2026-09-26 09:24 UTC153
713205a95af2atagen2026-09-26 09:24 UTC154
impl SessionStore for EncryptedSessionStore {
713205a95af2atagen2026-09-26 09:24 UTC155
    fn store(&self) -> &StoreConfig {
713205a95af2atagen2026-09-26 09:24 UTC156
        &self.store
713205a95af2atagen2026-09-26 09:24 UTC157
    }
713205a95af2atagen2026-09-26 09:24 UTC158
713205a95af2atagen2026-09-26 09:24 UTC159
    fn load(&self) -> BoxFuture<'_, Result<Option<SessionRecord>, AccountError>> {
713205a95af2atagen2026-09-26 09:24 UTC160
        Box::pin(self.load_record())
713205a95af2atagen2026-09-26 09:24 UTC161
    }
713205a95af2atagen2026-09-26 09:24 UTC162
713205a95af2atagen2026-09-26 09:24 UTC163
    fn save<'a>(&'a self, record: &'a SessionRecord) -> BoxFuture<'a, Result<(), AccountError>> {
713205a95af2atagen2026-09-26 09:24 UTC164
        Box::pin(self.save_record(record))
713205a95af2atagen2026-09-26 09:24 UTC165
    }
713205a95af2atagen2026-09-26 09:24 UTC166
f12377bce9b9atagen2026-09-26 12:32 UTC167
    #[cfg(feature = "sqlite")]
f12377bce9b9atagen2026-09-26 12:32 UTC168
    fn save_blocking(&self, record: &SessionRecord) -> Option<Result<(), AccountError>> {
f12377bce9b9atagen2026-09-26 12:32 UTC169
        self.save_now(record)
f12377bce9b9atagen2026-09-26 12:32 UTC170
    }
f12377bce9b9atagen2026-09-26 12:32 UTC171
6f1d6591c10datagen2026-09-26 09:52 UTC172
    fn soft_log_out(&self) -> BoxFuture<'_, Result<(), AccountError>> {
6f1d6591c10datagen2026-09-26 09:52 UTC173
        Box::pin(self.soft_log_out_record())
713205a95af2atagen2026-09-26 09:24 UTC174
    }
713205a95af2atagen2026-09-26 09:24 UTC175
713205a95af2atagen2026-09-26 09:24 UTC176
    fn discard(&self) -> BoxFuture<'_, Result<(), AccountError>> {
713205a95af2atagen2026-09-26 09:24 UTC177
        Box::pin(self.discard_all())
713205a95af2atagen2026-09-26 09:24 UTC178
    }
713205a95af2atagen2026-09-26 09:24 UTC179
}
713205a95af2atagen2026-09-26 09:24 UTC180
713205a95af2atagen2026-09-26 09:24 UTC181
impl Sealer {
713205a95af2atagen2026-09-26 09:24 UTC182
    fn new(passphrase: &str) -> Result<Self, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC183
        let cipher = StoreCipher::new()
713205a95af2atagen2026-09-26 09:24 UTC184
            .map_err(|error| account_error("creating the session record cipher", error))?;
713205a95af2atagen2026-09-26 09:24 UTC185
        let exported = cipher
713205a95af2atagen2026-09-26 09:24 UTC186
            .export(passphrase)
713205a95af2atagen2026-09-26 09:24 UTC187
            .map_err(|error| account_error("sealing the session record cipher", error))?;
713205a95af2atagen2026-09-26 09:24 UTC188
        Ok(Self { cipher, exported })
713205a95af2atagen2026-09-26 09:24 UTC189
    }
713205a95af2atagen2026-09-26 09:24 UTC190
713205a95af2atagen2026-09-26 09:24 UTC191
    fn seal(&self, record: &SessionRecord) -> Result<Vec<u8>, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC192
        let value = self
713205a95af2atagen2026-09-26 09:24 UTC193
            .cipher
713205a95af2atagen2026-09-26 09:24 UTC194
            .encrypt_value(record)
713205a95af2atagen2026-09-26 09:24 UTC195
            .map_err(|error| account_error("sealing the session record", error))?;
713205a95af2atagen2026-09-26 09:24 UTC196
        let length = u32::try_from(self.exported.len())
713205a95af2atagen2026-09-26 09:24 UTC197
            .expect("an exported store cipher is a few hundred bytes");
713205a95af2atagen2026-09-26 09:24 UTC198
        let mut sealed = Vec::with_capacity(4 + self.exported.len() + value.len());
713205a95af2atagen2026-09-26 09:24 UTC199
        sealed.extend_from_slice(&length.to_be_bytes());
713205a95af2atagen2026-09-26 09:24 UTC200
        sealed.extend_from_slice(&self.exported);
713205a95af2atagen2026-09-26 09:24 UTC201
        sealed.extend_from_slice(&value);
713205a95af2atagen2026-09-26 09:24 UTC202
        Ok(sealed)
713205a95af2atagen2026-09-26 09:24 UTC203
    }
713205a95af2atagen2026-09-26 09:24 UTC204
}
713205a95af2atagen2026-09-26 09:24 UTC205
713205a95af2atagen2026-09-26 09:24 UTC206
fn unseal(passphrase: &str, sealed: &[u8]) -> Result<(Sealer, SessionRecord), AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC207
    let truncated = || {
713205a95af2atagen2026-09-26 09:24 UTC208
        account_error(
713205a95af2atagen2026-09-26 09:24 UTC209
            "opening the session record",
713205a95af2atagen2026-09-26 09:24 UTC210
            std::io::Error::from(std::io::ErrorKind::UnexpectedEof),
713205a95af2atagen2026-09-26 09:24 UTC211
        )
713205a95af2atagen2026-09-26 09:24 UTC212
    };
713205a95af2atagen2026-09-26 09:24 UTC213
    let (length, rest) = sealed.split_first_chunk::<4>().ok_or_else(truncated)?;
713205a95af2atagen2026-09-26 09:24 UTC214
    let (exported, value) = rest
713205a95af2atagen2026-09-26 09:24 UTC215
        .split_at_checked(u32::from_be_bytes(*length) as usize)
713205a95af2atagen2026-09-26 09:24 UTC216
        .ok_or_else(truncated)?;
713205a95af2atagen2026-09-26 09:24 UTC217
    let cipher = StoreCipher::import(passphrase, exported)
713205a95af2atagen2026-09-26 09:24 UTC218
        .map_err(|error| account_error("opening the session record cipher", error))?;
713205a95af2atagen2026-09-26 09:24 UTC219
    let record = cipher
713205a95af2atagen2026-09-26 09:24 UTC220
        .decrypt_value(value)
713205a95af2atagen2026-09-26 09:24 UTC221
        .map_err(|error| account_error("opening the session record", error))?;
713205a95af2atagen2026-09-26 09:24 UTC222
    let sealer = Sealer {
713205a95af2atagen2026-09-26 09:24 UTC223
        cipher,
713205a95af2atagen2026-09-26 09:24 UTC224
        exported: exported.to_vec(),
713205a95af2atagen2026-09-26 09:24 UTC225
    };
713205a95af2atagen2026-09-26 09:24 UTC226
    Ok((sealer, record))
713205a95af2atagen2026-09-26 09:24 UTC227
}
713205a95af2atagen2026-09-26 09:24 UTC228
713205a95af2atagen2026-09-26 09:24 UTC229
#[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC230
mod backend {
713205a95af2atagen2026-09-26 09:24 UTC231
    use std::{
713205a95af2atagen2026-09-26 09:24 UTC232
        fs::{self, File, OpenOptions},
713205a95af2atagen2026-09-26 09:24 UTC233
        io::{self, ErrorKind, Write},
713205a95af2atagen2026-09-26 09:24 UTC234
        path::{Path, PathBuf},
713205a95af2atagen2026-09-26 09:24 UTC235
    };
713205a95af2atagen2026-09-26 09:24 UTC236
713205a95af2atagen2026-09-26 09:24 UTC237
    pub(super) async fn read(path: &Path) -> io::Result<Option<Vec<u8>>> {
713205a95af2atagen2026-09-26 09:24 UTC238
        match fs::read(path) {
713205a95af2atagen2026-09-26 09:24 UTC239
            Ok(sealed) => Ok(Some(sealed)),
713205a95af2atagen2026-09-26 09:24 UTC240
            Err(error) if error.kind() == ErrorKind::NotFound => Ok(None),
713205a95af2atagen2026-09-26 09:24 UTC241
            Err(error) => Err(error),
713205a95af2atagen2026-09-26 09:24 UTC242
        }
713205a95af2atagen2026-09-26 09:24 UTC243
    }
713205a95af2atagen2026-09-26 09:24 UTC244
713205a95af2atagen2026-09-26 09:24 UTC245
    pub(super) async fn write(path: &Path, sealed: &[u8]) -> io::Result<()> {
f12377bce9b9atagen2026-09-26 12:32 UTC246
        write_blocking(path, sealed)
f12377bce9b9atagen2026-09-26 12:32 UTC247
    }
f12377bce9b9atagen2026-09-26 12:32 UTC248
f12377bce9b9atagen2026-09-26 12:32 UTC249
    pub(super) fn write_blocking(path: &Path, sealed: &[u8]) -> io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC250
        let mut temporary = path.as_os_str().to_owned();
713205a95af2atagen2026-09-26 09:24 UTC251
        temporary.push(".tmp");
713205a95af2atagen2026-09-26 09:24 UTC252
        let temporary = PathBuf::from(temporary);
713205a95af2atagen2026-09-26 09:24 UTC253
        let parent = match path.parent() {
713205a95af2atagen2026-09-26 09:24 UTC254
            Some(parent) if !parent.as_os_str().is_empty() => parent,
713205a95af2atagen2026-09-26 09:24 UTC255
            _ => Path::new("."),
713205a95af2atagen2026-09-26 09:24 UTC256
        };
713205a95af2atagen2026-09-26 09:24 UTC257
        fs::create_dir_all(parent)?;
713205a95af2atagen2026-09-26 09:24 UTC258
        let written = replace(path, &temporary, parent, sealed);
713205a95af2atagen2026-09-26 09:24 UTC259
        if written.is_err() {
713205a95af2atagen2026-09-26 09:24 UTC260
            let _ = fs::remove_file(&temporary);
713205a95af2atagen2026-09-26 09:24 UTC261
        }
713205a95af2atagen2026-09-26 09:24 UTC262
        written
713205a95af2atagen2026-09-26 09:24 UTC263
    }
713205a95af2atagen2026-09-26 09:24 UTC264
713205a95af2atagen2026-09-26 09:24 UTC265
    fn replace(path: &Path, temporary: &Path, parent: &Path, sealed: &[u8]) -> io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC266
        let mut file = private_file(temporary)?;
713205a95af2atagen2026-09-26 09:24 UTC267
        file.write_all(sealed)?;
713205a95af2atagen2026-09-26 09:24 UTC268
        file.sync_all()?;
713205a95af2atagen2026-09-26 09:24 UTC269
        fs::rename(temporary, path)?;
713205a95af2atagen2026-09-26 09:24 UTC270
        sync_directory(parent)
713205a95af2atagen2026-09-26 09:24 UTC271
    }
713205a95af2atagen2026-09-26 09:24 UTC272
713205a95af2atagen2026-09-26 09:24 UTC273
    pub(super) async fn remove(path: &Path) -> io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC274
        match fs::remove_file(path) {
713205a95af2atagen2026-09-26 09:24 UTC275
            Err(error) if error.kind() != ErrorKind::NotFound => Err(error),
713205a95af2atagen2026-09-26 09:24 UTC276
            _ => Ok(()),
713205a95af2atagen2026-09-26 09:24 UTC277
        }
713205a95af2atagen2026-09-26 09:24 UTC278
    }
713205a95af2atagen2026-09-26 09:24 UTC279
713205a95af2atagen2026-09-26 09:24 UTC280
    #[cfg(unix)]
713205a95af2atagen2026-09-26 09:24 UTC281
    fn private_file(path: &Path) -> io::Result<File> {
713205a95af2atagen2026-09-26 09:24 UTC282
        use std::os::unix::fs::OpenOptionsExt;
713205a95af2atagen2026-09-26 09:24 UTC283
        OpenOptions::new()
713205a95af2atagen2026-09-26 09:24 UTC284
            .write(true)
713205a95af2atagen2026-09-26 09:24 UTC285
            .create(true)
713205a95af2atagen2026-09-26 09:24 UTC286
            .truncate(true)
713205a95af2atagen2026-09-26 09:24 UTC287
            .mode(0o600)
713205a95af2atagen2026-09-26 09:24 UTC288
            .open(path)
713205a95af2atagen2026-09-26 09:24 UTC289
    }
713205a95af2atagen2026-09-26 09:24 UTC290
713205a95af2atagen2026-09-26 09:24 UTC291
    #[cfg(not(unix))]
713205a95af2atagen2026-09-26 09:24 UTC292
    fn private_file(path: &Path) -> io::Result<File> {
713205a95af2atagen2026-09-26 09:24 UTC293
        OpenOptions::new()
713205a95af2atagen2026-09-26 09:24 UTC294
            .write(true)
713205a95af2atagen2026-09-26 09:24 UTC295
            .create(true)
713205a95af2atagen2026-09-26 09:24 UTC296
            .truncate(true)
713205a95af2atagen2026-09-26 09:24 UTC297
            .open(path)
713205a95af2atagen2026-09-26 09:24 UTC298
    }
713205a95af2atagen2026-09-26 09:24 UTC299
713205a95af2atagen2026-09-26 09:24 UTC300
    /// a rename isn't durable until its directory is synced
713205a95af2atagen2026-09-26 09:24 UTC301
    #[cfg(unix)]
713205a95af2atagen2026-09-26 09:24 UTC302
    fn sync_directory(path: &Path) -> io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC303
        File::open(path)?.sync_all()
713205a95af2atagen2026-09-26 09:24 UTC304
    }
713205a95af2atagen2026-09-26 09:24 UTC305
713205a95af2atagen2026-09-26 09:24 UTC306
    #[cfg(not(unix))]
713205a95af2atagen2026-09-26 09:24 UTC307
    fn sync_directory(_: &Path) -> io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC308
        Ok(())
713205a95af2atagen2026-09-26 09:24 UTC309
    }
713205a95af2atagen2026-09-26 09:24 UTC310
}
713205a95af2atagen2026-09-26 09:24 UTC311
713205a95af2atagen2026-09-26 09:24 UTC312
#[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC313
mod backend {
713205a95af2atagen2026-09-26 09:24 UTC314
    use indexed_db_futures::{
713205a95af2atagen2026-09-26 09:24 UTC315
        Build,
713205a95af2atagen2026-09-26 09:24 UTC316
        database::Database,
713205a95af2atagen2026-09-26 09:24 UTC317
        error::{Error, OpenDbError},
713205a95af2atagen2026-09-26 09:24 UTC318
        prelude::*,
713205a95af2atagen2026-09-26 09:24 UTC319
        transaction::{Transaction, TransactionMode},
713205a95af2atagen2026-09-26 09:24 UTC320
        typed_array::{Uint8Array, Uint8ArraySlice},
713205a95af2atagen2026-09-26 09:24 UTC321
    };
713205a95af2atagen2026-09-26 09:24 UTC322
713205a95af2atagen2026-09-26 09:24 UTC323
    const RECORDS: &str = "records";
713205a95af2atagen2026-09-26 09:24 UTC324
    const KEY: &str = "record";
713205a95af2atagen2026-09-26 09:24 UTC325
713205a95af2atagen2026-09-26 09:24 UTC326
    async fn open(name: &str) -> Result<Database, OpenDbError> {
713205a95af2atagen2026-09-26 09:24 UTC327
        Database::open(name)
713205a95af2atagen2026-09-26 09:24 UTC328
            .with_version(1u32)
713205a95af2atagen2026-09-26 09:24 UTC329
            .with_on_upgrade_needed(|_, transaction: &Transaction<'_>| -> Result<(), Error> {
713205a95af2atagen2026-09-26 09:24 UTC330
                transaction.db().create_object_store(RECORDS).build()?;
713205a95af2atagen2026-09-26 09:24 UTC331
                Ok(())
713205a95af2atagen2026-09-26 09:24 UTC332
            })
713205a95af2atagen2026-09-26 09:24 UTC333
            .build()?
713205a95af2atagen2026-09-26 09:24 UTC334
            .await
713205a95af2atagen2026-09-26 09:24 UTC335
    }
713205a95af2atagen2026-09-26 09:24 UTC336
713205a95af2atagen2026-09-26 09:24 UTC337
    pub(super) async fn read(name: &str) -> Result<Option<Vec<u8>>, OpenDbError> {
713205a95af2atagen2026-09-26 09:24 UTC338
        let database = open(name).await?;
713205a95af2atagen2026-09-26 09:24 UTC339
        let sealed: Option<Uint8Array> = database
713205a95af2atagen2026-09-26 09:24 UTC340
            .transaction(RECORDS)
713205a95af2atagen2026-09-26 09:24 UTC341
            .with_mode(TransactionMode::Readonly)
713205a95af2atagen2026-09-26 09:24 UTC342
            .build()?
713205a95af2atagen2026-09-26 09:24 UTC343
            .object_store(RECORDS)?
713205a95af2atagen2026-09-26 09:24 UTC344
            .get(KEY)
713205a95af2atagen2026-09-26 09:24 UTC345
            .await?;
713205a95af2atagen2026-09-26 09:24 UTC346
        Ok(sealed.map(Into::into))
713205a95af2atagen2026-09-26 09:24 UTC347
    }
713205a95af2atagen2026-09-26 09:24 UTC348
713205a95af2atagen2026-09-26 09:24 UTC349
    pub(super) async fn write(name: &str, sealed: &[u8]) -> Result<(), OpenDbError> {
713205a95af2atagen2026-09-26 09:24 UTC350
        let database = open(name).await?;
713205a95af2atagen2026-09-26 09:24 UTC351
        let transaction = database
713205a95af2atagen2026-09-26 09:24 UTC352
            .transaction(RECORDS)
713205a95af2atagen2026-09-26 09:24 UTC353
            .with_mode(TransactionMode::Readwrite)
713205a95af2atagen2026-09-26 09:24 UTC354
            .build()?;
713205a95af2atagen2026-09-26 09:24 UTC355
        transaction
713205a95af2atagen2026-09-26 09:24 UTC356
            .object_store(RECORDS)?
713205a95af2atagen2026-09-26 09:24 UTC357
            .put(Uint8ArraySlice::new(sealed))
713205a95af2atagen2026-09-26 09:24 UTC358
            .with_key(KEY)
713205a95af2atagen2026-09-26 09:24 UTC359
            .build()?;
713205a95af2atagen2026-09-26 09:24 UTC360
        Ok(transaction.commit().await?)
713205a95af2atagen2026-09-26 09:24 UTC361
    }
713205a95af2atagen2026-09-26 09:24 UTC362
713205a95af2atagen2026-09-26 09:24 UTC363
    pub(super) async fn remove(name: &str) -> Result<(), OpenDbError> {
713205a95af2atagen2026-09-26 09:24 UTC364
        Ok(Database::delete_by_name(name)?.await?)
713205a95af2atagen2026-09-26 09:24 UTC365
    }
713205a95af2atagen2026-09-26 09:24 UTC366
}
713205a95af2atagen2026-09-26 09:24 UTC367
713205a95af2atagen2026-09-26 09:24 UTC368
#[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC369
pub(crate) fn delete_sqlite_store(path: &Path) -> std::io::Result<()> {
713205a95af2atagen2026-09-26 09:24 UTC370
    use std::{fs, io::ErrorKind};
713205a95af2atagen2026-09-26 09:24 UTC371
713205a95af2atagen2026-09-26 09:24 UTC372
    // matrix-sdk-sqlite 0.18's database names, which it keeps private
713205a95af2atagen2026-09-26 09:24 UTC373
    const DATABASES: [&str; 4] = [
713205a95af2atagen2026-09-26 09:24 UTC374
        "matrix-sdk-state.sqlite3",
713205a95af2atagen2026-09-26 09:24 UTC375
        "matrix-sdk-crypto.sqlite3",
713205a95af2atagen2026-09-26 09:24 UTC376
        "matrix-sdk-event-cache.sqlite3",
713205a95af2atagen2026-09-26 09:24 UTC377
        "matrix-sdk-media.sqlite3",
713205a95af2atagen2026-09-26 09:24 UTC378
    ];
713205a95af2atagen2026-09-26 09:24 UTC379
    for database in DATABASES {
713205a95af2atagen2026-09-26 09:24 UTC380
        for suffix in ["", "-wal", "-shm", "-journal"] {
713205a95af2atagen2026-09-26 09:24 UTC381
            match fs::remove_file(path.join(format!("{database}{suffix}"))) {
713205a95af2atagen2026-09-26 09:24 UTC382
                Err(error) if error.kind() != ErrorKind::NotFound => return Err(error),
713205a95af2atagen2026-09-26 09:24 UTC383
                _ => {}
713205a95af2atagen2026-09-26 09:24 UTC384
            }
713205a95af2atagen2026-09-26 09:24 UTC385
        }
713205a95af2atagen2026-09-26 09:24 UTC386
    }
713205a95af2atagen2026-09-26 09:24 UTC387
    match fs::remove_dir(path) {
713205a95af2atagen2026-09-26 09:24 UTC388
        Err(error) if error.kind() != ErrorKind::NotFound => Err(error),
713205a95af2atagen2026-09-26 09:24 UTC389
        _ => Ok(()),
713205a95af2atagen2026-09-26 09:24 UTC390
    }
713205a95af2atagen2026-09-26 09:24 UTC391
}
713205a95af2atagen2026-09-26 09:24 UTC392
713205a95af2atagen2026-09-26 09:24 UTC393
#[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC394
pub(crate) async fn delete_indexeddb_store(
713205a95af2atagen2026-09-26 09:24 UTC395
    name: &str,
713205a95af2atagen2026-09-26 09:24 UTC396
) -> Result<(), indexed_db_futures::error::OpenDbError> {
713205a95af2atagen2026-09-26 09:24 UTC397
    use indexed_db_futures::database::Database;
713205a95af2atagen2026-09-26 09:24 UTC398
713205a95af2atagen2026-09-26 09:24 UTC399
    // the databases matrix-sdk-indexeddb 0.18 opens under one store name
713205a95af2atagen2026-09-26 09:24 UTC400
    for suffix in [
713205a95af2atagen2026-09-26 09:24 UTC401
        "",
713205a95af2atagen2026-09-26 09:24 UTC402
        "::matrix-sdk-state",
713205a95af2atagen2026-09-26 09:24 UTC403
        "::matrix-sdk-crypto",
713205a95af2atagen2026-09-26 09:24 UTC404
        "::matrix-sdk-crypto-meta",
713205a95af2atagen2026-09-26 09:24 UTC405
        "::event_cache",
713205a95af2atagen2026-09-26 09:24 UTC406
        "::media",
713205a95af2atagen2026-09-26 09:24 UTC407
    ] {
713205a95af2atagen2026-09-26 09:24 UTC408
        Database::delete_by_name(&format!("{name}{suffix}"))?.await?;
713205a95af2atagen2026-09-26 09:24 UTC409
    }
713205a95af2atagen2026-09-26 09:24 UTC410
    Ok(())
713205a95af2atagen2026-09-26 09:24 UTC411
}