atagen

macltok

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