feat: implement browser extension UI with WebAssembly integration
This commit is contained in:
@@ -9,8 +9,9 @@ path = "src/lib.rs"
|
||||
[dependencies]
|
||||
tokio = { version = "1.37", features = ["rt", "macros"] }
|
||||
async-trait = "0.1"
|
||||
js-sys = "0.3"
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
getrandom = { version = "0.3", features = ["wasm_js"] }
|
||||
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
thiserror = "1"
|
||||
|
||||
@@ -22,7 +23,9 @@ tempfile = "3"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
idb = { version = "0.4" }
|
||||
getrandom = { version = "0.3", features = ["wasm_js"] }
|
||||
getrandom_02 = { package = "getrandom", version = "0.2.16", features = ["js"] }
|
||||
idb = { version = "0.6" }
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[features]
|
||||
|
@@ -22,3 +22,12 @@ pub enum KVError {
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, KVError>;
|
||||
|
||||
// Allow automatic conversion from idb::Error to KVError
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl From<idb::Error> for KVError {
|
||||
fn from(e: idb::Error) -> Self {
|
||||
KVError::Other(format!("idb error: {e:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -26,8 +26,7 @@ use async_trait::async_trait;
|
||||
use idb::{Database, TransactionMode, Factory};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::JsValue;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use js_sys::Uint8Array;
|
||||
// use wasm-bindgen directly for Uint8Array if needed
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -47,6 +46,7 @@ impl WasmStore {
|
||||
let mut open_req = factory.open(name, None)
|
||||
.map_err(|e| KVError::Other(format!("IndexedDB factory open error: {e:?}")))?;
|
||||
open_req.on_upgrade_needed(|event| {
|
||||
use idb::DatabaseEvent;
|
||||
let db = event.database().expect("Failed to get database in upgrade event");
|
||||
if !db.store_names().iter().any(|n| n == STORE_NAME) {
|
||||
db.create_object_store(STORE_NAME, Default::default()).unwrap();
|
||||
@@ -66,11 +66,13 @@ impl KVStore for WasmStore {
|
||||
let store = tx.object_store(STORE_NAME)
|
||||
.map_err(|e| KVError::Other(format!("idb object_store error: {e:?}")))?;
|
||||
use idb::Query;
|
||||
let val = store.get(Query::from(JsValue::from_str(key))).await
|
||||
.map_err(|e| KVError::Other(format!("idb get await error: {e:?}")))?;
|
||||
let val = store.get(Query::from(JsValue::from_str(key)))?.await
|
||||
.map_err(|e| KVError::Other(format!("idb get error: {e:?}")))?;
|
||||
if let Some(jsval) = val {
|
||||
let arr = Uint8Array::new(&jsval);
|
||||
Ok(Some(arr.to_vec()))
|
||||
match jsval.into_serde::<Vec<u8>>() {
|
||||
Ok(bytes) => Ok(Some(bytes)),
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -80,8 +82,9 @@ impl KVStore for WasmStore {
|
||||
.map_err(|e| KVError::Other(format!("idb transaction error: {e:?}")))?;
|
||||
let store = tx.object_store(STORE_NAME)
|
||||
.map_err(|e| KVError::Other(format!("idb object_store error: {e:?}")))?;
|
||||
store.put(&Uint8Array::from(value).into(), Some(&JsValue::from_str(key))).await
|
||||
.map_err(|e| KVError::Other(format!("idb put await error: {e:?}")))?;
|
||||
let js_value = JsValue::from_serde(&value).map_err(|e| KVError::Other(format!("serde error: {e:?}")))?;
|
||||
store.put(&js_value, Some(&JsValue::from_str(key)))?.await
|
||||
.map_err(|e| KVError::Other(format!("idb put error: {e:?}")))?;
|
||||
Ok(())
|
||||
}
|
||||
async fn remove(&self, key: &str) -> Result<()> {
|
||||
@@ -90,8 +93,8 @@ impl KVStore for WasmStore {
|
||||
let store = tx.object_store(STORE_NAME)
|
||||
.map_err(|e| KVError::Other(format!("idb object_store error: {e:?}")))?;
|
||||
use idb::Query;
|
||||
store.delete(Query::from(JsValue::from_str(key))).await
|
||||
.map_err(|e| KVError::Other(format!("idb delete await error: {e:?}")))?;
|
||||
store.delete(Query::from(JsValue::from_str(key)))?.await
|
||||
.map_err(|e| KVError::Other(format!("idb delete error: {e:?}")))?;
|
||||
Ok(())
|
||||
}
|
||||
async fn contains_key(&self, key: &str) -> Result<bool> {
|
||||
@@ -103,12 +106,11 @@ impl KVStore for WasmStore {
|
||||
.map_err(|e| KVError::Other(format!("idb transaction error: {e:?}")))?;
|
||||
let store = tx.object_store(STORE_NAME)
|
||||
.map_err(|e| KVError::Other(format!("idb object_store error: {e:?}")))?;
|
||||
let js_keys = store.get_all_keys(None, None).await
|
||||
let js_keys = store.get_all_keys(None, None)?.await
|
||||
.map_err(|e| KVError::Other(format!("idb get_all_keys error: {e:?}")))?;
|
||||
let arr = js_sys::Array::from(&JsValue::from(js_keys));
|
||||
let mut keys = Vec::new();
|
||||
for i in 0..arr.length() {
|
||||
if let Some(s) = arr.get(i).as_string() {
|
||||
for key in js_keys.iter() {
|
||||
if let Some(s) = key.as_string() {
|
||||
keys.push(s);
|
||||
}
|
||||
}
|
||||
@@ -120,7 +122,7 @@ impl KVStore for WasmStore {
|
||||
.map_err(|e| KVError::Other(format!("idb transaction error: {e:?}")))?;
|
||||
let store = tx.object_store(STORE_NAME)
|
||||
.map_err(|e| KVError::Other(format!("idb object_store error: {e:?}")))?;
|
||||
store.clear().await
|
||||
store.clear()?.await
|
||||
.map_err(|e| KVError::Other(format!("idb clear error: {e:?}")))?;
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -31,3 +31,22 @@ async fn test_native_store_basic() {
|
||||
let keys = store.keys().await.unwrap();
|
||||
assert_eq!(keys.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_native_store_persistence() {
|
||||
let tmp_dir = tempfile::tempdir().unwrap();
|
||||
let path = tmp_dir.path().join("persistdb");
|
||||
let db_path = path.to_str().unwrap();
|
||||
// First open, set value
|
||||
{
|
||||
let store = NativeStore::open(db_path).unwrap();
|
||||
store.set("persist", b"value").await.unwrap();
|
||||
}
|
||||
// Reopen and check value
|
||||
{
|
||||
let store = NativeStore::open(db_path).unwrap();
|
||||
let val = store.get("persist").await.unwrap();
|
||||
assert_eq!(val, Some(b"value".to_vec()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,7 @@ wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_set_and_get() {
|
||||
let store = WasmStore::open("test-db").await.expect("open");
|
||||
let store = WasmStore::open("vault").await.expect("open");
|
||||
store.set("foo", b"bar").await.expect("set");
|
||||
let val = store.get("foo").await.expect("get");
|
||||
assert_eq!(val, Some(b"bar".to_vec()));
|
||||
@@ -16,7 +16,7 @@ async fn test_set_and_get() {
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_delete_and_exists() {
|
||||
let store = WasmStore::open("test-db").await.expect("open");
|
||||
let store = WasmStore::open("vault").await.expect("open");
|
||||
store.set("foo", b"bar").await.expect("set");
|
||||
assert_eq!(store.contains_key("foo").await.unwrap(), true);
|
||||
assert_eq!(store.contains_key("bar").await.unwrap(), false);
|
||||
@@ -26,7 +26,7 @@ async fn test_delete_and_exists() {
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_keys() {
|
||||
let store = WasmStore::open("test-db").await.expect("open");
|
||||
let store = WasmStore::open("vault").await.expect("open");
|
||||
store.set("foo", b"bar").await.expect("set");
|
||||
store.set("baz", b"qux").await.expect("set");
|
||||
let keys = store.keys().await.unwrap();
|
||||
@@ -35,9 +35,26 @@ async fn test_keys() {
|
||||
assert!(keys.contains(&"baz".to_string()));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_wasm_store_persistence() {
|
||||
// Use a unique store name to avoid collisions
|
||||
let store_name = "persist_test_store";
|
||||
// First open, set value
|
||||
{
|
||||
let store = WasmStore::open(store_name).await.expect("open");
|
||||
store.set("persist", b"value").await.expect("set");
|
||||
}
|
||||
// Reopen and check value
|
||||
{
|
||||
let store = WasmStore::open(store_name).await.expect("open");
|
||||
let val = store.get("persist").await.expect("get");
|
||||
assert_eq!(val, Some(b"value".to_vec()));
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_clear() {
|
||||
let store = WasmStore::open("test-db").await.expect("open");
|
||||
let store = WasmStore::open("vault").await.expect("open");
|
||||
store.set("foo", b"bar").await.expect("set");
|
||||
store.set("baz", b"qux").await.expect("set");
|
||||
store.clear().await.unwrap();
|
||||
|
Reference in New Issue
Block a user