use crate::error::WasmCoreError;
use crate::storage::wasm_client_traits::{v1, v2, WasmClientStorage};
use async_trait::async_trait;
use js_sys::Promise;
use nym_wasm_storage::traits::BaseWasmStorage;
use nym_wasm_storage::{
Build, Database, RawDbResult, TryFromJs, TryToJs, VersionChangeEvent, WasmStorage,
};
use nym_wasm_utils::error::{simple_js_error, PromisableResult};
use serde::de::DeserializeOwned;
use serde::Serialize;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use zeroize::Zeroizing;
pub mod core_client_traits;
mod types;
pub mod wasm_client_traits;
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
const STORAGE_VERSION: u32 = 2;
#[wasm_bindgen]
pub struct ClientStorage {
#[allow(dead_code)]
pub(crate) name: String,
pub(crate) inner: WasmStorage,
}
#[wasm_bindgen]
impl ClientStorage {
fn db_name(client_id: &str) -> String {
format!("{STORAGE_NAME_PREFIX}-{client_id}")
}
pub async fn new_async(
client_id: &str,
passphrase: Option<String>,
) -> Result<ClientStorage, WasmCoreError> {
let name = Self::db_name(client_id);
let passphrase = Zeroizing::new(passphrase);
let migrate_fn = Some(|evt: VersionChangeEvent, db: Database| -> RawDbResult<()> {
let old_version = evt.old_version() as u32;
if old_version < 1 {
db.create_object_store(v1::KEYS_STORE).build()?;
db.create_object_store(v1::CORE_STORE).build()?;
db.create_object_store(v2::GATEWAY_REGISTRATIONS_ACTIVE_GATEWAY_STORE)
.build()?;
db.create_object_store(v2::GATEWAY_REGISTRATIONS_REGISTERED_GATEWAYS_STORE)
.build()?;
return Ok(());
}
if old_version < 2 {
return Err(simple_js_error("this client is incompatible with existing storage. please initialise it again.").into());
}
Ok(())
});
let inner = WasmStorage::new(
&name,
STORAGE_VERSION,
migrate_fn,
passphrase.as_ref().map(|p| p.as_bytes()),
)
.await?;
Ok(ClientStorage { inner, name })
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(client_id: String, passphrase: String) -> Promise {
future_to_promise(async move {
Self::new_async(&client_id, Some(passphrase))
.await
.into_promise_result()
})
}
pub fn new_unencrypted(client_id: String) -> Promise {
future_to_promise(async move {
Self::new_async(&client_id, None)
.await
.into_promise_result()
})
}
}
#[async_trait(?Send)]
impl BaseWasmStorage for ClientStorage {
type StorageError = WasmCoreError;
async fn exists(db_name: &str) -> Result<bool, Self::StorageError> {
Ok(WasmStorage::exists(db_name).await?)
}
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
where
T: DeserializeOwned,
K: TryToJs,
{
Ok(self.inner.read_value(store, key).await?)
}
async fn store_value<T, K>(
&self,
store: &str,
key: K,
value: &T,
) -> Result<(), Self::StorageError>
where
T: Serialize,
K: TryToJs + TryFromJs,
{
Ok(self.inner.store_value(store, key, value).await?)
}
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
where
K: TryToJs,
{
Ok(self.inner.remove_value(store, key).await?)
}
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
where
K: TryToJs,
{
Ok(self.inner.has_value(store, key).await?)
}
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
where
K: TryToJs,
{
Ok(self.inner.key_count(store, key).await?)
}
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError> {
Ok(self.inner.get_all_keys(store).await?)
}
}
#[async_trait(?Send)]
impl WasmClientStorage for ClientStorage {
type StorageError = WasmCoreError;
}