pub fn new(key_prefix: &str) -> Self {
Self {
key_prefix: key_prefix.to_string(),
use_local_storage: true,
use_session_storage: true,
use_indexed_db: true, }
}
pub async fn init_indexed_db(&self) -> Result<()> {
if !self.use_indexed_db {
return Ok(());
}
let window = web_sys::window().ok_or_else(|| anyhow::anyhow!("No window object"))?;
let indexed_db = window.indexed_db()?
.ok_or_else(|| anyhow::anyhow!("IndexedDB not supported"))?;
let db_name = format!("{}_synapse", self.key_prefix);
let open_request = indexed_db.open_with_u32(&db_name, 1)?;
let upgrade_needed = Closure::once_into_js(move |event: web_sys::IdbVersionChangeEvent| {
web_sys::console::log_1(&"Creating IndexedDB database...".into());
let db = event.target()
.and_then(|target| target.dyn_into::<web_sys::IdbOpenDbRequest>().ok())
.and_then(|request| request.result().ok())
.and_then(|result| result.dyn_into::<web_sys::IdbDatabase>().ok());
if let Some(db) = db {
let _ = db.create_object_store("participants");
let _ = db.create_object_store("messages");
let _ = db.create_object_store("blocks");
let _ = db.create_object_store("trust_reports");
web_sys::console::log_1(&"IndexedDB database created successfully!".into());
}
});
open_request.set_onupgradeneeded(Some(upgrade_needed.as_ref().unchecked_ref()));
let promise = Promise::new(&mut |resolve, reject| {
let on_success = Closure::once_into_js(move |_event: web_sys::Event| {
web_sys::console::log_1(&"IndexedDB opened successfully!".into());
resolve.call0(&JsValue::NULL).unwrap();
});
let on_error = Closure::once_into_js(move |event: web_sys::Event| {
web_sys::console::error_1(&"Error opening IndexedDB".into());
reject.call1(&JsValue::NULL, &event).unwrap();
});
open_request.set_onsuccess(Some(on_success.as_ref().unchecked_ref()));
open_request.set_onerror(Some(on_error.as_ref().unchecked_ref()));
});
JsFuture::from(promise).await?;
Ok(())
}
use wasm_bindgen::prelude::*;
use web_sys::{window, Storage};
use gloo::storage::{LocalStorage, SessionStorage, Storage as GlooStorage};
use std::collections::HashMap;
use crate::error::Result;
pub struct BrowserStorage {
key_prefix: String,
use_local_storage: bool,
use_session_storage: bool,
use_indexed_db: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum StorageType {
Persistent,
Session,
Database,
}
#[derive(Debug, Clone)]
pub struct StoredPeer {
pub entity_id: String,
pub display_name: String,
pub last_seen: f64,
pub capabilities: Vec<String>,
pub connection_info: StoredConnectionInfo,
}
#[derive(Debug, Clone)]
pub struct StoredConnectionInfo {
pub webrtc_supported: bool,
pub websocket_endpoints: Vec<String>,
pub ice_servers: Vec<String>,
pub preferred_transport: String,
}
impl BrowserStorage {
pub fn new(key_prefix: &str) -> Self {
Self {
key_prefix: key_prefix.to_string(),
use_local_storage: true,
use_session_storage: true,
use_indexed_db: true, }
}
pub async fn store_peer(&self, peer: &StoredPeer) -> Result<()> {
let key = format!("{}peer_{}", self.key_prefix, peer.entity_id);
let serialized = self.serialize_peer(peer)?;
if self.use_local_storage {
LocalStorage::set(&key, serialized)
.map_err(|e| anyhow::anyhow!("Failed to store peer in localStorage: {:?}", e))?;
}
web_sys::console::log_1(&format!("Stored peer: {}", peer.entity_id).into());
Ok(())
}
pub async fn load_peer(&self, entity_id: &str) -> Result<Option<StoredPeer>> {
let key = format!("{}peer_{}", self.key_prefix, entity_id);
if self.use_local_storage {
match LocalStorage::get::<String>(&key) {
Ok(serialized) => {
let peer = self.deserialize_peer(&serialized)?;
return Ok(Some(peer));
}
Err(_) => {
}
}
}
Ok(None)
}
pub async fn load_all_peers(&self) -> Result<Vec<StoredPeer>> {
let mut peers = Vec::new();
if let Some(storage) = self.get_local_storage() {
let peer_prefix = format!("{}peer_", self.key_prefix);
for i in 0..storage.length().unwrap_or(0) {
if let Ok(Some(key)) = storage.key(i) {
if key.starts_with(&peer_prefix) {
if let Ok(Some(value)) = storage.get_item(&key) {
if let Ok(peer) = self.deserialize_peer(&value) {
peers.push(peer);
}
}
}
}
}
}
web_sys::console::log_1(&format!("Loaded {} peers from storage", peers.len()).into());
Ok(peers)
}
pub async fn store_connection_history(&self, peer_id: &str, success: bool, latency_ms: Option<u32>) -> Result<()> {
let key = format!("{}connection_history_{}", self.key_prefix, peer_id);
let mut history = self.load_connection_history(peer_id).await.unwrap_or_default();
let entry = ConnectionHistoryEntry {
timestamp: js_sys::Date::now(),
success,
latency_ms,
};
history.push(entry);
if history.len() > 100 {
history.drain(0..history.len() - 100);
}
let serialized = self.serialize_connection_history(&history)?;
if self.use_local_storage {
LocalStorage::set(&key, serialized)
.map_err(|e| anyhow::anyhow!("Failed to store connection history: {:?}", e))?;
}
Ok(())
}
pub async fn load_connection_history(&self, peer_id: &str) -> Result<Vec<ConnectionHistoryEntry>> {
let key = format!("{}connection_history_{}", self.key_prefix, peer_id);
if self.use_local_storage {
match LocalStorage::get::<String>(&key) {
Ok(serialized) => {
return self.deserialize_connection_history(&serialized);
}
Err(_) => {
}
}
}
Ok(Vec::new())
}
pub async fn store_config(&self, config: &HashMap<String, String>) -> Result<()> {
let key = format!("{}config", self.key_prefix);
let serialized = self.serialize_config(config)?;
if self.use_local_storage {
LocalStorage::set(&key, serialized)
.map_err(|e| anyhow::anyhow!("Failed to store config: {:?}", e))?;
}
web_sys::console::log_1(&"Stored application configuration".into());
Ok(())
}
pub async fn load_config(&self) -> Result<HashMap<String, String>> {
let key = format!("{}config", self.key_prefix);
if self.use_local_storage {
match LocalStorage::get::<String>(&key) {
Ok(serialized) => {
return self.deserialize_config(&serialized);
}
Err(_) => {
}
}
}
Ok(HashMap::new())
}
pub async fn clear_all(&self) -> Result<()> {
if let Some(storage) = self.get_local_storage() {
let mut keys_to_remove = Vec::new();
for i in 0..storage.length().unwrap_or(0) {
if let Ok(Some(key)) = storage.key(i) {
if key.starts_with(&self.key_prefix) {
keys_to_remove.push(key);
}
}
}
for key in keys_to_remove {
storage.remove_item(&key).ok();
}
}
web_sys::console::log_1(&"Cleared all Synapse data from storage".into());
Ok(())
}
pub async fn get_usage_stats(&self) -> Result<StorageStats> {
let mut total_size = 0;
let mut peer_count = 0;
let mut config_size = 0;
if let Some(storage) = self.get_local_storage() {
for i in 0..storage.length().unwrap_or(0) {
if let Ok(Some(key)) = storage.key(i) {
if key.starts_with(&self.key_prefix) {
if let Ok(Some(value)) = storage.get_item(&key) {
total_size += key.len() + value.len();
if key.contains("peer_") {
peer_count += 1;
} else if key.contains("config") {
config_size += value.len();
}
}
}
}
}
}
Ok(StorageStats {
total_size_bytes: total_size,
peer_count,
config_size_bytes: config_size,
})
}
pub async fn store_in_indexed_db(&self, store_name: &str, key: &str, value: &str) -> Result<()> {
if !self.use_indexed_db {
return Ok(());
}
let window = web_sys::window().ok_or_else(|| anyhow::anyhow!("No window object"))?;
let indexed_db = window.indexed_db()?
.ok_or_else(|| anyhow::anyhow!("IndexedDB not supported"))?;
let db_name = format!("{}_synapse", self.key_prefix);
let open_request = indexed_db.open(&db_name)?;
let key_str = key.to_string();
let val_str = value.to_string();
let store_name_str = store_name.to_string();
let promise = Promise::new(&mut |resolve, reject| {
let on_success = Closure::once_into_js(move |event: web_sys::Event| {
let db = event.target()
.and_then(|target| target.dyn_into::<web_sys::IdbOpenDbRequest>().ok())
.and_then(|request| request.result().ok())
.and_then(|result| result.dyn_into::<web_sys::IdbDatabase>().ok());
if let Some(db) = db {
if let Ok(tx) = db.transaction_with_str_and_mode(
&store_name_str,
web_sys::IdbTransactionMode::Readwrite,
) {
if let Ok(store) = tx.object_store(&store_name_str) {
let key_js = JsValue::from_str(&key_str);
let value_js = JsValue::from_str(&val_str);
if let Ok(put_request) = store.put_with_key(&value_js, &key_js) {
let put_success = Closure::once_into_js(move |_: web_sys::Event| {
web_sys::console::log_1(&format!("Stored {} in IndexedDB", key_str).into());
resolve.call0(&JsValue::NULL).unwrap();
});
let put_error = Closure::once_into_js(move |event: web_sys::Event| {
web_sys::console::error_1(&format!("Error storing in IndexedDB: {:?}", event).into());
reject.call1(&JsValue::NULL, &event).unwrap();
});
put_request.set_onsuccess(Some(put_success.as_ref().unchecked_ref()));
put_request.set_onerror(Some(put_error.as_ref().unchecked_ref()));
} else {
reject.call1(&JsValue::NULL, &JsValue::from_str("Failed to create put request")).unwrap();
}
} else {
reject.call1(&JsValue::NULL, &JsValue::from_str("Failed to open object store")).unwrap();
}
} else {
reject.call1(&JsValue::NULL, &JsValue::from_str("Failed to create transaction")).unwrap();
}
} else {
reject.call1(&JsValue::NULL, &JsValue::from_str("Failed to open database")).unwrap();
}
});
let on_error = Closure::once_into_js(move |event: web_sys::Event| {
web_sys::console::error_1(&"Error opening IndexedDB".into());
reject.call1(&JsValue::NULL, &event).unwrap();
});
open_request.set_onsuccess(Some(on_success.as_ref().unchecked_ref()));
open_request.set_onerror(Some(on_error.as_ref().unchecked_ref()));
});
JsFuture::from(promise).await?;
Ok(())
}
pub async fn get_from_indexed_db(&self, store_name: &str, key: &str) -> Result<Option<String>> {
if !self.use_indexed_db {
return Ok(None);
}
let window = web_sys::window().ok_or_else(|| anyhow::anyhow!("No window object"))?;
let indexed_db = window.indexed_db()?
.ok_or_else(|| anyhow::anyhow!("IndexedDB not supported"))?;
let db_name = format!("{}_synapse", self.key_prefix);
let open_request = indexed_db.open(&db_name)?;
let key_str = key.to_string();
let store_name_str = store_name.to_string();
let promise = Promise::new(&mut |resolve, reject| {
let on_success = Closure::once_into_js(move |event: web_sys::Event| {
let db = event.target()
.and_then(|target| target.dyn_into::<web_sys::IdbOpenDbRequest>().ok())
.and_then(|request| request.result().ok())
.and_then(|result| result.dyn_into::<web_sys::IdbDatabase>().ok());
if let Some(db) = db {
if let Ok(tx) = db.transaction_with_str(&store_name_str) {
if let Ok(store) = tx.object_store(&store_name_str) {
let key_js = JsValue::from_str(&key_str);
if let Ok(get_request) = store.get(&key_js) {
let get_success = Closure::once_into_js(move |event: web_sys::Event| {
let result = event.target()
.and_then(|target| target.dyn_into::<web_sys::IdbRequest>().ok())
.and_then(|request| request.result().ok());
if let Some(value) = result {
if value.is_undefined() || value.is_null() {
resolve.call1(&JsValue::NULL, &JsValue::NULL).unwrap();
} else if let Some(value_str) = value.as_string() {
resolve.call1(&JsValue::NULL, &JsValue::from_str(&value_str)).unwrap();
} else {
resolve.call1(&JsValue::NULL, &JsValue::NULL).unwrap();
}
} else {
resolve.call1(&JsValue::NULL, &JsValue::NULL).unwrap();
}
});
let get_error = Closure::once_into_js(move |event: web_sys::Event| {
web_sys::console::error_1(&format!("Error getting from IndexedDB: {:?}", event).into());
reject.call1(&JsValue::NULL, &event).unwrap();
});
get_request.set_onsuccess(Some(get_success.as_ref().unchecked_ref()));
get_request.set_onerror(Some(get_error.as_ref().unchecked_ref()));
} else {
reject.call1(&JsValue::NULL, &JsValue::from_str("Failed to create get request")).unwrap();
}
} else {
reject.call1(&JsValue::NULL, &JsValue::from_str("Failed to open object store")).unwrap();
}
} else {
reject.call1(&JsValue::NULL, &JsValue::from_str("Failed to create transaction")).unwrap();
}
} else {
reject.call1(&JsValue::NULL, &JsValue::from_str("Failed to open database")).unwrap();
}
});
let on_error = Closure::once_into_js(move |event: web_sys::Event| {
web_sys::console::error_1(&"Error opening IndexedDB".into());
reject.call1(&JsValue::NULL, &event).unwrap();
});
open_request.set_onsuccess(Some(on_success.as_ref().unchecked_ref()));
open_request.set_onerror(Some(on_error.as_ref().unchecked_ref()));
});
let result = JsFuture::from(promise).await?;
if result.is_null() || result.is_undefined() {
Ok(None)
} else {
Ok(result.as_string())
}
}
pub async fn store_large_data(&self, key: &str, data: &[u8]) -> Result<()> {
if self.use_indexed_db {
self.store_in_indexed_db(key, data).await
} else {
let encoded_data = base64::encode(data);
self.store_in_local_storage(key, &encoded_data).await
}
}
pub async fn load_large_data(&self, key: &str) -> Result<Option<Vec<u8>>> {
if self.use_indexed_db {
self.load_from_indexed_db(key).await
} else {
if let Some(encoded_data) = self.load_from_local_storage(key).await? {
base64::decode(&encoded_data)
.map(Some)
.map_err(|e| anyhow::anyhow!("Failed to decode data: {}", e))
} else {
Ok(None)
}
}
}
}
#[derive(Debug, Clone)]
pub struct ConnectionHistoryEntry {
pub timestamp: f64,
pub success: bool,
pub latency_ms: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct StorageStats {
pub total_size_bytes: usize,
pub peer_count: usize,
pub config_size_bytes: usize,
}
pub mod indexed_db {
use super::*;
pub struct IndexedDbStorage {
db_name: String,
version: u32,
}
impl IndexedDbStorage {
pub fn new(db_name: String) -> Self {
Self {
db_name,
version: 1,
}
}
pub async fn initialize(&self) -> Result<()> {
web_sys::console::log_1(&format!("Initializing IndexedDB: {}", self.db_name).into());
Ok(())
}
}
}