//! WebAssembly bindings for accessing vault operations (session, keypairs, signing, scripting, etc) #![cfg(target_arch = "wasm32")] use kvstore::wasm::WasmStore; use once_cell::unsync::Lazy; use rhai::Engine; use std::cell::RefCell; use vault::rhai_bindings as vault_rhai_bindings; use vault::session::SessionManager; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; thread_local! { static ENGINE: Lazy> = Lazy::new(|| RefCell::new(Engine::new())); static SESSION_PASSWORD: RefCell>> = RefCell::new(None); } pub use vault::session_singleton::SESSION_MANAGER; // ===================== // Session Lifecycle // ===================== /// Initialize session with keyspace and password #[wasm_bindgen] pub async fn init_session(keyspace: &str, password: &str) -> Result<(), JsValue> { let keyspace = keyspace.to_string(); let password_vec = password.as_bytes().to_vec(); match WasmStore::open(&keyspace).await { Ok(store) => { let vault = vault::Vault::new(store); let mut manager = SessionManager::new(vault); match manager.unlock_keyspace(&keyspace, &password_vec).await { Ok(_) => { SESSION_MANAGER.with(|cell| cell.replace(Some(manager))); } Err(e) => { web_sys::console::error_1(&format!("Failed to unlock keyspace: {e}").into()); return Err(JsValue::from_str(&format!("Failed to unlock keyspace: {e}"))); } } } Err(e) => { web_sys::console::error_1(&format!("Failed to open WasmStore: {e}").into()); return Err(JsValue::from_str(&format!("Failed to open WasmStore: {e}"))); } } SESSION_PASSWORD.with(|cell| cell.replace(Some(password.as_bytes().to_vec()))); Ok(()) } /// Lock the session (zeroize password and session) #[wasm_bindgen] pub fn lock_session() { SESSION_MANAGER.with(|cell| *cell.borrow_mut() = None); SESSION_PASSWORD.with(|cell| *cell.borrow_mut() = None); } // ===================== // Keypair Management // ===================== /// Get all keypairs from the current session /// Returns an array of keypair objects with id, type, and metadata // #[wasm_bindgen] // pub async fn list_keypairs() -> Result { // // [Function body commented out to resolve duplicate symbol error] // // (Original implementation moved to keypair_bindings.rs) // unreachable!("This function is disabled. Use the export from keypair_bindings.rs."); // } // [Function body commented out to resolve duplicate symbol error] // } /// Select keypair for the session #[wasm_bindgen] pub fn select_keypair(key_id: &str) -> Result<(), JsValue> { let mut result = Err(JsValue::from_str("Session not initialized")); SESSION_MANAGER.with(|cell| { if let Some(session) = cell.borrow_mut().as_mut() { result = session .select_keypair(key_id) .map_err(|e| JsValue::from_str(&format!("select_keypair error: {e}"))); } }); result } /// List keypairs in the current session's keyspace #[wasm_bindgen] pub async fn list_keypairs() -> Result { SESSION_MANAGER.with(|cell| { if let Some(session) = cell.borrow().as_ref() { if let Some(keyspace) = session.current_keyspace() { let keypairs = &keyspace.keypairs; serde_json::to_string(keypairs) .map(|s| JsValue::from_str(&s)) .map_err(|e| JsValue::from_str(&format!("Serialization error: {e}"))) } else { Err(JsValue::from_str("No keyspace unlocked")) } } else { Err(JsValue::from_str("Session not initialized")) } }) } /// Add a keypair to the current keyspace #[wasm_bindgen] pub async fn add_keypair( key_type: Option, metadata: Option, ) -> Result { use vault::{KeyMetadata, KeyType}; let password = SESSION_PASSWORD .with(|pw| pw.borrow().clone()) .ok_or_else(|| JsValue::from_str("Session password not set"))?; let (keyspace_name, session_exists) = SESSION_MANAGER.with(|cell| { if let Some(ref session) = cell.borrow().as_ref() { let keyspace_name = session.current_keyspace().map(|_| "".to_string()); // TODO: replace with actual keyspace name if available; (keyspace_name, true) } else { (None, false) } }); let keyspace_name = keyspace_name.ok_or_else(|| JsValue::from_str("No keyspace selected"))?; if !session_exists { return Err(JsValue::from_str("Session not initialized")); } let key_type = key_type .as_deref() .map(|s| match s { "Ed25519" => KeyType::Ed25519, "Secp256k1" => KeyType::Secp256k1, _ => KeyType::Secp256k1, }) .unwrap_or(KeyType::Secp256k1); let metadata = match metadata { Some(ref meta_str) => Some( serde_json::from_str::(meta_str) .map_err(|e| JsValue::from_str(&format!("Invalid metadata: {e}")))?, ), None => None, }; // Take session out, do async work, then put it back let mut session_opt = SESSION_MANAGER.with(|cell| cell.borrow_mut().take()); let session = session_opt.as_mut().ok_or_else(|| JsValue::from_str("Session not initialized"))?; let key_id = session .get_vault_mut() .add_keypair(&keyspace_name, &password, Some(key_type), metadata) .await .map_err(|e| JsValue::from_str(&format!("add_keypair error: {e}")))?; // Put session back SESSION_MANAGER.with(|cell| *cell.borrow_mut() = Some(session_opt.take().unwrap())); Ok(JsValue::from_str(&key_id)) } /// Sign message with current session #[wasm_bindgen] pub async fn sign(message: &[u8]) -> Result { { // SAFETY: We only use this pointer synchronously within this function, and SESSION_MANAGER outlives this scope. let session_ptr = SESSION_MANAGER.with(|cell| cell.borrow().as_ref().map(|s| s as *const _)); let password_opt = SESSION_PASSWORD.with(|pw| pw.borrow().clone()); let session: &vault::session::SessionManager = match session_ptr { Some(ptr) => unsafe { &*ptr }, None => return Err(JsValue::from_str("Session not initialized")), }; let password = match password_opt { Some(p) => p, None => return Err(JsValue::from_str("Session password not set")), }; match session.sign(message).await { Ok(sig_bytes) => { let hex_sig = hex::encode(&sig_bytes); Ok(JsValue::from_str(&hex_sig)) } Err(e) => Err(JsValue::from_str(&format!("Sign error: {e}"))), } } }