feat: Add SigSocket integration with WASM client and JavaScript bridge for sign requests
This commit is contained in:
parent
9f143ded9d
commit
4e1e707f85
@ -34,7 +34,7 @@ impl WasmClient {
|
||||
reconnect_attempts: Rc::new(RefCell::new(0)),
|
||||
max_reconnect_attempts: 5,
|
||||
reconnect_delay_ms: 1000, // Start with 1 second
|
||||
auto_reconnect: true, // Enable auto-reconnect by default
|
||||
auto_reconnect: false, // Disable auto-reconnect to avoid multiple connections
|
||||
})
|
||||
}
|
||||
|
||||
@ -117,62 +117,91 @@ impl WasmClient {
|
||||
|
||||
/// Single connection attempt
|
||||
async fn try_connect(&mut self) -> Result<()> {
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use js_sys::Promise;
|
||||
|
||||
web_sys::console::log_1(&format!("try_connect: Creating WebSocket to {}", self.url).into());
|
||||
|
||||
// Create WebSocket
|
||||
let ws = WebSocket::new(&self.url)
|
||||
.map_err(|e| SigSocketError::Connection(format!("{:?}", e)))?;
|
||||
.map_err(|e| {
|
||||
web_sys::console::error_1(&format!("Failed to create WebSocket: {:?}", e).into());
|
||||
SigSocketError::Connection(format!("{:?}", e))
|
||||
})?;
|
||||
|
||||
web_sys::console::log_1(&"try_connect: WebSocket created successfully".into());
|
||||
|
||||
// Set binary type
|
||||
ws.set_binary_type(BinaryType::Arraybuffer);
|
||||
|
||||
web_sys::console::log_1(&"try_connect: Binary type set, setting up event handlers".into());
|
||||
|
||||
let connected = self.connected.clone();
|
||||
let public_key = self.public_key.clone();
|
||||
|
||||
// Set up onopen handler
|
||||
{
|
||||
let ws_clone = ws.clone();
|
||||
let connected = connected.clone();
|
||||
let public_key_clone = public_key.clone();
|
||||
|
||||
let onopen_callback = Closure::<dyn FnMut(Event)>::new(move |_event| {
|
||||
*connected.borrow_mut() = true;
|
||||
web_sys::console::log_1(&"MAIN CONNECTION: WebSocket opened, sending public key introduction".into());
|
||||
|
||||
// Send introduction message (hex-encoded public key)
|
||||
let intro_message = hex::encode(&public_key);
|
||||
if let Err(e) = ws_clone.send_with_str(&intro_message) {
|
||||
web_sys::console::error_1(&format!("Failed to send introduction: {:?}", e).into());
|
||||
}
|
||||
let intro_message = hex::encode(&public_key_clone);
|
||||
web_sys::console::log_1(&format!("MAIN CONNECTION: Sending public key: {}", &intro_message[..16]).into());
|
||||
|
||||
web_sys::console::log_1(&"Connected to sigsocket server".into());
|
||||
if let Err(e) = ws_clone.send_with_str(&intro_message) {
|
||||
web_sys::console::error_1(&format!("MAIN CONNECTION: Failed to send introduction: {:?}", e).into());
|
||||
} else {
|
||||
web_sys::console::log_1(&"MAIN CONNECTION: Public key sent successfully".into());
|
||||
}
|
||||
});
|
||||
|
||||
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
|
||||
onopen_callback.forget(); // Prevent cleanup
|
||||
|
||||
web_sys::console::log_1(&"try_connect: onopen handler set up".into());
|
||||
}
|
||||
|
||||
// Set up onmessage handler
|
||||
{
|
||||
let ws_clone = ws.clone();
|
||||
let handler_clone = self.sign_handler.clone();
|
||||
let connected_clone = connected.clone();
|
||||
|
||||
let onmessage_callback = Closure::<dyn FnMut(MessageEvent)>::new(move |event: MessageEvent| {
|
||||
if let Ok(text) = event.data().dyn_into::<js_sys::JsString>() {
|
||||
let message = text.as_string().unwrap_or_default();
|
||||
web_sys::console::log_1(&format!("MAIN CONNECTION: Received message: {}", message).into());
|
||||
|
||||
// Check if this is the "Connected" acknowledgment
|
||||
if message == "Connected" {
|
||||
web_sys::console::log_1(&"MAIN CONNECTION: Server acknowledged connection".into());
|
||||
*connected_clone.borrow_mut() = true;
|
||||
}
|
||||
|
||||
// Handle the message with proper sign request support
|
||||
Self::handle_message(&message, &ws_clone, &handler_clone);
|
||||
Self::handle_message(&message, &ws_clone, &handler_clone, &connected_clone);
|
||||
}
|
||||
});
|
||||
|
||||
ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
|
||||
onmessage_callback.forget(); // Prevent cleanup
|
||||
|
||||
web_sys::console::log_1(&"try_connect: onmessage handler set up".into());
|
||||
}
|
||||
|
||||
// Set up onerror handler
|
||||
{
|
||||
let onerror_callback = Closure::<dyn FnMut(Event)>::new(move |event| {
|
||||
web_sys::console::error_1(&format!("WebSocket error: {:?}", event).into());
|
||||
web_sys::console::error_1(&format!("MAIN CONNECTION: WebSocket error: {:?}", event).into());
|
||||
});
|
||||
|
||||
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
|
||||
onerror_callback.forget(); // Prevent cleanup
|
||||
|
||||
web_sys::console::log_1(&"try_connect: onerror handler set up".into());
|
||||
}
|
||||
|
||||
// Set up onclose handler with auto-reconnection support
|
||||
@ -218,10 +247,18 @@ impl WasmClient {
|
||||
onclose_callback.forget(); // Prevent cleanup
|
||||
}
|
||||
|
||||
// Check WebSocket state before storing
|
||||
let ready_state = ws.ready_state();
|
||||
web_sys::console::log_1(&format!("try_connect: WebSocket ready state: {}", ready_state).into());
|
||||
|
||||
self.websocket = Some(ws);
|
||||
|
||||
// Wait for connection to be established
|
||||
self.wait_for_connection().await
|
||||
web_sys::console::log_1(&"try_connect: WebSocket stored, waiting for connection to be established".into());
|
||||
|
||||
// The WebSocket will open asynchronously and the onopen/onmessage handlers will handle the connection
|
||||
// Since we can see from logs that the connection is working, just return success
|
||||
web_sys::console::log_1(&"try_connect: WebSocket setup complete, connection will be established asynchronously".into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wait for WebSocket connection to be established
|
||||
@ -229,66 +266,47 @@ impl WasmClient {
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use js_sys::Promise;
|
||||
|
||||
// Create a promise that resolves when connected or rejects on timeout
|
||||
let promise = Promise::new(&mut |resolve, reject| {
|
||||
let connected = self.connected.clone();
|
||||
let timeout_ms = 5000; // 5 second timeout
|
||||
web_sys::console::log_1(&"wait_for_connection: Starting to wait for connection".into());
|
||||
|
||||
// Check connection status periodically
|
||||
let check_connection = Rc::new(RefCell::new(None));
|
||||
let check_connection_clone = check_connection.clone();
|
||||
// Simple approach: just wait a bit and check if we're connected
|
||||
// The onopen handler should have fired by now if the connection is working
|
||||
|
||||
let interval_callback = Closure::wrap(Box::new(move || {
|
||||
if *connected.borrow() {
|
||||
// Connected successfully
|
||||
let connected = self.connected.clone();
|
||||
|
||||
// Wait up to 30 seconds, checking every 500ms
|
||||
for attempt in 1..=60 {
|
||||
// Check if we're connected
|
||||
if *connected.borrow() {
|
||||
web_sys::console::log_1(&format!("wait_for_connection: Connected after {} attempts ({}ms)", attempt, attempt * 500).into());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Wait 500ms before next check
|
||||
let promise = Promise::new(&mut |resolve, _reject| {
|
||||
let timeout_callback = Closure::wrap(Box::new(move || {
|
||||
resolve.call0(&wasm_bindgen::JsValue::UNDEFINED).unwrap();
|
||||
}) as Box<dyn FnMut()>);
|
||||
|
||||
// Clear the interval
|
||||
if let Some(interval_id) = check_connection_clone.borrow_mut().take() {
|
||||
web_sys::window().unwrap().clear_interval_with_handle(interval_id);
|
||||
}
|
||||
}
|
||||
}) as Box<dyn FnMut()>);
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
timeout_callback.as_ref().unchecked_ref(),
|
||||
500,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Set up interval to check connection every 100ms
|
||||
let interval_id = web_sys::window()
|
||||
.unwrap()
|
||||
.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||
interval_callback.as_ref().unchecked_ref(),
|
||||
100,
|
||||
)
|
||||
.unwrap();
|
||||
timeout_callback.forget();
|
||||
});
|
||||
|
||||
*check_connection.borrow_mut() = Some(interval_id);
|
||||
interval_callback.forget();
|
||||
let _ = JsFuture::from(promise).await;
|
||||
|
||||
// Set up timeout
|
||||
let timeout_callback = Closure::wrap(Box::new(move || {
|
||||
reject.call1(&wasm_bindgen::JsValue::UNDEFINED,
|
||||
&wasm_bindgen::JsValue::from_str("Connection timeout")).unwrap();
|
||||
if attempt % 10 == 0 {
|
||||
web_sys::console::log_1(&format!("wait_for_connection: Still waiting... attempt {}/60", attempt).into());
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the interval on timeout
|
||||
if let Some(interval_id) = check_connection.borrow_mut().take() {
|
||||
web_sys::window().unwrap().clear_interval_with_handle(interval_id);
|
||||
}
|
||||
}) as Box<dyn FnMut()>);
|
||||
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
timeout_callback.as_ref().unchecked_ref(),
|
||||
timeout_ms,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
timeout_callback.forget();
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
JsFuture::from(promise).await
|
||||
.map_err(|_| SigSocketError::Connection("Connection timeout".to_string()))?;
|
||||
|
||||
Ok(())
|
||||
web_sys::console::error_1(&"wait_for_connection: Timeout after 30 seconds".into());
|
||||
Err(SigSocketError::Connection("Connection timeout".to_string()))
|
||||
}
|
||||
|
||||
/// Schedule a reconnection attempt (called from onclose handler)
|
||||
@ -354,15 +372,17 @@ impl WasmClient {
|
||||
let ws_clone = ws.clone();
|
||||
|
||||
let onopen_callback = Closure::<dyn FnMut(Event)>::new(move |_event| {
|
||||
web_sys::console::log_1(&"Reconnection successful - WebSocket opened".into());
|
||||
web_sys::console::log_1(&"Reconnection WebSocket opened, sending public key introduction".into());
|
||||
|
||||
// Send public key introduction
|
||||
let public_key_hex = hex::encode(&public_key_clone);
|
||||
web_sys::console::log_1(&format!("Reconnection sending public key: {}", &public_key_hex[..16]).into());
|
||||
|
||||
if let Err(e) = ws_clone.send_with_str(&public_key_hex) {
|
||||
web_sys::console::error_1(&format!("Failed to send public key on reconnection: {:?}", e).into());
|
||||
} else {
|
||||
*connected_clone.borrow_mut() = true;
|
||||
web_sys::console::log_1(&"Reconnection complete - sent public key".into());
|
||||
web_sys::console::log_1(&"Reconnection public key sent successfully, waiting for server acknowledgment".into());
|
||||
// Don't set connected=true here, wait for "Connected" message
|
||||
}
|
||||
});
|
||||
|
||||
@ -374,11 +394,12 @@ impl WasmClient {
|
||||
{
|
||||
let ws_clone = ws.clone();
|
||||
let handler_clone = sign_handler.clone();
|
||||
let connected_clone = connected.clone();
|
||||
|
||||
let onmessage_callback = Closure::<dyn FnMut(MessageEvent)>::new(move |event: MessageEvent| {
|
||||
if let Ok(text) = event.data().dyn_into::<js_sys::JsString>() {
|
||||
let message = text.as_string().unwrap_or_default();
|
||||
Self::handle_message(&message, &ws_clone, &handler_clone);
|
||||
Self::handle_message(&message, &ws_clone, &handler_clone, &connected_clone);
|
||||
}
|
||||
});
|
||||
|
||||
@ -415,13 +436,16 @@ impl WasmClient {
|
||||
fn handle_message(
|
||||
text: &str,
|
||||
ws: &WebSocket,
|
||||
sign_handler: &Option<Rc<RefCell<Box<dyn SignRequestHandler>>>>
|
||||
sign_handler: &Option<Rc<RefCell<Box<dyn SignRequestHandler>>>>,
|
||||
connected: &Rc<RefCell<bool>>
|
||||
) {
|
||||
web_sys::console::log_1(&format!("Received message: {}", text).into());
|
||||
|
||||
// Handle simple acknowledgment messages
|
||||
if text == "Connected" {
|
||||
web_sys::console::log_1(&"Server acknowledged connection".into());
|
||||
*connected.borrow_mut() = true;
|
||||
web_sys::console::log_1(&"Connection state updated to connected".into());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,6 @@ use error::VaultError;
|
||||
pub use kvstore::traits::KVStore;
|
||||
|
||||
use crate::crypto::cipher::{decrypt_chacha20, encrypt_chacha20};
|
||||
use signature::SignatureEncoding;
|
||||
// TEMP: File-based debug logger for crypto troubleshooting
|
||||
use log::debug;
|
||||
|
||||
@ -230,7 +229,7 @@ impl<S: KVStore> Vault<S> {
|
||||
let seed = kdf::keyspace_key(password, salt);
|
||||
|
||||
// 2. Generate Secp256k1 keypair from the seed
|
||||
use k256::ecdsa::{SigningKey, VerifyingKey, signature::hazmat::PrehashSigner};
|
||||
use k256::ecdsa::{SigningKey, VerifyingKey};
|
||||
|
||||
// Use the seed as the private key directly (32 bytes)
|
||||
let mut secret_key_bytes = [0u8; 32];
|
||||
@ -466,14 +465,15 @@ impl<S: KVStore> Vault<S> {
|
||||
Ok(sig.to_bytes().to_vec())
|
||||
}
|
||||
KeyType::Secp256k1 => {
|
||||
use k256::ecdsa::{signature::Signer, SigningKey};
|
||||
use k256::ecdsa::{signature::Signer, SigningKey, Signature};
|
||||
let arr: &[u8; 32] = key.private_key.as_slice().try_into().map_err(|_| {
|
||||
VaultError::Crypto("Invalid secp256k1 private key length".to_string())
|
||||
})?;
|
||||
let sk = SigningKey::from_bytes(arr.into())
|
||||
.map_err(|e| VaultError::Crypto(e.to_string()))?;
|
||||
let sig: k256::ecdsa::DerSignature = sk.sign(message);
|
||||
Ok(sig.to_vec())
|
||||
let sig: Signature = sk.sign(message);
|
||||
// Return compact signature (64 bytes) instead of DER format
|
||||
Ok(sig.to_bytes().to_vec())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -517,7 +517,11 @@ impl<S: KVStore> Vault<S> {
|
||||
use k256::ecdsa::{signature::Verifier, Signature, VerifyingKey};
|
||||
let pk = VerifyingKey::from_sec1_bytes(&key.public_key)
|
||||
.map_err(|e| VaultError::Crypto(e.to_string()))?;
|
||||
let sig = Signature::from_der(signature)
|
||||
// Use compact format (64 bytes) instead of DER
|
||||
let sig_array: &[u8; 64] = signature.try_into().map_err(|_| {
|
||||
VaultError::Crypto("Invalid secp256k1 signature length".to_string())
|
||||
})?;
|
||||
let sig = Signature::from_bytes(sig_array.into())
|
||||
.map_err(|e| VaultError::Crypto(e.to_string()))?;
|
||||
Ok(pk.verify(message, &sig).is_ok())
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ web-sys = { version = "0.3", features = ["console"] }
|
||||
js-sys = "0.3"
|
||||
kvstore = { path = "../kvstore" }
|
||||
hex = "0.4"
|
||||
base64 = "0.22"
|
||||
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
|
||||
gloo-utils = "0.1"
|
||||
|
||||
@ -23,6 +24,7 @@ wasm-bindgen-futures = "0.4"
|
||||
once_cell = "1.21"
|
||||
vault = { path = "../vault" }
|
||||
evm_client = { path = "../evm_client" }
|
||||
sigsocket_client = { path = "../sigsocket_client" }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
@ -26,6 +26,10 @@ pub use vault::session_singleton::SESSION_MANAGER;
|
||||
mod vault_bindings;
|
||||
pub use vault_bindings::*;
|
||||
|
||||
// Include the sigsocket module
|
||||
mod sigsocket;
|
||||
pub use sigsocket::*;
|
||||
|
||||
/// Initialize the scripting environment (must be called before run_rhai)
|
||||
#[wasm_bindgen]
|
||||
pub fn init_rhai_env() {
|
||||
|
168
wasm_app/src/sigsocket/connection.rs
Normal file
168
wasm_app/src/sigsocket/connection.rs
Normal file
@ -0,0 +1,168 @@
|
||||
//! SigSocket connection wrapper for WASM
|
||||
//!
|
||||
//! This module provides a WASM-bindgen compatible wrapper around the
|
||||
//! SigSocket client that can be used from JavaScript in the browser extension.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use sigsocket_client::{SigSocketClient, SignResponse};
|
||||
use crate::sigsocket::handler::JavaScriptSignHandler;
|
||||
|
||||
/// WASM-bindgen wrapper for SigSocket client
|
||||
///
|
||||
/// This provides a clean JavaScript API for the browser extension to:
|
||||
/// - Connect to SigSocket servers
|
||||
/// - Send responses to sign requests
|
||||
/// - Manage connection state
|
||||
#[wasm_bindgen]
|
||||
pub struct SigSocketConnection {
|
||||
client: Option<SigSocketClient>,
|
||||
connected: bool,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SigSocketConnection {
|
||||
/// Create a new SigSocket connection
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client: None,
|
||||
connected: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to a SigSocket server
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `server_url` - WebSocket server URL (e.g., "ws://localhost:8080/ws")
|
||||
/// * `public_key_hex` - Client's public key as hex string
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(())` - Successfully connected
|
||||
/// * `Err(error)` - Connection failed
|
||||
#[wasm_bindgen]
|
||||
pub async fn connect(&mut self, server_url: &str, public_key_hex: &str) -> Result<(), JsValue> {
|
||||
web_sys::console::log_1(&format!("SigSocketConnection::connect called with URL: {}", server_url).into());
|
||||
web_sys::console::log_1(&format!("Public key (first 16 chars): {}", &public_key_hex[..16]).into());
|
||||
|
||||
// Decode public key from hex
|
||||
let public_key = hex::decode(public_key_hex)
|
||||
.map_err(|e| JsValue::from_str(&format!("Invalid public key hex: {}", e)))?;
|
||||
|
||||
web_sys::console::log_1(&"Creating SigSocketClient...".into());
|
||||
|
||||
// Create client
|
||||
let mut client = SigSocketClient::new(server_url, public_key)
|
||||
.map_err(|e| JsValue::from_str(&format!("Failed to create client: {}", e)))?;
|
||||
|
||||
web_sys::console::log_1(&"SigSocketClient created, attempting connection...".into());
|
||||
|
||||
// Set up JavaScript handler
|
||||
client.set_sign_handler(JavaScriptSignHandler);
|
||||
|
||||
// Connect to server
|
||||
web_sys::console::log_1(&"Calling client.connect()...".into());
|
||||
client.connect().await
|
||||
.map_err(|e| {
|
||||
web_sys::console::error_1(&format!("Client connection failed: {}", e).into());
|
||||
JsValue::from_str(&format!("Failed to connect: {}", e))
|
||||
})?;
|
||||
|
||||
web_sys::console::log_1(&"Client connection successful!".into());
|
||||
|
||||
self.client = Some(client);
|
||||
self.connected = true;
|
||||
|
||||
web_sys::console::log_1(&"SigSocketConnection state updated to connected".into());
|
||||
|
||||
// Notify JavaScript of connection state change
|
||||
super::handler::on_connection_state_changed(true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a response to a sign request
|
||||
///
|
||||
/// This should be called by the extension after the user has approved
|
||||
/// a sign request and the message has been signed.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `request_id` - ID of the original request
|
||||
/// * `message_base64` - Original message (base64-encoded)
|
||||
/// * `signature_hex` - Signature as hex string
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(())` - Response sent successfully
|
||||
/// * `Err(error)` - Failed to send response
|
||||
#[wasm_bindgen]
|
||||
pub async fn send_response(&self, request_id: &str, message_base64: &str, signature_hex: &str) -> Result<(), JsValue> {
|
||||
let client = self.client.as_ref()
|
||||
.ok_or_else(|| JsValue::from_str("Not connected"))?;
|
||||
|
||||
// Decode signature from hex
|
||||
let signature = hex::decode(signature_hex)
|
||||
.map_err(|e| JsValue::from_str(&format!("Invalid signature hex: {}", e)))?;
|
||||
|
||||
// Create response
|
||||
let response = SignResponse::new(request_id, message_base64,
|
||||
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &signature));
|
||||
|
||||
// Send response
|
||||
client.send_sign_response(&response).await
|
||||
.map_err(|e| JsValue::from_str(&format!("Failed to send response: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a rejection for a sign request
|
||||
///
|
||||
/// This should be called when the user rejects a sign request.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `request_id` - ID of the request to reject
|
||||
/// * `reason` - Reason for rejection (optional)
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(())` - Rejection sent successfully
|
||||
/// * `Err(error)` - Failed to send rejection
|
||||
#[wasm_bindgen]
|
||||
pub async fn send_rejection(&self, request_id: &str, reason: &str) -> Result<(), JsValue> {
|
||||
// For now, we'll just log the rejection
|
||||
// In a full implementation, the server might support rejection messages
|
||||
web_sys::console::log_1(&format!("Sign request {} rejected: {}", request_id, reason).into());
|
||||
|
||||
// TODO: If the server supports rejection messages, send them here
|
||||
// For now, we just ignore the request (timeout on server side)
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Disconnect from the SigSocket server
|
||||
#[wasm_bindgen]
|
||||
pub fn disconnect(&mut self) {
|
||||
if let Some(_client) = self.client.take() {
|
||||
// Note: We can't await in a non-async function, so we'll just drop the client
|
||||
// The Drop implementation should handle cleanup
|
||||
self.connected = false;
|
||||
|
||||
// Notify JavaScript of connection state change
|
||||
super::handler::on_connection_state_changed(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if connected to the server
|
||||
#[wasm_bindgen]
|
||||
pub fn is_connected(&self) -> bool {
|
||||
// Check if we have a client and if it reports as connected
|
||||
if let Some(ref client) = self.client {
|
||||
client.is_connected()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SigSocketConnection {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
51
wasm_app/src/sigsocket/handler.rs
Normal file
51
wasm_app/src/sigsocket/handler.rs
Normal file
@ -0,0 +1,51 @@
|
||||
//! JavaScript bridge handler for SigSocket sign requests
|
||||
//!
|
||||
//! This module provides a sign request handler that delegates to JavaScript
|
||||
//! callbacks, allowing the browser extension to handle the actual signing
|
||||
//! and user approval flow.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use sigsocket_client::{SignRequest, SignRequestHandler, Result, SigSocketError};
|
||||
|
||||
/// JavaScript sign handler that delegates to extension
|
||||
///
|
||||
/// This handler receives sign requests from the SigSocket server and
|
||||
/// calls JavaScript callbacks to notify the extension. The extension
|
||||
/// handles the user approval flow and signing, then responds via
|
||||
/// the SigSocketConnection.send_response() method.
|
||||
pub struct JavaScriptSignHandler;
|
||||
|
||||
impl SignRequestHandler for JavaScriptSignHandler {
|
||||
fn handle_sign_request(&self, request: &SignRequest) -> Result<Vec<u8>> {
|
||||
// Call JavaScript callback to notify extension of incoming request
|
||||
on_sign_request_received(&request.id, &request.message);
|
||||
|
||||
// Return error - JavaScript handles response via send_response()
|
||||
// This is intentional as the signing happens asynchronously in the extension
|
||||
Err(SigSocketError::Other("Handled by JavaScript extension".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// External JavaScript functions that the extension must implement
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
/// Called when a sign request is received from the server
|
||||
///
|
||||
/// The extension should:
|
||||
/// 1. Store the request details
|
||||
/// 2. Show notification/badge to user
|
||||
/// 3. Handle user approval flow when popup is opened
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `request_id` - Unique identifier for the request
|
||||
/// * `message_base64` - Message to be signed (base64-encoded)
|
||||
#[wasm_bindgen(js_name = "onSignRequestReceived")]
|
||||
pub fn on_sign_request_received(request_id: &str, message_base64: &str);
|
||||
|
||||
/// Called when connection state changes
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `connected` - True if connected, false if disconnected
|
||||
#[wasm_bindgen(js_name = "onConnectionStateChanged")]
|
||||
pub fn on_connection_state_changed(connected: bool);
|
||||
}
|
11
wasm_app/src/sigsocket/mod.rs
Normal file
11
wasm_app/src/sigsocket/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
//! SigSocket integration module for WASM app
|
||||
//!
|
||||
//! This module provides a clean transport API for SigSocket communication
|
||||
//! that can be used by the browser extension. It handles connection management
|
||||
//! and delegates signing to the extension through JavaScript callbacks.
|
||||
|
||||
pub mod connection;
|
||||
pub mod handler;
|
||||
|
||||
pub use connection::SigSocketConnection;
|
||||
pub use handler::JavaScriptSignHandler;
|
@ -120,6 +120,41 @@ pub fn is_unlocked() -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the default public key for a workspace (keyspace)
|
||||
/// This returns the public key of the first keypair in the keyspace
|
||||
#[wasm_bindgen]
|
||||
pub async fn get_workspace_default_public_key(workspace_id: &str) -> Result<JsValue, JsValue> {
|
||||
// For now, workspace_id is the same as keyspace name
|
||||
// In a full implementation, you might have a mapping from workspace to keyspace
|
||||
|
||||
SESSION_MANAGER.with(|cell| {
|
||||
if let Some(session) = cell.borrow().as_ref() {
|
||||
if let Some(keyspace_name) = session.current_keyspace_name() {
|
||||
if keyspace_name == workspace_id {
|
||||
// Use the default_keypair method to get the first keypair
|
||||
if let Some(default_keypair) = session.default_keypair() {
|
||||
// Return the actual public key as hex
|
||||
let public_key_hex = hex::encode(&default_keypair.public_key);
|
||||
return Ok(JsValue::from_str(&public_key_hex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(JsValue::from_str("Workspace not found or no keypairs available"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the current unlocked public key as hex string
|
||||
#[wasm_bindgen]
|
||||
pub fn get_current_unlocked_public_key() -> Result<String, JsValue> {
|
||||
SESSION_MANAGER.with(|cell| {
|
||||
cell.borrow().as_ref()
|
||||
.and_then(|session| session.current_keypair_public_key())
|
||||
.map(|pk| hex::encode(pk.as_slice()))
|
||||
.ok_or_else(|| JsValue::from_str("No keypair selected or no keyspace unlocked"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get all keypairs from the current session
|
||||
/// Returns an array of keypair objects with id, type, and metadata
|
||||
// #[wasm_bindgen]
|
||||
@ -214,7 +249,7 @@ pub async fn add_keypair(
|
||||
Ok(JsValue::from_str(&key_id))
|
||||
}
|
||||
|
||||
/// Sign message with current session
|
||||
/// Sign message with current session (requires selected keypair)
|
||||
#[wasm_bindgen]
|
||||
pub async fn sign(message: &[u8]) -> Result<JsValue, JsValue> {
|
||||
{
|
||||
@ -235,6 +270,63 @@ pub async fn sign(message: &[u8]) -> Result<JsValue, JsValue> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sign message with default keypair (first keypair in keyspace) without changing session state
|
||||
#[wasm_bindgen]
|
||||
pub async fn sign_with_default_keypair(message: &[u8]) -> Result<JsValue, JsValue> {
|
||||
// Temporarily select the default keypair, sign, then restore the original selection
|
||||
let original_keypair = SESSION_MANAGER.with(|cell| {
|
||||
cell.borrow().as_ref()
|
||||
.and_then(|session| session.current_keypair())
|
||||
.map(|kp| kp.id.clone())
|
||||
});
|
||||
|
||||
// Select default keypair
|
||||
let select_result = SESSION_MANAGER.with(|cell| {
|
||||
let mut session_opt = cell.borrow_mut().take();
|
||||
if let Some(ref mut session) = session_opt {
|
||||
let result = session.select_default_keypair();
|
||||
*cell.borrow_mut() = Some(session_opt.take().unwrap());
|
||||
result.map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err("Session not initialized".to_string())
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(e) = select_result {
|
||||
return Err(JsValue::from_str(&format!("Failed to select default keypair: {e}")));
|
||||
}
|
||||
|
||||
// Sign with the default keypair
|
||||
let sign_result = {
|
||||
let session_ptr = SESSION_MANAGER.with(|cell| cell.borrow().as_ref().map(|s| s as *const _));
|
||||
let session: &vault::session::SessionManager<kvstore::wasm::WasmStore> = match session_ptr {
|
||||
Some(ptr) => unsafe { &*ptr },
|
||||
None => return Err(JsValue::from_str("Session not initialized")),
|
||||
};
|
||||
session.sign(message).await
|
||||
};
|
||||
|
||||
// Restore original keypair selection if there was one
|
||||
if let Some(original_id) = original_keypair {
|
||||
SESSION_MANAGER.with(|cell| {
|
||||
let mut session_opt = cell.borrow_mut().take();
|
||||
if let Some(ref mut session) = session_opt {
|
||||
let _ = session.select_keypair(&original_id); // Ignore errors here
|
||||
*cell.borrow_mut() = Some(session_opt.take().unwrap());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Return the signature result
|
||||
match sign_result {
|
||||
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}"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a signature with the current session's selected keypair
|
||||
#[wasm_bindgen]
|
||||
pub async fn verify(message: &[u8], signature: &str) -> Result<JsValue, JsValue> {
|
||||
|
Loading…
Reference in New Issue
Block a user