a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 1 | use crate::{AccountError, account::account_error}; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 2 | |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 3 | /// the platform's own exclusive lock on a store. matrix-sdk 0.18's cross process lease drops |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 4 | /// itself silently after one failed renewal or a stall past 500ms, so it can't be what keeps a |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 5 | /// second engine out |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 6 | pub(crate) struct StoreLock { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 7 | #[cfg(feature = "sqlite")] |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 8 | _file: std::fs::File, |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 9 | #[cfg(feature = "indexeddb")] |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 10 | release: js_sys::Function, |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 11 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 12 | |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 13 | #[cfg(feature = "sqlite")] |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 14 | pub(crate) const LOCK_FILE: &str = "macltok.lock"; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 15 | |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 16 | #[cfg(feature = "sqlite")] |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 17 | impl StoreLock { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 18 | pub(crate) fn file(directory: &std::path::Path) -> Result<Self, AccountError> { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 19 | use std::fs::{self, File, TryLockError}; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 20 | |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 21 | fs::create_dir_all(directory) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 22 | .map_err(|error| account_error("creating the Matrix store directory", error))?; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 23 | let file = File::options() |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 24 | .create(true) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 25 | .truncate(false) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 26 | .write(true) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 27 | .open(directory.join(LOCK_FILE)) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 28 | .map_err(|error| account_error("opening the Matrix store's lock file", error))?; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 29 | match file.try_lock() { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 30 | Ok(()) => Ok(Self { _file: file }), |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 31 | Err(TryLockError::WouldBlock) => Err(AccountError::StoreInUse), |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 32 | Err(TryLockError::Error(error)) => { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 33 | Err(account_error("locking the Matrix store", error)) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 34 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 35 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 36 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 37 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 38 | |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 39 | #[cfg(feature = "indexeddb")] |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 40 | #[derive(Debug, thiserror::Error)] |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 41 | enum WebLockError { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 42 | #[error("this global scope has no Web Locks, they need a secure context")] |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 43 | Unavailable, |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 44 | #[error("requesting the Web Lock failed, {0}")] |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 45 | Failed(String), |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 46 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 47 | |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 48 | #[cfg(feature = "indexeddb")] |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 49 | impl StoreLock { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 50 | /// `ifAvailable` answers at once. the lock is ours until the promise we hand back settles |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 51 | pub(crate) async fn web(name: &str) -> Result<Self, AccountError> { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 52 | use futures_util::future::{Either, select}; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 53 | use js_sys::{Function, Object, Promise, Reflect}; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 54 | use tokio::sync::oneshot; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 55 | use wasm_bindgen::{JsCast, JsValue, closure::Closure}; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 56 | |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 57 | let failed = |error: JsValue| { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 58 | account_error( |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 59 | "locking the Matrix store", |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 60 | WebLockError::Failed(format!("{error:?}")), |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 61 | ) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 62 | }; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 63 | let locks = Reflect::get(&js_sys::global(), &JsValue::from_str("navigator")) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 64 | .and_then(|navigator| Reflect::get(&navigator, &JsValue::from_str("locks"))) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 65 | .map_err(failed)?; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 66 | if locks.is_undefined() { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 67 | return Err(account_error( |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 68 | "locking the Matrix store", |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 69 | WebLockError::Unavailable, |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 70 | )); |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 71 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 72 | let request: Function = Reflect::get(&locks, &JsValue::from_str("request")) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 73 | .map_err(failed)? |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 74 | .dyn_into() |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 75 | .map_err(failed)?; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 76 | let options = Object::new(); |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 77 | Reflect::set(&options, &JsValue::from_str("ifAvailable"), &JsValue::TRUE) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 78 | .map_err(failed)?; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 79 | |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 80 | let (granted, grant) = oneshot::channel(); |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 81 | let callback = Closure::once(move |lock: JsValue| -> JsValue { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 82 | if lock.is_null() { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 83 | let _ = granted.send(None); |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 84 | return JsValue::UNDEFINED; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 85 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 86 | let mut release = None; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 87 | let held = Promise::new(&mut |resolve, _| release = Some(resolve)); |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 88 | let _ = granted.send(release); |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 89 | held.into() |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 90 | }); |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 91 | let outcome: Promise = request |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 92 | .call3( |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 93 | &locks, |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 94 | &JsValue::from_str(&format!("macltok:{name}")), |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 95 | &options, |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 96 | callback.as_ref(), |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 97 | ) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 98 | .map_err(failed)? |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 99 | .dyn_into() |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 100 | .map_err(failed)?; |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 101 | match select(grant, wasm_bindgen_futures::JsFuture::from(outcome)).await { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 102 | Either::Left((Ok(Some(release)), _)) => Ok(Self { release }), |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 103 | Either::Left((Ok(None), _)) => Err(AccountError::StoreInUse), |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 104 | Either::Left((Err(_), _)) | Either::Right((Ok(_), _)) => { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 105 | Err(failed(JsValue::from_str("the lock callback never ran"))) |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 106 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 107 | Either::Right((Err(error), _)) => Err(failed(error)), |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 108 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 109 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 110 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 111 | |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 112 | #[cfg(feature = "indexeddb")] |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 113 | impl Drop for StoreLock { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 114 | fn drop(&mut self) { |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 115 | let _ = self.release.call0(&wasm_bindgen::JsValue::UNDEFINED); |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 116 | } |
a250c7fa43fb | atagen | 2026-09-26 13:35 UTC | 117 | } |