use async_trait::async_trait;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use js_sys::Promise;
use wasm_bindgen_futures::JsFuture;
use web_sys::{IdbDatabase, IdbObjectStore, IdbOpenDbRequest, IdbRequest, IdbTransaction, IdbTransactionMode, IdbVersionChangeEvent};
use crate::{
crypto::primitives::{EncValue, Tag},
server::edb::{EncryptedStore, RawEdbEntry},
VaultError,
};
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
const DB_NAME: &str = "rose-squared-db";
const OBJECT_STORE_NAME: &str = "edb";
pub struct IndexedDbStore {
db: IdbDatabase,
}
impl IndexedDbStore {
pub async fn new() -> Result<Self, VaultError> {
log("Creating new IndexedDbStore");
let window = web_sys::window().ok_or(VaultError::StorageError("no window".to_string()))?;
let idb_factory = window.indexed_db()
.map_err(|e| VaultError::StorageError(format!("{:?}", e)))?
.ok_or(VaultError::StorageError("no indexeddb factory".to_string()))?;
let open_request: IdbOpenDbRequest = idb_factory.open_with_u32(DB_NAME, 1)
.map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
let onupgradeneeded = Closure::wrap(Box::new(move |event: &IdbVersionChangeEvent| {
log("onupgradeneeded");
if let Some(target) = event.target() {
if let Ok(open_request) = target.dyn_into::<IdbOpenDbRequest>() {
if let Ok(result) = open_request.result() {
let db: IdbDatabase = result.into();
if !db.object_store_names().contains(&OBJECT_STORE_NAME.to_string()) {
if let Err(e) = db.create_object_store(OBJECT_STORE_NAME) {
log(&format!("Failed to create object store: {:?}", e));
}
}
}
} }
}) as Box<dyn FnMut(&IdbVersionChangeEvent)>);
open_request.set_onupgradeneeded(Some(onupgradeneeded.as_ref().unchecked_ref()));
onupgradeneeded.forget();
let db = future_from_request(open_request.into()).await?;
Ok(Self { db: db.into() })
}
fn transaction(&self, mode: IdbTransactionMode) -> Result<IdbTransaction, VaultError> {
let store_names = js_sys::Array::new();
store_names.push(&JsValue::from_str(OBJECT_STORE_NAME));
self.db
.transaction_with_str_sequence_and_mode(&store_names, mode)
.map_err(|e| VaultError::StorageError(format!("{:?}", e)))
}
fn store(&self, tx: &IdbTransaction) -> Result<IdbObjectStore, VaultError> {
tx.object_store(OBJECT_STORE_NAME)
.map_err(|e| VaultError::StorageError(format!("{:?}", e)))
}
}
async fn future_from_request(request: IdbRequest) -> Result<JsValue, VaultError> {
let promise = Promise::new(&mut |resolve, reject| {
let onsuccess = Closure::wrap(Box::new(move |event: web_sys::Event| {
if let Some(target) = event.target() {
if let Ok(req) = target.dyn_into::<IdbRequest>() {
if let Ok(res) = req.result() {
let _ = resolve.call1(&JsValue::NULL, &res);
return;
}
}
}
let _ = resolve.call1(&JsValue::NULL, &JsValue::NULL);
}) as Box<dyn FnMut(web_sys::Event)>);
let onerror = Closure::wrap(Box::new(move |event: web_sys::Event| {
let _ = reject.call1(&JsValue::NULL, &event.into());
}) as Box<dyn FnMut(web_sys::Event)>);
request.set_onsuccess(Some(onsuccess.as_ref().unchecked_ref()));
request.set_onerror(Some(onerror.as_ref().unchecked_ref()));
onsuccess.forget();
onerror.forget();
});
JsFuture::from(promise).await
.map_err(|e| VaultError::StorageError(format!("{:?}", e)))
}
#[async_trait(?Send)]
impl EncryptedStore for IndexedDbStore {
async fn get(&self, tag: &Tag) -> Result<Option<EncValue>, VaultError> {
let tx = self.transaction(IdbTransactionMode::Readonly)?;
let store = self.store(&tx)?;
let key = serde_wasm_bindgen::to_value(&tag.0.to_vec()).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
let request = store.get(&key)
.map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
let result = future_from_request(request).await?;
if result.is_undefined() || result.is_null() {
Ok(None)
} else {
let value: Vec<u8> = serde_wasm_bindgen::from_value(result).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
Ok(Some(EncValue(value)))
}
}
async fn put(&self, tag: Tag, value: EncValue) -> Result<(), VaultError> {
let tx = self.transaction(IdbTransactionMode::Readwrite)?;
let store = self.store(&tx)?;
let key = serde_wasm_bindgen::to_value(&tag.0.to_vec()).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
let val = serde_wasm_bindgen::to_value(&value.0).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
store.put_with_key(&val, &key)
.map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
Ok(())
}
async fn delete(&self, tag: &Tag) -> Result<(), VaultError> {
let tx = self.transaction(IdbTransactionMode::Readwrite)?;
let store = self.store(&tx)?;
let key = serde_wasm_bindgen::to_value(&tag.0.to_vec()).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
store.delete(&key)
.map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
Ok(())
}
async fn atomic_update(
&self,
puts: Vec<RawEdbEntry>,
removes: Vec<Tag>,
) -> Result<(), VaultError> {
let tx = self.transaction(IdbTransactionMode::Readwrite)?;
let store = self.store(&tx)?;
for tag in removes {
let key = serde_wasm_bindgen::to_value(&tag.0.to_vec()).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
store.delete(&key)
.map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
}
for entry in puts {
let key = serde_wasm_bindgen::to_value(&entry.tag.0.to_vec()).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
let val = serde_wasm_bindgen::to_value(&entry.value.0).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
store.put_with_key(&val, &key)
.map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
}
Ok(())
}
}