ping_mls_store/async_blob.rs
1//! Async single-blob backend for the WASM persistent-provider path.
2//!
3//! [CR-4] The Sqlite variant uses synchronous local-file I/O; the IndexedDb
4//! variant CAN'T because IDB is async-only on the web. Rather than introduce
5//! a Rust-side IDB binding (extra wasm-bindgen surface + duplicate the host's
6//! AES-GCM wrapper), the WASM build asks the host to round-trip a single
7//! serialized snapshot blob through its existing encrypted-IDB storage layer
8//! (`packages/ui/src/storage-mls/PingStorage.web.ts`).
9//!
10//! Why a separate trait from `ping_core::Storage`: this crate must NOT depend
11//! on `ping-core` (the dep edge runs the other way). The host implements
12//! this trait by wrapping its own `Storage` — typically writing the
13//! snapshot under a reserved namespace + key (e.g. `("__mls", "snapshot")`)
14//! so it sits alongside the metadata entries the host already manages.
15
16use std::future::Future;
17use std::pin::Pin;
18
19/// Future returned by [`AsyncBlobStore`] methods. Mirrors the
20/// `Send`-bound pattern from `ping_core::storage::StorageFuture`: on native
21/// targets the future must cross tokio threads, on WASM the runtime is
22/// single-threaded and `JsFuture` is `!Send`.
23#[cfg(not(target_arch = "wasm32"))]
24pub type BlobFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
25#[cfg(target_arch = "wasm32")]
26pub type BlobFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
27
28/// Async single-blob storage. Reads + writes the entire MLS snapshot as one
29/// opaque byte slab; the provider takes care of (de)serialising the
30/// `MemoryStorage` HashMap inside that slab.
31///
32/// Native targets get `Send + Sync` so the provider can be shared across
33/// tokio threads; WASM drops the bound to match `wasm-bindgen` + `JsFuture`
34/// being `!Send`.
35///
36/// `Debug` is required so [`crate::StorageBackend`] can still derive `Debug`
37/// without resorting to a manual impl on the enum.
38#[cfg(not(target_arch = "wasm32"))]
39pub trait AsyncBlobStore: std::fmt::Debug + Send + Sync {
40 /// Return the previously-written snapshot, or `Ok(None)` when the store
41 /// is empty. Errors propagate as `String` so we don't have to pull the
42 /// host's error type into this crate.
43 fn read_blob(&self) -> BlobFuture<'_, std::result::Result<Option<Vec<u8>>, String>>;
44 /// Overwrite the snapshot. Implementations MUST be atomic (a partial
45 /// write would leave the provider unable to load on next cold start).
46 fn write_blob(&self, bytes: Vec<u8>) -> BlobFuture<'_, std::result::Result<(), String>>;
47}
48
49#[cfg(target_arch = "wasm32")]
50pub trait AsyncBlobStore: std::fmt::Debug {
51 fn read_blob(&self) -> BlobFuture<'_, std::result::Result<Option<Vec<u8>>, String>>;
52 fn write_blob(&self, bytes: Vec<u8>) -> BlobFuture<'_, std::result::Result<(), String>>;
53}