feat: Add SigSocket integration with WASM client and JavaScript bridge for sign requests

This commit is contained in:
Sameh Abouel-saad
2025-06-04 16:11:53 +03:00
parent 9f143ded9d
commit 4e1e707f85
8 changed files with 436 additions and 80 deletions

View File

@@ -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);
let intro_message = hex::encode(&public_key_clone);
web_sys::console::log_1(&format!("MAIN CONNECTION: Sending public key: {}", &intro_message[..16]).into());
if let Err(e) = ws_clone.send_with_str(&intro_message) {
web_sys::console::error_1(&format!("Failed to send introduction: {:?}", e).into());
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());
}
web_sys::console::log_1(&"Connected to sigsocket server".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;
}