Move indexeddb out into a separate crate

This commit is contained in:
Benjamin Kampmann
2022-02-14 17:00:55 +01:00
parent 95e06ec732
commit e928d02c6f
8 changed files with 158 additions and 139 deletions

View File

@@ -1,11 +1,4 @@
[workspace]
members = [
"crates/matrix-qrcode",
"crates/matrix-sdk",
"crates/matrix-sdk-appservice",
"crates/matrix-sdk-base",
"crates/matrix-sdk-common",
"crates/matrix-sdk-crypto",
"crates/matrix-sdk-test",
"crates/matrix-sdk-test-macros",
"crates/*",
]

View File

@@ -22,16 +22,21 @@ qrcode = ["matrix-sdk-crypto/qrcode"]
sled_state_store = [
"sled",
"tokio",
"state_key",
]
sled_cryptostore = ["matrix-sdk-crypto/sled_cryptostore"]
indexeddb_state_store = ["state_key"]
indexeddb_cryptostore = ["matrix-sdk-crypto/indexeddb_cryptostore"]
# State Key is a helper feature for state store implementations
state_key = [
"pbkdf2",
"hmac",
"sha2",
"rand",
"chacha20poly1305",
]
sled_cryptostore = ["matrix-sdk-crypto/sled_cryptostore"]
indexeddb_state_store = ["indexed_db_futures", "wasm-bindgen", "pbkdf2", "hmac", "sha2", "rand", "chacha20poly1305"]
indexeddb_cryptostore = ["matrix-sdk-crypto/indexeddb_cryptostore"]
[dependencies]
chacha20poly1305 = { version = "0.9.0", optional = true }
@@ -51,14 +56,11 @@ sled = { version = "0.34.6", optional = true }
thiserror = "1.0.25"
tracing = "0.1.26"
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
anyhow = "1"
## Feature sled_state_store
tokio = { version = "1.7.1", optional = true, default-features = false, features = ["sync", "fs"] }
## Feature indexeddb-state-store
indexed_db_futures = { version = "0.2.0", optional = true }
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"], optional = true }
[dependencies.ruma]
git = "https://github.com/ruma/ruma/"
rev = "37095f88553b311e7a70adaaabe39976fb8ff71c"

View File

@@ -15,9 +15,9 @@
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(missing_docs)]
#![deny(
missing_debug_implementations,
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
@@ -46,7 +46,7 @@ mod error;
pub mod media;
mod rooms;
mod session;
mod store;
pub mod store;
pub use client::{BaseClient, BaseClientConfig};
#[cfg(feature = "encryption")]

View File

@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/// Implementing the state store
#[cfg(feature = "sled_state_store")]
use std::path::Path;
use std::{
@@ -20,7 +22,7 @@ use std::{
sync::Arc,
};
#[cfg(test)]
#[cfg(any(test, feature = "testing"))]
#[macro_use]
pub mod integration_tests;
@@ -40,15 +42,11 @@ use ruma::{
EventId, MxcUri, RoomId, UserId,
};
#[cfg(any(feature = "sled_state_store", feature = "indexeddb_state_store"))]
mod store_key;
pub mod store_key;
#[cfg(feature = "sled_state_store")]
use sled::Db;
#[cfg(feature = "indexeddb_state_store")]
mod indexeddb_store;
use crate::{
deserialized_responses::{MemberEvent, StrippedMemberEvent},
media::MediaRequest,
@@ -61,8 +59,6 @@ mod memory_store;
#[cfg(feature = "sled_state_store")]
mod sled_store;
#[cfg(feature = "indexeddb_state_store")]
use self::indexeddb_store::IndexeddbStore;
#[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_state_store")))]
use self::memory_store::MemoryStore;
#[cfg(feature = "sled_state_store")]
@@ -71,21 +67,9 @@ use self::sled_store::SledStore;
/// State store specific error type.
#[derive(Debug, thiserror::Error)]
pub enum StoreError {
/// An error happened in the underlying sled database.
#[cfg(feature = "sled_state_store")]
#[error(transparent)]
Sled(#[from] sled::Error),
/// An error happened in the underlying Indexed Database.
#[cfg(feature = "indexeddb_state_store")]
#[error("IndexDB error: {name} ({code}): {message}")]
Indexeddb {
/// DomException code
code: u16,
/// Specific name of the DomException
name: String,
/// Message given to the DomException
message: String,
},
/// An error happened in the underlying sled database.
Backend(#[from] anyhow::Error),
/// An error happened while serializing or deserializing some data.
#[error(transparent)]
Json(#[from] serde_json::Error),
@@ -111,14 +95,6 @@ pub enum StoreError {
#[error(transparent)]
Task(#[from] tokio::task::JoinError),
}
#[cfg(feature = "indexeddb_state_store")]
impl From<indexed_db_futures::web_sys::DomException> for StoreError {
fn from(frm: indexed_db_futures::web_sys::DomException) -> StoreError {
StoreError::Indexeddb { name: frm.name(), message: frm.message(), code: frm.code() }
}
}
/// A `StateStore` specific result type.
pub type Result<T, E = StoreError> = std::result::Result<T, E>;

View File

@@ -10,6 +10,7 @@
pub use async_trait::async_trait;
pub use instant;
pub use ruma;
pub mod deserialized_responses;
pub mod executor;

View File

@@ -0,0 +1,17 @@
[package]
name = "matrix-sdk-indexeddb"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
matrix-sdk-base = { path = "../matrix-sdk-base", features = ["state_key"] }
matrix-sdk-common = { path = "../matrix-sdk-common" }
thiserror = "1.0.25"
anyhow = "1"
indexed_db_futures = { version = "0.2.0" }
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] }
serde = { version = "1.0.126" }
serde_json = "1.0.64"

View File

@@ -0,0 +1,5 @@
#[cfg(not(target_arch="wasm32"))]
compile_error!("indexeddb is only support on wasm32. You may want to add --target wasm32-unknown-unknown");
pub mod state_store;

View File

@@ -15,27 +15,32 @@
use std::collections::BTreeSet;
use indexed_db_futures::prelude::*;
use matrix_sdk_common::{async_trait, SafeEncode};
use ruma::{
events::{
presence::PresenceEvent,
receipt::Receipt,
room::member::{MembershipState, RoomMemberEventContent},
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent, EventType,
},
receipt::ReceiptType,
serde::Raw,
EventId, MxcUri, RoomId, UserId,
use matrix_sdk_common::{async_trait, SafeEncode,
ruma::{
events::{
presence::PresenceEvent,
receipt::Receipt,
room::member::{MembershipState, RoomMemberEventContent},
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent, EventType,
},
receipt::ReceiptType,
serde::Raw,
EventId, MxcUri, RoomId, UserId,
}
};
use serde::{Deserialize, Serialize};
use wasm_bindgen::JsValue;
use self::store_key::{EncryptedEvent, StoreKey};
use super::{store_key, Result, RoomInfo, StateChanges, StateStore, StoreError};
use crate::{
use matrix_sdk_base::{
store::{
store_key::{self, EncryptedEvent, StoreKey},
Result as StoreResult, StateChanges, StateStore, StoreError
},
RoomInfo,
deserialized_responses::MemberEvent,
media::{MediaRequest, UniqueKey},
};
use anyhow::anyhow;
#[derive(Debug, Serialize, Deserialize)]
pub enum DatabaseType {
@@ -49,11 +54,41 @@ pub enum SerializationError {
Json(#[from] serde_json::Error),
#[error(transparent)]
Encryption(#[from] store_key::Error),
#[error("DomException {name} ({code}): {message}")]
DomException {
name: String,
message: String,
code: u16,
},
#[error(transparent)]
StoreError(#[from] StoreError),
}
impl From<indexed_db_futures::web_sys::DomException> for SerializationError {
fn from(frm: indexed_db_futures::web_sys::DomException) -> SerializationError {
SerializationError::DomException { name: frm.name(), message: frm.message(), code: frm.code() }
}
}
impl From<SerializationError> for StoreError {
fn from(e: SerializationError) -> Self {
match e {
SerializationError::Json(e) => StoreError::Json(e),
SerializationError::StoreError(e) => e,
SerializationError::Encryption(e) => match e {
store_key::Error::Random(e) => StoreError::Encryption(e.to_string()),
store_key::Error::Serialization(e) => StoreError::Json(e),
store_key::Error::Encryption(e) => StoreError::Encryption(e),
},
_ => {
StoreError::Backend(anyhow!(e))
}
}
}
}
#[allow(non_snake_case)]
mod KEYS {
// STORES
pub const SESSION: &'static str = "session";
@@ -88,18 +123,6 @@ mod KEYS {
pub const SYNC_TOKEN: &'static str = "sync_token";
}
impl From<SerializationError> for StoreError {
fn from(e: SerializationError) -> Self {
match e {
SerializationError::Json(e) => StoreError::Json(e),
SerializationError::Encryption(e) => match e {
store_key::Error::Random(e) => StoreError::Encryption(e.to_string()),
store_key::Error::Serialization(e) => StoreError::Json(e),
store_key::Error::Encryption(e) => StoreError::Encryption(e),
},
}
}
}
pub struct IndexeddbStore {
name: String,
@@ -113,6 +136,8 @@ impl std::fmt::Debug for IndexeddbStore {
}
}
type Result<A, E = SerializationError> = std::result::Result<A, E>;
impl IndexeddbStore {
async fn open_helper(name: String, store_key: Option<StoreKey>) -> Result<Self> {
// Open my_db v1
@@ -190,12 +215,12 @@ impl IndexeddbStore {
if let DatabaseType::Encrypted(k) = key {
StoreKey::import(passphrase, k).map_err(|_| StoreError::StoreLocked)?
} else {
return Err(StoreError::UnencryptedStore);
return Err(StoreError::UnencryptedStore.into());
}
} else {
let key = StoreKey::new().map_err::<StoreError, _>(|e| e.into())?;
let key = StoreKey::new().map_err::<SerializationError, _>(|e| e.into())?;
let encrypted_key = DatabaseType::Encrypted(
key.export(passphrase).map_err::<StoreError, _>(|e| e.into())?,
key.export(passphrase).map_err::<SerializationError, _>(|e| e.into())?,
);
ob.put_key_val(
&JsValue::from_str(KEYS::STORE_KEY),
@@ -213,7 +238,7 @@ impl IndexeddbStore {
IndexeddbStore::open_helper(name, None).await
}
fn serialize_event(&self, event: &impl Serialize) -> Result<JsValue, SerializationError> {
fn serialize_event(&self, event: &impl Serialize) -> std::result::Result<JsValue, SerializationError> {
Ok(match self.store_key {
Some(ref key) => JsValue::from_serde(&key.encrypt(event)?)?,
None => JsValue::from_serde(event)?,
@@ -223,7 +248,7 @@ impl IndexeddbStore {
fn deserialize_event<T: for<'b> Deserialize<'b>>(
&self,
event: JsValue,
) -> Result<T, SerializationError> {
) -> std::result::Result<T, SerializationError> {
match self.store_key {
Some(ref key) => {
let encrypted: EncryptedEvent = event.into_serde()?;
@@ -468,7 +493,7 @@ impl IndexeddbStore {
}
}
tx.await.into_result().map_err::<StoreError, _>(|e| e.into())
tx.await.into_result().map_err::<SerializationError, _>(|e| e.into())
}
pub async fn get_presence_event(&self, user_id: &UserId) -> Result<Option<Raw<PresenceEvent>>> {
@@ -630,7 +655,7 @@ impl IndexeddbStore {
.await?
.map(|f| {
self.deserialize_event::<BTreeSet<Box<UserId>>>(f)
.map_err::<StoreError, _>(|e| e.into())
.map_err::<SerializationError, _>(|e| e.into())
})
.unwrap_or_else(|| Ok(Default::default()))
}
@@ -645,7 +670,7 @@ impl IndexeddbStore {
.object_store(KEYS::ACCOUNT_DATA)?
.get(&JsValue::from_str(event_type.as_str()))?
.await?
.map(|f| self.deserialize_event(f).map_err::<StoreError, _>(|e| e.into()))
.map(|f| self.deserialize_event(f).map_err::<SerializationError, _>(|e| e.into()))
.transpose()?)
}
@@ -660,7 +685,7 @@ impl IndexeddbStore {
.object_store(KEYS::ROOM_ACCOUNT_DATA)?
.get(&(room_id.as_str(), event_type.as_str()).encode())?
.await?
.map(|f| self.deserialize_event(f).map_err::<StoreError, _>(|e| e.into()))
.map(|f| self.deserialize_event(f).map_err::<SerializationError, _>(|e| e.into()))
.transpose()?)
}
@@ -704,7 +729,7 @@ impl IndexeddbStore {
Box::<UserId>::try_from(&k_str[prefix_len..])
.map_err(|e| StoreError::Codec(format!("{:?}", e)))?
} else {
return Err(StoreError::Codec(format!("{:?}", k)));
return Err(StoreError::Codec(format!("{:?}", k)).into());
};
let r = self
.deserialize_event::<Receipt>(res)
@@ -766,7 +791,7 @@ impl IndexeddbStore {
tx.object_store(KEYS::CUSTOM)?.put_key_val(&jskey, &self.serialize_event(&value)?)?;
tx.await.into_result().map_err::<StoreError, _>(|e| e.into())?;
tx.await.into_result().map_err::<SerializationError, _>(|e| e.into())?;
Ok(prev)
}
@@ -833,30 +858,30 @@ impl IndexeddbStore {
store.delete(&key)?;
}
}
tx.await.into_result().map_err::<StoreError, _>(|e| e.into())
tx.await.into_result().map_err::<SerializationError, _>(|e| e.into())
}
}
#[async_trait(?Send)]
impl StateStore for IndexeddbStore {
async fn save_filter(&self, filter_name: &str, filter_id: &str) -> Result<()> {
self.save_filter(filter_name, filter_id).await
async fn save_filter(&self, filter_name: &str, filter_id: &str) -> StoreResult<()> {
self.save_filter(filter_name, filter_id).await.map_err(|e| e.into())
}
async fn save_changes(&self, changes: &StateChanges) -> Result<()> {
self.save_changes(changes).await
async fn save_changes(&self, changes: &StateChanges) -> StoreResult<()> {
self.save_changes(changes).await.map_err(|e| e.into())
}
async fn get_filter(&self, filter_id: &str) -> Result<Option<String>> {
self.get_filter(filter_id).await
async fn get_filter(&self, filter_id: &str) -> StoreResult<Option<String>> {
self.get_filter(filter_id).await.map_err(|e| e.into())
}
async fn get_sync_token(&self) -> Result<Option<String>> {
self.get_sync_token().await
async fn get_sync_token(&self) -> StoreResult<Option<String>> {
self.get_sync_token().await.map_err(|e| e.into())
}
async fn get_presence_event(&self, user_id: &UserId) -> Result<Option<Raw<PresenceEvent>>> {
self.get_presence_event(user_id).await
async fn get_presence_event(&self, user_id: &UserId) -> StoreResult<Option<Raw<PresenceEvent>>> {
self.get_presence_event(user_id).await.map_err(|e| e.into())
}
async fn get_state_event(
@@ -864,75 +889,75 @@ impl StateStore for IndexeddbStore {
room_id: &RoomId,
event_type: EventType,
state_key: &str,
) -> Result<Option<Raw<AnySyncStateEvent>>> {
self.get_state_event(room_id, event_type, state_key).await
) -> StoreResult<Option<Raw<AnySyncStateEvent>>> {
self.get_state_event(room_id, event_type, state_key).await.map_err(|e| e.into())
}
async fn get_state_events(
&self,
room_id: &RoomId,
event_type: EventType,
) -> Result<Vec<Raw<AnySyncStateEvent>>> {
self.get_state_events(room_id, event_type).await
) -> StoreResult<Vec<Raw<AnySyncStateEvent>>> {
self.get_state_events(room_id, event_type).await.map_err(|e| e.into())
}
async fn get_profile(
&self,
room_id: &RoomId,
user_id: &UserId,
) -> Result<Option<RoomMemberEventContent>> {
self.get_profile(room_id, user_id).await
) -> StoreResult<Option<RoomMemberEventContent>> {
self.get_profile(room_id, user_id).await.map_err(|e| e.into())
}
async fn get_member_event(
&self,
room_id: &RoomId,
state_key: &UserId,
) -> Result<Option<MemberEvent>> {
self.get_member_event(room_id, state_key).await
) -> StoreResult<Option<MemberEvent>> {
self.get_member_event(room_id, state_key).await.map_err(|e| e.into())
}
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<Box<UserId>>> {
self.get_user_ids_stream(room_id).await
async fn get_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<Box<UserId>>> {
self.get_user_ids_stream(room_id).await.map_err(|e| e.into())
}
async fn get_invited_user_ids(&self, room_id: &RoomId) -> Result<Vec<Box<UserId>>> {
self.get_invited_user_ids(room_id).await
async fn get_invited_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<Box<UserId>>> {
self.get_invited_user_ids(room_id).await.map_err(|e| e.into())
}
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<Box<UserId>>> {
self.get_joined_user_ids(room_id).await
async fn get_joined_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<Box<UserId>>> {
self.get_joined_user_ids(room_id).await.map_err(|e| e.into())
}
async fn get_room_infos(&self) -> Result<Vec<RoomInfo>> {
self.get_room_infos().await
async fn get_room_infos(&self) -> StoreResult<Vec<RoomInfo>> {
self.get_room_infos().await.map_err(|e| e.into())
}
async fn get_stripped_room_infos(&self) -> Result<Vec<RoomInfo>> {
self.get_stripped_room_infos().await
async fn get_stripped_room_infos(&self) -> StoreResult<Vec<RoomInfo>> {
self.get_stripped_room_infos().await.map_err(|e| e.into())
}
async fn get_users_with_display_name(
&self,
room_id: &RoomId,
display_name: &str,
) -> Result<BTreeSet<Box<UserId>>> {
self.get_users_with_display_name(room_id, display_name).await
) -> StoreResult<BTreeSet<Box<UserId>>> {
self.get_users_with_display_name(room_id, display_name).await.map_err(|e| e.into())
}
async fn get_account_data_event(
&self,
event_type: EventType,
) -> Result<Option<Raw<AnyGlobalAccountDataEvent>>> {
self.get_account_data_event(event_type).await
) -> StoreResult<Option<Raw<AnyGlobalAccountDataEvent>>> {
self.get_account_data_event(event_type).await.map_err(|e| e.into())
}
async fn get_room_account_data_event(
&self,
room_id: &RoomId,
event_type: EventType,
) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
self.get_room_account_data_event(room_id, event_type).await
) -> StoreResult<Option<Raw<AnyRoomAccountDataEvent>>> {
self.get_room_account_data_event(room_id, event_type).await.map_err(|e| e.into())
}
async fn get_user_room_receipt_event(
@@ -940,8 +965,8 @@ impl StateStore for IndexeddbStore {
room_id: &RoomId,
receipt_type: ReceiptType,
user_id: &UserId,
) -> Result<Option<(Box<EventId>, Receipt)>> {
self.get_user_room_receipt_event(room_id, receipt_type, user_id).await
) -> StoreResult<Option<(Box<EventId>, Receipt)>> {
self.get_user_room_receipt_event(room_id, receipt_type, user_id).await.map_err(|e| e.into())
}
async fn get_event_room_receipt_events(
@@ -949,36 +974,36 @@ impl StateStore for IndexeddbStore {
room_id: &RoomId,
receipt_type: ReceiptType,
event_id: &EventId,
) -> Result<Vec<(Box<UserId>, Receipt)>> {
self.get_event_room_receipt_events(room_id, receipt_type, event_id).await
) -> StoreResult<Vec<(Box<UserId>, Receipt)>> {
self.get_event_room_receipt_events(room_id, receipt_type, event_id).await.map_err(|e| e.into())
}
async fn get_custom_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
self.get_custom_value(key).await
async fn get_custom_value(&self, key: &[u8]) -> StoreResult<Option<Vec<u8>>> {
self.get_custom_value(key).await.map_err(|e| e.into())
}
async fn set_custom_value(&self, key: &[u8], value: Vec<u8>) -> Result<Option<Vec<u8>>> {
self.set_custom_value(key, value).await
async fn set_custom_value(&self, key: &[u8], value: Vec<u8>) -> StoreResult<Option<Vec<u8>>> {
self.set_custom_value(key, value).await.map_err(|e| e.into())
}
async fn add_media_content(&self, request: &MediaRequest, data: Vec<u8>) -> Result<()> {
self.add_media_content(request, data).await
async fn add_media_content(&self, request: &MediaRequest, data: Vec<u8>) -> StoreResult<()> {
self.add_media_content(request, data).await.map_err(|e| e.into())
}
async fn get_media_content(&self, request: &MediaRequest) -> Result<Option<Vec<u8>>> {
self.get_media_content(request).await
async fn get_media_content(&self, request: &MediaRequest) -> StoreResult<Option<Vec<u8>>> {
self.get_media_content(request).await.map_err(|e| e.into())
}
async fn remove_media_content(&self, request: &MediaRequest) -> Result<()> {
self.remove_media_content(request).await
async fn remove_media_content(&self, request: &MediaRequest) -> StoreResult<()> {
self.remove_media_content(request).await.map_err(|e| e.into())
}
async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<()> {
self.remove_media_content_for_uri(uri).await
async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> StoreResult<()> {
self.remove_media_content_for_uri(uri).await.map_err(|e| e.into())
}
async fn remove_room(&self, room_id: &RoomId) -> Result<()> {
self.remove_room(room_id).await
async fn remove_room(&self, room_id: &RoomId) -> StoreResult<()> {
self.remove_room(room_id).await.map_err(|e| e.into())
}
}