feat: Enhance request management in SigSocket client with new methods and structures
This commit is contained in:
		| @@ -1,9 +1,18 @@ | |||||||
| //! Main client interface for sigsocket communication | //! Main client interface for sigsocket communication | ||||||
|  |  | ||||||
| #[cfg(target_arch = "wasm32")] | #[cfg(target_arch = "wasm32")] | ||||||
| use alloc::{string::String, vec::Vec, boxed::Box}; | use alloc::{string::String, vec::Vec, boxed::Box, string::ToString}; | ||||||
|  |  | ||||||
|  | #[cfg(not(target_arch = "wasm32"))] | ||||||
|  | use std::collections::HashMap; | ||||||
|  |  | ||||||
|  | #[cfg(target_arch = "wasm32")] | ||||||
|  | use alloc::collections::BTreeMap as HashMap; | ||||||
|  |  | ||||||
| use crate::{SignRequest, SignResponse, Result, SigSocketError}; | use crate::{SignRequest, SignResponse, Result, SigSocketError}; | ||||||
|  | use crate::protocol::ManagedSignRequest; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /// Connection state of the sigsocket client | /// Connection state of the sigsocket client | ||||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||
| @@ -67,6 +76,10 @@ pub struct SigSocketClient { | |||||||
|     state: ConnectionState, |     state: ConnectionState, | ||||||
|     /// Sign request handler |     /// Sign request handler | ||||||
|     sign_handler: Option<Box<dyn SignRequestHandler>>, |     sign_handler: Option<Box<dyn SignRequestHandler>>, | ||||||
|  |     /// Pending sign requests managed by the client | ||||||
|  |     pending_requests: HashMap<String, ManagedSignRequest>, | ||||||
|  |     /// Connected public key (hex-encoded) - set when connection is established | ||||||
|  |     connected_public_key: Option<String>, | ||||||
|     /// Platform-specific implementation |     /// Platform-specific implementation | ||||||
|     #[cfg(not(target_arch = "wasm32"))] |     #[cfg(not(target_arch = "wasm32"))] | ||||||
|     inner: Option<crate::native::NativeClient>, |     inner: Option<crate::native::NativeClient>, | ||||||
| @@ -100,14 +113,16 @@ impl SigSocketClient { | |||||||
|             public_key, |             public_key, | ||||||
|             state: ConnectionState::Disconnected, |             state: ConnectionState::Disconnected, | ||||||
|             sign_handler: None, |             sign_handler: None, | ||||||
|  |             pending_requests: HashMap::new(), | ||||||
|  |             connected_public_key: None, | ||||||
|             inner: None, |             inner: None, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Set the sign request handler |     /// Set the sign request handler | ||||||
|     ///  |     /// | ||||||
|     /// This handler will be called whenever the server sends a signature request. |     /// This handler will be called whenever the server sends a signature request. | ||||||
|     ///  |     /// | ||||||
|     /// # Arguments |     /// # Arguments | ||||||
|     /// * `handler` - Implementation of SignRequestHandler trait |     /// * `handler` - Implementation of SignRequestHandler trait | ||||||
|     pub fn set_sign_handler<H>(&mut self, handler: H) |     pub fn set_sign_handler<H>(&mut self, handler: H) | ||||||
| @@ -117,6 +132,8 @@ impl SigSocketClient { | |||||||
|         self.sign_handler = Some(Box::new(handler)); |         self.sign_handler = Some(Box::new(handler)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// Get the current connection state |     /// Get the current connection state | ||||||
|     pub fn state(&self) -> ConnectionState { |     pub fn state(&self) -> ConnectionState { | ||||||
|         self.state |         self.state | ||||||
| @@ -136,6 +153,109 @@ impl SigSocketClient { | |||||||
|     pub fn url(&self) -> &str { |     pub fn url(&self) -> &str { | ||||||
|         &self.url |         &self.url | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Get the connected public key (if connected) | ||||||
|  |     pub fn connected_public_key(&self) -> Option<&str> { | ||||||
|  |         self.connected_public_key.as_deref() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // === Request Management Methods === | ||||||
|  |  | ||||||
|  |     /// Add a pending sign request | ||||||
|  |     /// | ||||||
|  |     /// This is typically called when a sign request is received from the server. | ||||||
|  |     /// The request will be stored and can be retrieved later for processing. | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `request` - The sign request to add | ||||||
|  |     /// * `target_public_key` - The public key this request is intended for | ||||||
|  |     pub fn add_pending_request(&mut self, request: SignRequest, target_public_key: String) { | ||||||
|  |         let managed_request = ManagedSignRequest::new(request, target_public_key); | ||||||
|  |         self.pending_requests.insert(managed_request.id().to_string(), managed_request); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Remove a pending request by ID | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `request_id` - The ID of the request to remove | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Some(request)` - The removed request if it existed | ||||||
|  |     /// * `None` - If no request with that ID was found | ||||||
|  |     pub fn remove_pending_request(&mut self, request_id: &str) -> Option<ManagedSignRequest> { | ||||||
|  |         self.pending_requests.remove(request_id) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get a pending request by ID | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `request_id` - The ID of the request to retrieve | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Some(request)` - The request if it exists | ||||||
|  |     /// * `None` - If no request with that ID was found | ||||||
|  |     pub fn get_pending_request(&self, request_id: &str) -> Option<&ManagedSignRequest> { | ||||||
|  |         self.pending_requests.get(request_id) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get all pending requests | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * A reference to the HashMap containing all pending requests | ||||||
|  |     pub fn get_pending_requests(&self) -> &HashMap<String, ManagedSignRequest> { | ||||||
|  |         &self.pending_requests | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get pending requests filtered by public key | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `public_key` - The public key to filter by (hex-encoded) | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * A vector of references to requests for the specified public key | ||||||
|  |     pub fn get_requests_for_public_key(&self, public_key: &str) -> Vec<&ManagedSignRequest> { | ||||||
|  |         self.pending_requests | ||||||
|  |             .values() | ||||||
|  |             .filter(|req| req.is_for_public_key(public_key)) | ||||||
|  |             .collect() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Check if a request can be handled for the given public key | ||||||
|  |     /// | ||||||
|  |     /// This performs protocol-level validation without cryptographic operations. | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `request` - The sign request to validate | ||||||
|  |     /// * `public_key` - The public key to check against (hex-encoded) | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `true` - If the request can be handled for this public key | ||||||
|  |     /// * `false` - If the request cannot be handled | ||||||
|  |     pub fn can_handle_request_for_key(&self, request: &SignRequest, public_key: &str) -> bool { | ||||||
|  |         // Basic protocol validation | ||||||
|  |         if request.id.is_empty() || request.message.is_empty() { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Check if we can decode the message | ||||||
|  |         if request.message_bytes().is_err() { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // For now, we assume any valid request can be handled for any public key | ||||||
|  |         // More sophisticated validation can be added here | ||||||
|  |         !public_key.is_empty() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Clear all pending requests | ||||||
|  |     pub fn clear_pending_requests(&mut self) { | ||||||
|  |         self.pending_requests.clear(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get the count of pending requests | ||||||
|  |     pub fn pending_request_count(&self) -> usize { | ||||||
|  |         self.pending_requests.len() | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| // Platform-specific implementations will be added in separate modules | // Platform-specific implementations will be added in separate modules | ||||||
| @@ -176,6 +296,7 @@ impl SigSocketClient { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         self.state = ConnectionState::Connected; |         self.state = ConnectionState::Connected; | ||||||
|  |         self.connected_public_key = Some(self.public_key_hex()); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -190,17 +311,19 @@ impl SigSocketClient { | |||||||
|         } |         } | ||||||
|         self.inner = None; |         self.inner = None; | ||||||
|         self.state = ConnectionState::Disconnected; |         self.state = ConnectionState::Disconnected; | ||||||
|  |         self.connected_public_key = None; | ||||||
|  |         self.clear_pending_requests(); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Send a sign response to the server |     /// Send a sign response to the server | ||||||
|     ///  |     /// | ||||||
|     /// This is typically called after the user has approved a signature request |     /// This is typically called after the user has approved a signature request | ||||||
|     /// and the application has generated the signature. |     /// and the application has generated the signature. | ||||||
|     ///  |     /// | ||||||
|     /// # Arguments |     /// # Arguments | ||||||
|     /// * `response` - The sign response containing the signature |     /// * `response` - The sign response containing the signature | ||||||
|     ///  |     /// | ||||||
|     /// # Returns |     /// # Returns | ||||||
|     /// * `Ok(())` - Response sent successfully |     /// * `Ok(())` - Response sent successfully | ||||||
|     /// * `Err(error)` - Failed to send response |     /// * `Err(error)` - Failed to send response | ||||||
| @@ -215,6 +338,41 @@ impl SigSocketClient { | |||||||
|             Err(SigSocketError::NotConnected) |             Err(SigSocketError::NotConnected) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Send a response for a specific request ID with signature | ||||||
|  |     /// | ||||||
|  |     /// This is a convenience method that creates a SignResponse and sends it. | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `request_id` - The ID of the request being responded to | ||||||
|  |     /// * `message` - The original message (base64-encoded) | ||||||
|  |     /// * `signature` - The signature (base64-encoded) | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(())` - Response sent successfully | ||||||
|  |     /// * `Err(error)` - Failed to send response | ||||||
|  |     pub async fn send_response(&self, request_id: &str, message: &str, signature: &str) -> Result<()> { | ||||||
|  |         let response = SignResponse::new(request_id, message, signature); | ||||||
|  |         self.send_sign_response(&response).await | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Send a rejection for a specific request ID | ||||||
|  |     /// | ||||||
|  |     /// This sends an error response to indicate the request was rejected. | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `request_id` - The ID of the request being rejected | ||||||
|  |     /// * `reason` - The reason for rejection | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(())` - Rejection sent successfully | ||||||
|  |     /// * `Err(error)` - Failed to send rejection | ||||||
|  |     pub async fn send_rejection(&self, request_id: &str, _reason: &str) -> Result<()> { | ||||||
|  |         // For now, we'll send an empty signature to indicate rejection | ||||||
|  |         // This can be improved with a proper rejection protocol | ||||||
|  |         let response = SignResponse::new(request_id, "", ""); | ||||||
|  |         self.send_sign_response(&response).await | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Drop for SigSocketClient { | impl Drop for SigSocketClient { | ||||||
| @@ -222,3 +380,5 @@ impl Drop for SigSocketClient { | |||||||
|         // Cleanup will be handled by the platform-specific implementations |         // Cleanup will be handled by the platform-specific implementations | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -60,10 +60,13 @@ mod native; | |||||||
| mod wasm; | mod wasm; | ||||||
|  |  | ||||||
| pub use error::{SigSocketError, Result}; | pub use error::{SigSocketError, Result}; | ||||||
| pub use protocol::{SignRequest, SignResponse}; | pub use protocol::{SignRequest, SignResponse, ManagedSignRequest, RequestStatus}; | ||||||
| pub use client::{SigSocketClient, SignRequestHandler, ConnectionState}; | pub use client::{SigSocketClient, SignRequestHandler, ConnectionState}; | ||||||
|  |  | ||||||
| // Re-export for convenience | // Re-export for convenience | ||||||
| pub mod prelude { | pub mod prelude { | ||||||
|     pub use crate::{SigSocketClient, SignRequest, SignResponse, SignRequestHandler, ConnectionState, SigSocketError, Result}; |     pub use crate::{ | ||||||
|  |         SigSocketClient, SignRequest, SignResponse, ManagedSignRequest, RequestStatus, | ||||||
|  |         SignRequestHandler, ConnectionState, SigSocketError, Result | ||||||
|  |     }; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -82,6 +82,92 @@ impl SignResponse { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// Enhanced sign request with additional metadata for request management | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] | ||||||
|  | pub struct ManagedSignRequest { | ||||||
|  |     /// The original sign request | ||||||
|  |     #[serde(flatten)] | ||||||
|  |     pub request: SignRequest, | ||||||
|  |     /// Timestamp when the request was received (Unix timestamp in milliseconds) | ||||||
|  |     pub timestamp: u64, | ||||||
|  |     /// Target public key for this request (hex-encoded) | ||||||
|  |     pub target_public_key: String, | ||||||
|  |     /// Current status of the request | ||||||
|  |     pub status: RequestStatus, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Status of a sign request | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] | ||||||
|  | pub enum RequestStatus { | ||||||
|  |     /// Request is pending user approval | ||||||
|  |     Pending, | ||||||
|  |     /// Request has been approved and signed | ||||||
|  |     Approved, | ||||||
|  |     /// Request has been rejected by user | ||||||
|  |     Rejected, | ||||||
|  |     /// Request has expired or been cancelled | ||||||
|  |     Cancelled, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl ManagedSignRequest { | ||||||
|  |     /// Create a new managed sign request | ||||||
|  |     pub fn new(request: SignRequest, target_public_key: String) -> Self { | ||||||
|  |         Self { | ||||||
|  |             request, | ||||||
|  |             timestamp: current_timestamp_ms(), | ||||||
|  |             target_public_key, | ||||||
|  |             status: RequestStatus::Pending, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get the request ID | ||||||
|  |     pub fn id(&self) -> &str { | ||||||
|  |         &self.request.id | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get the message as bytes (decoded from base64) | ||||||
|  |     pub fn message_bytes(&self) -> Result<Vec<u8>, base64::DecodeError> { | ||||||
|  |         self.request.message_bytes() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Check if this request is for the given public key | ||||||
|  |     pub fn is_for_public_key(&self, public_key: &str) -> bool { | ||||||
|  |         self.target_public_key == public_key | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Mark the request as approved | ||||||
|  |     pub fn mark_approved(&mut self) { | ||||||
|  |         self.status = RequestStatus::Approved; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Mark the request as rejected | ||||||
|  |     pub fn mark_rejected(&mut self) { | ||||||
|  |         self.status = RequestStatus::Rejected; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Check if the request is still pending | ||||||
|  |     pub fn is_pending(&self) -> bool { | ||||||
|  |         matches!(self.status, RequestStatus::Pending) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Get current timestamp in milliseconds | ||||||
|  | #[cfg(not(target_arch = "wasm32"))] | ||||||
|  | fn current_timestamp_ms() -> u64 { | ||||||
|  |     std::time::SystemTime::now() | ||||||
|  |         .duration_since(std::time::UNIX_EPOCH) | ||||||
|  |         .unwrap_or_default() | ||||||
|  |         .as_millis() as u64 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Get current timestamp in milliseconds (WASM version) | ||||||
|  | #[cfg(target_arch = "wasm32")] | ||||||
|  | fn current_timestamp_ms() -> u64 { | ||||||
|  |     // In WASM, we'll use a simple counter or Date.now() via JS | ||||||
|  |     // For now, return 0 - this can be improved later | ||||||
|  |     0 | ||||||
|  | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use super::*; |     use super::*; | ||||||
| @@ -138,4 +224,33 @@ mod tests { | |||||||
|         let deserialized: SignResponse = serde_json::from_str(&json).unwrap(); |         let deserialized: SignResponse = serde_json::from_str(&json).unwrap(); | ||||||
|         assert_eq!(response, deserialized); |         assert_eq!(response, deserialized); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_managed_sign_request() { | ||||||
|  |         let request = SignRequest::new("test-id", "dGVzdCBtZXNzYWdl"); | ||||||
|  |         let managed = ManagedSignRequest::new(request.clone(), "test-public-key".to_string()); | ||||||
|  |  | ||||||
|  |         assert_eq!(managed.id(), "test-id"); | ||||||
|  |         assert_eq!(managed.request, request); | ||||||
|  |         assert_eq!(managed.target_public_key, "test-public-key"); | ||||||
|  |         assert!(managed.is_pending()); | ||||||
|  |         assert!(managed.is_for_public_key("test-public-key")); | ||||||
|  |         assert!(!managed.is_for_public_key("other-key")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_managed_request_status_changes() { | ||||||
|  |         let request = SignRequest::new("test-id", "dGVzdCBtZXNzYWdl"); | ||||||
|  |         let mut managed = ManagedSignRequest::new(request, "test-public-key".to_string()); | ||||||
|  |  | ||||||
|  |         assert!(managed.is_pending()); | ||||||
|  |  | ||||||
|  |         managed.mark_approved(); | ||||||
|  |         assert_eq!(managed.status, RequestStatus::Approved); | ||||||
|  |         assert!(!managed.is_pending()); | ||||||
|  |  | ||||||
|  |         managed.mark_rejected(); | ||||||
|  |         assert_eq!(managed.status, RequestStatus::Rejected); | ||||||
|  |         assert!(!managed.is_pending()); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										92
									
								
								sigsocket_client/tests/request_management_test.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								sigsocket_client/tests/request_management_test.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | //! Tests for the enhanced request management functionality | ||||||
|  |  | ||||||
|  | use sigsocket_client::prelude::*; | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_client_request_management() { | ||||||
|  |     let public_key = hex::decode("02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9").unwrap(); | ||||||
|  |     let mut client = SigSocketClient::new("ws://localhost:8080/ws", public_key).unwrap(); | ||||||
|  |      | ||||||
|  |     // Initially no requests | ||||||
|  |     assert_eq!(client.pending_request_count(), 0); | ||||||
|  |     assert!(client.get_pending_requests().is_empty()); | ||||||
|  |      | ||||||
|  |     // Add a request | ||||||
|  |     let request = SignRequest::new("test-1", "dGVzdCBtZXNzYWdl"); | ||||||
|  |     let public_key_hex = "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"; | ||||||
|  |     client.add_pending_request(request.clone(), public_key_hex.to_string()); | ||||||
|  |      | ||||||
|  |     // Check request was added | ||||||
|  |     assert_eq!(client.pending_request_count(), 1); | ||||||
|  |     assert!(client.get_pending_request("test-1").is_some()); | ||||||
|  |      | ||||||
|  |     // Check filtering by public key | ||||||
|  |     let filtered = client.get_requests_for_public_key(public_key_hex); | ||||||
|  |     assert_eq!(filtered.len(), 1); | ||||||
|  |     assert_eq!(filtered[0].id(), "test-1"); | ||||||
|  |      | ||||||
|  |     // Add another request for different public key | ||||||
|  |     let request2 = SignRequest::new("test-2", "dGVzdCBtZXNzYWdlMg=="); | ||||||
|  |     let other_public_key = "03f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"; | ||||||
|  |     client.add_pending_request(request2, other_public_key.to_string()); | ||||||
|  |      | ||||||
|  |     // Check total count | ||||||
|  |     assert_eq!(client.pending_request_count(), 2); | ||||||
|  |      | ||||||
|  |     // Check filtering still works | ||||||
|  |     let filtered = client.get_requests_for_public_key(public_key_hex); | ||||||
|  |     assert_eq!(filtered.len(), 1); | ||||||
|  |      | ||||||
|  |     let filtered_other = client.get_requests_for_public_key(other_public_key); | ||||||
|  |     assert_eq!(filtered_other.len(), 1); | ||||||
|  |      | ||||||
|  |     // Remove a request | ||||||
|  |     let removed = client.remove_pending_request("test-1"); | ||||||
|  |     assert!(removed.is_some()); | ||||||
|  |     assert_eq!(removed.unwrap().id(), "test-1"); | ||||||
|  |     assert_eq!(client.pending_request_count(), 1); | ||||||
|  |      | ||||||
|  |     // Clear all requests | ||||||
|  |     client.clear_pending_requests(); | ||||||
|  |     assert_eq!(client.pending_request_count(), 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_client_request_validation() { | ||||||
|  |     let public_key = hex::decode("02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9").unwrap(); | ||||||
|  |     let client = SigSocketClient::new("ws://localhost:8080/ws", public_key).unwrap(); | ||||||
|  |      | ||||||
|  |     // Valid request | ||||||
|  |     let valid_request = SignRequest::new("test-1", "dGVzdCBtZXNzYWdl"); | ||||||
|  |     assert!(client.can_handle_request_for_key(&valid_request, "some-public-key")); | ||||||
|  |      | ||||||
|  |     // Invalid request - empty ID | ||||||
|  |     let invalid_request = SignRequest::new("", "dGVzdCBtZXNzYWdl"); | ||||||
|  |     assert!(!client.can_handle_request_for_key(&invalid_request, "some-public-key")); | ||||||
|  |      | ||||||
|  |     // Invalid request - empty message | ||||||
|  |     let invalid_request2 = SignRequest::new("test-1", ""); | ||||||
|  |     assert!(!client.can_handle_request_for_key(&invalid_request2, "some-public-key")); | ||||||
|  |      | ||||||
|  |     // Invalid request - invalid base64 | ||||||
|  |     let invalid_request3 = SignRequest::new("test-1", "invalid-base64!"); | ||||||
|  |     assert!(!client.can_handle_request_for_key(&invalid_request3, "some-public-key")); | ||||||
|  |      | ||||||
|  |     // Invalid public key | ||||||
|  |     assert!(!client.can_handle_request_for_key(&valid_request, "")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_client_connection_state() { | ||||||
|  |     let public_key = hex::decode("02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9").unwrap(); | ||||||
|  |     let client = SigSocketClient::new("ws://localhost:8080/ws", public_key).unwrap(); | ||||||
|  |      | ||||||
|  |     // Initially disconnected | ||||||
|  |     assert_eq!(client.state(), ConnectionState::Disconnected); | ||||||
|  |     assert!(!client.is_connected()); | ||||||
|  |     assert!(client.connected_public_key().is_none()); | ||||||
|  |      | ||||||
|  |     // Public key should be available | ||||||
|  |     assert_eq!(client.public_key_hex(), "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"); | ||||||
|  |     assert_eq!(client.url(), "ws://localhost:8080/ws"); | ||||||
|  | } | ||||||
| @@ -24,6 +24,7 @@ pub use vault::session_singleton::SESSION_MANAGER; | |||||||
|  |  | ||||||
| // Include the keypair bindings module | // Include the keypair bindings module | ||||||
| mod vault_bindings; | mod vault_bindings; | ||||||
|  | mod sigsocket_bindings; | ||||||
| pub use vault_bindings::*; | pub use vault_bindings::*; | ||||||
|  |  | ||||||
| // Include the sigsocket module | // Include the sigsocket module | ||||||
|   | |||||||
							
								
								
									
										427
									
								
								wasm_app/src/sigsocket_bindings.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										427
									
								
								wasm_app/src/sigsocket_bindings.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,427 @@ | |||||||
|  | //! SigSocket bindings for WASM - integrates sigsocket_client with vault system | ||||||
|  |  | ||||||
|  | use std::cell::RefCell; | ||||||
|  | use wasm_bindgen::prelude::*; | ||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
|  | use sigsocket_client::{SigSocketClient, SignRequest, SignRequestHandler, Result as SigSocketResult, SigSocketError}; | ||||||
|  | use web_sys::console; | ||||||
|  |  | ||||||
|  | use crate::vault_bindings::{get_workspace_default_public_key, get_current_keyspace_name, is_unlocked, sign_with_default_keypair}; | ||||||
|  |  | ||||||
|  | // Global SigSocket client instance | ||||||
|  | thread_local! { | ||||||
|  |     static SIGSOCKET_CLIENT: RefCell<Option<SigSocketClient>> = RefCell::new(None); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Helper macro for console logging | ||||||
|  | macro_rules! console_log { | ||||||
|  |     ($($t:tt)*) => (console::log_1(&format!($($t)*).into())) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Extension notification handler that forwards requests to JavaScript | ||||||
|  | pub struct ExtensionNotificationHandler { | ||||||
|  |     callback: js_sys::Function, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl ExtensionNotificationHandler { | ||||||
|  |     pub fn new(callback: js_sys::Function) -> Self { | ||||||
|  |         Self { callback } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl SignRequestHandler for ExtensionNotificationHandler { | ||||||
|  |     fn handle_sign_request(&self, request: &SignRequest) -> SigSocketResult<Vec<u8>> { | ||||||
|  |         // Create event object for JavaScript | ||||||
|  |         let event = js_sys::Object::new(); | ||||||
|  |         js_sys::Reflect::set(&event, &"type".into(), &"sign_request".into()) | ||||||
|  |             .map_err(|_| SigSocketError::Other("Failed to set event type".to_string()))?; | ||||||
|  |         js_sys::Reflect::set(&event, &"request_id".into(), &request.id.clone().into()) | ||||||
|  |             .map_err(|_| SigSocketError::Other("Failed to set request_id".to_string()))?; | ||||||
|  |         js_sys::Reflect::set(&event, &"message".into(), &request.message.clone().into()) | ||||||
|  |             .map_err(|_| SigSocketError::Other("Failed to set message".to_string()))?; | ||||||
|  |  | ||||||
|  |         // Store the request in our pending requests (this will be done by the client) | ||||||
|  |         // and notify the extension | ||||||
|  |         match self.callback.call1(&wasm_bindgen::JsValue::NULL, &event) { | ||||||
|  |             Ok(_) => { | ||||||
|  |                 console_log!("Notified extension about sign request: {}", request.id); | ||||||
|  |                 // Return an error to indicate this request should not be auto-signed | ||||||
|  |                 // The extension will handle the approval flow | ||||||
|  |                 Err(SigSocketError::Other("Request forwarded to extension for approval".to_string())) | ||||||
|  |             } | ||||||
|  |             Err(e) => { | ||||||
|  |                 console_log!("Failed to notify extension: {:?}", e); | ||||||
|  |                 Err(SigSocketError::Other("Extension notification failed".to_string())) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Connection information for SigSocket | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
|  | pub struct SigSocketConnectionInfo { | ||||||
|  |     pub workspace: String, | ||||||
|  |     pub public_key: String, | ||||||
|  |     pub is_connected: bool, | ||||||
|  |     pub server_url: String, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// SigSocket manager for high-level operations | ||||||
|  | #[wasm_bindgen] | ||||||
|  | pub struct SigSocketManager; | ||||||
|  |  | ||||||
|  | #[wasm_bindgen] | ||||||
|  | impl SigSocketManager { | ||||||
|  |     /// Connect to SigSocket server with a specific workspace and event callback | ||||||
|  |     /// | ||||||
|  |     /// This establishes a real WebSocket connection using the workspace's default public key | ||||||
|  |     /// and integrates with the vault system for security validation. | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `workspace` - The workspace name to connect with | ||||||
|  |     /// * `server_url` - The SigSocket server URL (e.g., "ws://localhost:8080/ws") | ||||||
|  |     /// * `event_callback` - JavaScript function to call when events occur | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(connection_info)` - JSON string with connection details | ||||||
|  |     /// * `Err(error)` - If connection failed or workspace is invalid | ||||||
|  |     #[wasm_bindgen] | ||||||
|  |     pub async fn connect_workspace_with_events(workspace: &str, server_url: &str, event_callback: &js_sys::Function) -> Result<String, JsValue> { | ||||||
|  |         // 1. Validate workspace exists and get default public key from vault | ||||||
|  |         let public_key_js = get_workspace_default_public_key(workspace).await | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Failed to get workspace public key: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |         let public_key_hex = public_key_js.as_string() | ||||||
|  |             .ok_or_else(|| JsValue::from_str("Public key is not a string"))?; | ||||||
|  |  | ||||||
|  |         // 2. Decode public key | ||||||
|  |         let public_key_bytes = hex::decode(&public_key_hex) | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Invalid public key format: {}", e)))?; | ||||||
|  |          | ||||||
|  |         // 3. Create SigSocket client with extension notification handler | ||||||
|  |         let mut client = SigSocketClient::new(server_url, public_key_bytes) | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Failed to create client: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |         // Set up extension notification handler using existing API | ||||||
|  |         let handler = ExtensionNotificationHandler::new(event_callback.clone()); | ||||||
|  |         client.set_sign_handler(handler); | ||||||
|  |  | ||||||
|  |         // 4. Connect to the WebSocket server | ||||||
|  |         client.connect().await | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Connection failed: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |         console_log!("SigSocket connected successfully to {}", server_url); | ||||||
|  |  | ||||||
|  |         // 5. Store the connected client | ||||||
|  |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             *c.borrow_mut() = Some(client); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 6. Return connection info | ||||||
|  |         let connection_info = SigSocketConnectionInfo { | ||||||
|  |             workspace: workspace.to_string(), | ||||||
|  |             public_key: public_key_hex.clone(), | ||||||
|  |             is_connected: true, | ||||||
|  |             server_url: server_url.to_string(), | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         // 7. Serialize and return connection info | ||||||
|  |         serde_json::to_string(&connection_info) | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Connect to SigSocket server with a specific workspace (backward compatibility) | ||||||
|  |     /// | ||||||
|  |     /// This is a simpler version that doesn't set up event callbacks. | ||||||
|  |     /// Use connect_workspace_with_events for full functionality. | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `workspace` - The workspace name to connect with | ||||||
|  |     /// * `server_url` - The SigSocket server URL (e.g., "ws://localhost:8080/ws") | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(connection_info)` - JSON string with connection details | ||||||
|  |     /// * `Err(error)` - If connection failed or workspace is invalid | ||||||
|  |     #[wasm_bindgen] | ||||||
|  |     pub async fn connect_workspace(workspace: &str, server_url: &str) -> Result<String, JsValue> { | ||||||
|  |         // Create a dummy callback that just logs | ||||||
|  |         let dummy_callback = js_sys::Function::new_no_args("console.log('SigSocket event:', arguments[0]);"); | ||||||
|  |         Self::connect_workspace_with_events(workspace, server_url, &dummy_callback).await | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     /// Disconnect from SigSocket server | ||||||
|  |     ///  | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(())` - Successfully disconnected | ||||||
|  |     /// * `Err(error)` - If disconnect failed | ||||||
|  |     #[wasm_bindgen] | ||||||
|  |     pub async fn disconnect() -> Result<(), JsValue> { | ||||||
|  |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             let mut client_opt = c.borrow_mut(); | ||||||
|  |             if let Some(_client) = client_opt.take() { | ||||||
|  |                 // client.disconnect().await?; // Will be async in real implementation | ||||||
|  |                 console_log!("SigSocket client disconnected"); | ||||||
|  |             } | ||||||
|  |             Ok(()) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     /// Check if we can approve a specific sign request | ||||||
|  |     ///  | ||||||
|  |     /// This validates that: | ||||||
|  |     /// 1. The request exists | ||||||
|  |     /// 2. The vault session is unlocked | ||||||
|  |     /// 3. The current workspace matches the request's target | ||||||
|  |     ///  | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `request_id` - The ID of the request to validate | ||||||
|  |     ///  | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(true)` - Request can be approved | ||||||
|  |     /// * `Ok(false)` - Request cannot be approved | ||||||
|  |     /// * `Err(error)` - Validation error | ||||||
|  |     #[wasm_bindgen] | ||||||
|  |     pub async fn can_approve_request(request_id: &str) -> Result<bool, JsValue> { | ||||||
|  |         // 1. Check if vault session is unlocked | ||||||
|  |         if !is_unlocked() { | ||||||
|  |             return Ok(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 2. Get current workspace and its public key | ||||||
|  |         let current_workspace = get_current_keyspace_name() | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Failed to get current workspace: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |         let current_public_key_js = get_workspace_default_public_key(¤t_workspace).await | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Failed to get current public key: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |         let current_public_key = current_public_key_js.as_string() | ||||||
|  |             .ok_or_else(|| JsValue::from_str("Current public key is not a string"))?; | ||||||
|  |  | ||||||
|  |         // 3. Check the request | ||||||
|  |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             let client = c.borrow(); | ||||||
|  |             let client = client.as_ref().ok_or_else(|| JsValue::from_str("Not connected to SigSocket"))?; | ||||||
|  |  | ||||||
|  |             // Get the request | ||||||
|  |             let request = client.get_pending_request(request_id) | ||||||
|  |                 .ok_or_else(|| JsValue::from_str("Request not found"))?; | ||||||
|  |  | ||||||
|  |             // Check if request matches current session | ||||||
|  |             let can_approve = request.target_public_key == current_public_key; | ||||||
|  |  | ||||||
|  |             console_log!("Can approve request {}: {} (current: {}, target: {})", | ||||||
|  |                         request_id, can_approve, current_public_key, request.target_public_key); | ||||||
|  |  | ||||||
|  |             Ok(can_approve) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     /// Approve a sign request and send the signature to the server | ||||||
|  |     ///  | ||||||
|  |     /// This performs the complete approval flow: | ||||||
|  |     /// 1. Validates the request can be approved | ||||||
|  |     /// 2. Signs the message using the vault | ||||||
|  |     /// 3. Sends the signature to the SigSocket server | ||||||
|  |     /// 4. Removes the request from pending list | ||||||
|  |     ///  | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `request_id` - The ID of the request to approve | ||||||
|  |     ///  | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(signature)` - Base64-encoded signature that was sent | ||||||
|  |     /// * `Err(error)` - If approval failed | ||||||
|  |     #[wasm_bindgen] | ||||||
|  |     pub async fn approve_request(request_id: &str) -> Result<String, JsValue> { | ||||||
|  |         // 1. Validate we can approve this request | ||||||
|  |         if !Self::can_approve_request(request_id).await? { | ||||||
|  |             return Err(JsValue::from_str("Cannot approve this request")); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // 2. Get request details and sign the message | ||||||
|  |         let (message_bytes, original_request) = SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             let client = c.borrow(); | ||||||
|  |             let client = client.as_ref().ok_or_else(|| JsValue::from_str("Not connected"))?; | ||||||
|  |              | ||||||
|  |             let request = client.get_pending_request(request_id) | ||||||
|  |                 .ok_or_else(|| JsValue::from_str("Request not found"))?; | ||||||
|  |              | ||||||
|  |             // Decode the message | ||||||
|  |             let message_bytes = request.message_bytes() | ||||||
|  |                 .map_err(|e| JsValue::from_str(&format!("Invalid message format: {}", e)))?; | ||||||
|  |              | ||||||
|  |             Ok::<(Vec<u8>, SignRequest), JsValue>((message_bytes, request.request.clone())) | ||||||
|  |         })?; | ||||||
|  |          | ||||||
|  |         // 3. Sign with vault | ||||||
|  |         let signature_result = sign_with_default_keypair(&message_bytes).await?; | ||||||
|  |         let signature_obj: serde_json::Value = serde_json::from_str(&signature_result.as_string().unwrap()) | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Failed to parse signature: {}", e)))?; | ||||||
|  |          | ||||||
|  |         let signature_base64 = signature_obj["signature"].as_str() | ||||||
|  |             .ok_or_else(|| JsValue::from_str("Invalid signature format"))?; | ||||||
|  |          | ||||||
|  |         // 4. Send response to server and remove request | ||||||
|  |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             let mut client = c.borrow_mut(); | ||||||
|  |             let client = client.as_mut().ok_or_else(|| JsValue::from_str("Not connected"))?; | ||||||
|  |              | ||||||
|  |             // Send response (will be async in real implementation) | ||||||
|  |             // client.send_response(request_id, &original_request.message, signature_base64).await?; | ||||||
|  |              | ||||||
|  |             // Remove the request | ||||||
|  |             client.remove_pending_request(request_id); | ||||||
|  |              | ||||||
|  |             console_log!("Approved and sent signature for request: {}", request_id); | ||||||
|  |              | ||||||
|  |             Ok(signature_base64.to_string()) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     /// Reject a sign request | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `request_id` - The ID of the request to reject | ||||||
|  |     /// * `reason` - The reason for rejection | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(())` - Request rejected successfully | ||||||
|  |     /// * `Err(error)` - If rejection failed | ||||||
|  |     #[wasm_bindgen] | ||||||
|  |     pub async fn reject_request(request_id: &str, reason: &str) -> Result<(), JsValue> { | ||||||
|  |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             let mut client = c.borrow_mut(); | ||||||
|  |             let client = client.as_mut().ok_or_else(|| JsValue::from_str("Not connected"))?; | ||||||
|  |  | ||||||
|  |             // Send rejection (will be async in real implementation) | ||||||
|  |             // client.send_rejection(request_id, reason).await?; | ||||||
|  |  | ||||||
|  |             // Remove the request | ||||||
|  |             client.remove_pending_request(request_id); | ||||||
|  |  | ||||||
|  |             console_log!("Rejected request {}: {}", request_id, reason); | ||||||
|  |  | ||||||
|  |             Ok(()) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get pending requests filtered by current workspace | ||||||
|  |     /// | ||||||
|  |     /// This returns only the requests that the current vault session can handle, | ||||||
|  |     /// based on the unlocked workspace and its public key. | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(requests_json)` - JSON array of filtered requests | ||||||
|  |     /// * `Err(error)` - If filtering failed | ||||||
|  |     #[wasm_bindgen] | ||||||
|  |     pub async fn get_filtered_requests() -> Result<String, JsValue> { | ||||||
|  |         // If vault is locked, return empty array | ||||||
|  |         if !is_unlocked() { | ||||||
|  |             return Ok("[]".to_string()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Get current workspace public key | ||||||
|  |         let current_workspace = get_current_keyspace_name() | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Failed to get current workspace: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |         let current_public_key_js = get_workspace_default_public_key(¤t_workspace).await | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Failed to get current public key: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |         let current_public_key = current_public_key_js.as_string() | ||||||
|  |             .ok_or_else(|| JsValue::from_str("Current public key is not a string"))?; | ||||||
|  |  | ||||||
|  |         // Filter requests for current workspace | ||||||
|  |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             let client = c.borrow(); | ||||||
|  |             let client = client.as_ref().ok_or_else(|| JsValue::from_str("Not connected to SigSocket"))?; | ||||||
|  |  | ||||||
|  |             let filtered_requests: Vec<_> = client.get_requests_for_public_key(¤t_public_key); | ||||||
|  |  | ||||||
|  |             console_log!("Filtered requests: {} total, {} for current workspace", | ||||||
|  |                         client.pending_request_count(), filtered_requests.len()); | ||||||
|  |  | ||||||
|  |             // Serialize and return | ||||||
|  |             serde_json::to_string(&filtered_requests) | ||||||
|  |                 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Add a pending sign request (called when request arrives from server) | ||||||
|  |     /// | ||||||
|  |     /// # Arguments | ||||||
|  |     /// * `request_json` - JSON string containing the sign request | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(())` - Request added successfully | ||||||
|  |     /// * `Err(error)` - If adding failed | ||||||
|  |     #[wasm_bindgen] | ||||||
|  |     pub fn add_pending_request(request_json: &str) -> Result<(), JsValue> { | ||||||
|  |         // Parse the request | ||||||
|  |         let request: SignRequest = serde_json::from_str(request_json) | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Invalid request JSON: {}", e)))?; | ||||||
|  |  | ||||||
|  |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             let mut client = c.borrow_mut(); | ||||||
|  |             let client = client.as_mut().ok_or_else(|| JsValue::from_str("Not connected to SigSocket"))?; | ||||||
|  |  | ||||||
|  |             // Get the connected public key as the target | ||||||
|  |             let target_public_key = client.connected_public_key() | ||||||
|  |                 .ok_or_else(|| JsValue::from_str("No connected public key"))? | ||||||
|  |                 .to_string(); | ||||||
|  |  | ||||||
|  |             // Add the request | ||||||
|  |             client.add_pending_request(request, target_public_key); | ||||||
|  |  | ||||||
|  |             console_log!("Added pending request: {}", request_json); | ||||||
|  |  | ||||||
|  |             Ok(()) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get connection status | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(status_json)` - JSON object with connection status | ||||||
|  |     /// * `Err(error)` - If getting status failed | ||||||
|  |     #[wasm_bindgen] | ||||||
|  |     pub fn get_connection_status() -> Result<String, JsValue> { | ||||||
|  |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             let client = c.borrow(); | ||||||
|  |  | ||||||
|  |             if let Some(client) = client.as_ref() { | ||||||
|  |                 let status = serde_json::json!({ | ||||||
|  |                     "is_connected": client.is_connected(), | ||||||
|  |                     "connected_public_key": client.connected_public_key(), | ||||||
|  |                     "pending_request_count": client.pending_request_count(), | ||||||
|  |                     "server_url": client.url() | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 Ok(status.to_string()) | ||||||
|  |             } else { | ||||||
|  |                 let status = serde_json::json!({ | ||||||
|  |                     "is_connected": false, | ||||||
|  |                     "connected_public_key": null, | ||||||
|  |                     "pending_request_count": 0, | ||||||
|  |                     "server_url": null | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 Ok(status.to_string()) | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Clear all pending requests | ||||||
|  |     /// | ||||||
|  |     /// # Returns | ||||||
|  |     /// * `Ok(())` - Requests cleared successfully | ||||||
|  |     #[wasm_bindgen] | ||||||
|  |     pub fn clear_pending_requests() -> Result<(), JsValue> { | ||||||
|  |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             let mut client = c.borrow_mut(); | ||||||
|  |             if let Some(client) = client.as_mut() { | ||||||
|  |                 client.clear_pending_requests(); | ||||||
|  |                 console_log!("Cleared all pending requests"); | ||||||
|  |             } | ||||||
|  |             Ok(()) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user