atagen

macltok

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