v2
This commit is contained in:
		| @@ -6,6 +6,9 @@ let sessionTimeoutDuration = 15; // Default 15 seconds | |||||||
| let sessionTimeoutId = null; // Background timer | let sessionTimeoutId = null; // Background timer | ||||||
| let popupPort = null; // Track popup connection | let popupPort = null; // Track popup connection | ||||||
|  |  | ||||||
|  | // SigSocket service instance | ||||||
|  | let sigSocketService = null; | ||||||
|  |  | ||||||
| // Utility function to convert Uint8Array to hex | // Utility function to convert Uint8Array to hex | ||||||
| function toHex(uint8Array) { | function toHex(uint8Array) { | ||||||
|   return Array.from(uint8Array) |   return Array.from(uint8Array) | ||||||
| @@ -135,8 +138,9 @@ async function restoreSession() { | |||||||
|   return null; |   return null; | ||||||
| } | } | ||||||
|  |  | ||||||
| // Import WASM module functions | // Import WASM module functions and SigSocket service | ||||||
| import init, * as wasmFunctions from './wasm/wasm_app.js'; | import init, * as wasmFunctions from './wasm/wasm_app.js'; | ||||||
|  | import SigSocketService from './background/sigsocket.js'; | ||||||
|  |  | ||||||
| // Initialize WASM module | // Initialize WASM module | ||||||
| async function initVault() { | async function initVault() { | ||||||
| @@ -151,6 +155,13 @@ async function initVault() { | |||||||
|     vault = wasmFunctions; |     vault = wasmFunctions; | ||||||
|     isInitialized = true; |     isInitialized = true; | ||||||
|  |  | ||||||
|  |     // Initialize SigSocket service | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       sigSocketService = new SigSocketService(); | ||||||
|  |       await sigSocketService.initialize(vault); | ||||||
|  |       console.log('🔌 SigSocket service initialized'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Try to restore previous session |     // Try to restore previous session | ||||||
|     await restoreSession(); |     await restoreSession(); | ||||||
|  |  | ||||||
| @@ -172,6 +183,20 @@ const messageHandlers = { | |||||||
|   initSession: async (request) => { |   initSession: async (request) => { | ||||||
|     await vault.init_session(request.keyspace, request.password); |     await vault.init_session(request.keyspace, request.password); | ||||||
|     await sessionManager.save(request.keyspace); |     await sessionManager.save(request.keyspace); | ||||||
|  |  | ||||||
|  |     // Smart auto-connect to SigSocket when session is initialized | ||||||
|  |     if (sigSocketService) { | ||||||
|  |       try { | ||||||
|  |         // This will reuse existing connection if same workspace, or switch if different | ||||||
|  |         const connected = await sigSocketService.connectToServer(request.keyspace); | ||||||
|  |         if (connected) { | ||||||
|  |           console.log(`🔗 SigSocket ready for workspace: ${request.keyspace}`); | ||||||
|  |         } | ||||||
|  |       } catch (error) { | ||||||
|  |         console.warn('Failed to auto-connect to SigSocket:', error); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return { success: true }; |     return { success: true }; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
| @@ -261,6 +286,62 @@ const messageHandlers = { | |||||||
|     await chrome.storage.local.set({ sessionTimeout: request.timeout }); |     await chrome.storage.local.set({ sessionTimeout: request.timeout }); | ||||||
|     resetSessionTimeout(); // Restart with new duration |     resetSessionTimeout(); // Restart with new duration | ||||||
|     return { success: true }; |     return { success: true }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   // SigSocket handlers | ||||||
|  |   connectSigSocket: async (request) => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not initialized' }; | ||||||
|  |     } | ||||||
|  |     const connected = await sigSocketService.connectToServer(request.workspace); | ||||||
|  |     return { success: connected }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   disconnectSigSocket: async () => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not initialized' }; | ||||||
|  |     } | ||||||
|  |     await sigSocketService.disconnect(); | ||||||
|  |     return { success: true }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   getSigSocketStatus: async () => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not initialized' }; | ||||||
|  |     } | ||||||
|  |     const status = await sigSocketService.getStatus(); | ||||||
|  |     return { success: true, status }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   getPendingSignRequests: async () => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not initialized' }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       // Use WASM filtered requests which handles workspace filtering | ||||||
|  |       const requests = await sigSocketService.getFilteredRequests(); | ||||||
|  |       return { success: true, requests }; | ||||||
|  |     } catch (error) { | ||||||
|  |       console.error('Failed to get pending requests:', error); | ||||||
|  |       return { success: false, error: error.message }; | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   approveSignRequest: async (request) => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not initialized' }; | ||||||
|  |     } | ||||||
|  |     const approved = await sigSocketService.approveSignRequest(request.requestId); | ||||||
|  |     return { success: approved }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   rejectSignRequest: async (request) => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not initialized' }; | ||||||
|  |     } | ||||||
|  |     const rejected = await sigSocketService.rejectSignRequest(request.requestId, request.reason); | ||||||
|  |     return { success: rejected }; | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -302,6 +383,11 @@ chrome.runtime.onConnect.addListener((port) => { | |||||||
|     // Track popup connection |     // Track popup connection | ||||||
|     popupPort = port; |     popupPort = port; | ||||||
|  |  | ||||||
|  |     // Connect SigSocket service to popup | ||||||
|  |     if (sigSocketService) { | ||||||
|  |       sigSocketService.setPopupPort(port); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // If we have an active session, ensure keep-alive is running |     // If we have an active session, ensure keep-alive is running | ||||||
|     if (currentSession) { |     if (currentSession) { | ||||||
|       startKeepAlive(); |       startKeepAlive(); | ||||||
| @@ -311,6 +397,11 @@ chrome.runtime.onConnect.addListener((port) => { | |||||||
|       // Popup closed, clear reference and stop keep-alive |       // Popup closed, clear reference and stop keep-alive | ||||||
|       popupPort = null; |       popupPort = null; | ||||||
|       stopKeepAlive(); |       stopKeepAlive(); | ||||||
|  |  | ||||||
|  |       // Disconnect SigSocket service from popup | ||||||
|  |       if (sigSocketService) { | ||||||
|  |         sigSocketService.setPopupPort(null); | ||||||
|  |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
| @@ -1,31 +1,35 @@ | |||||||
| /** | /** | ||||||
|  * SigSocket Service for Browser Extension |  * SigSocket Service - Clean Implementation with New WASM APIs | ||||||
|  *  |  * | ||||||
|  * Handles SigSocket client functionality including: |  * This service provides a clean interface for SigSocket functionality using | ||||||
|  * - Auto-connecting to SigSocket server when workspace is created |  * the new WASM-based APIs that handle all WebSocket management, request storage, | ||||||
|  * - Managing pending sign requests |  * and security validation internally. | ||||||
|  * - Handling user approval/rejection flow |  * | ||||||
|  * - Validating keyspace matches before showing approval UI |  * Architecture: | ||||||
|  |  * - WASM handles: WebSocket connection, message parsing, request storage, security | ||||||
|  |  * - Extension handles: UI notifications, badge updates, user interactions | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| class SigSocketService { | class SigSocketService { | ||||||
|     constructor() { |     constructor() { | ||||||
|         this.connection = null; |         // Connection state | ||||||
|         this.pendingRequests = new Map(); // requestId -> SignRequestData |  | ||||||
|         this.connectedPublicKey = null; |  | ||||||
|         this.isConnected = false; |         this.isConnected = false; | ||||||
|  |         this.currentWorkspace = null; | ||||||
|  |         this.connectedPublicKey = null; | ||||||
|  |  | ||||||
|  |         // Configuration | ||||||
|         this.defaultServerUrl = "ws://localhost:8080/ws"; |         this.defaultServerUrl = "ws://localhost:8080/ws"; | ||||||
|  |  | ||||||
|         // Initialize WASM module reference |         // WASM module reference | ||||||
|         this.wasmModule = null; |         this.wasmModule = null; | ||||||
|  |  | ||||||
|         // Reference to popup port for communication |         // UI communication | ||||||
|         this.popupPort = null; |         this.popupPort = null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Initialize the service with WASM module |      * Initialize the service with WASM module | ||||||
|      * @param {Object} wasmModule - The loaded WASM module |      * @param {Object} wasmModule - The loaded WASM module with SigSocketManager | ||||||
|      */ |      */ | ||||||
|     async initialize(wasmModule) { |     async initialize(wasmModule) { | ||||||
|         this.wasmModule = wasmModule; |         this.wasmModule = wasmModule; | ||||||
| @@ -40,427 +44,333 @@ class SigSocketService { | |||||||
|             console.warn('Failed to load SigSocket URL from storage:', error); |             console.warn('Failed to load SigSocket URL from storage:', error); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Set up global callbacks for WASM |         console.log('🔌 SigSocket service initialized with WASM APIs'); | ||||||
|         globalThis.onSignRequestReceived = this.handleIncomingRequest.bind(this); |  | ||||||
|         globalThis.onConnectionStateChanged = this.handleConnectionStateChange.bind(this); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Connect to SigSocket server for a workspace |      * Connect to SigSocket server using WASM APIs | ||||||
|  |      * WASM handles all connection logic (reuse, switching, etc.) | ||||||
|      * @param {string} workspaceId - The workspace/keyspace identifier |      * @param {string} workspaceId - The workspace/keyspace identifier | ||||||
|      * @returns {Promise<boolean>} - True if connected successfully |      * @returns {Promise<boolean>} - True if connected successfully | ||||||
|      */ |      */ | ||||||
|     async connectToServer(workspaceId) { |     async connectToServer(workspaceId) { | ||||||
|         try { |         try { | ||||||
|             if (!this.wasmModule) { |             if (!this.wasmModule?.SigSocketManager) { | ||||||
|                 throw new Error('WASM module not initialized'); |                 throw new Error('WASM SigSocketManager not available'); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Check if already connected to this workspace |             console.log(`🔗 Requesting SigSocket connection for workspace: ${workspaceId}`); | ||||||
|             if (this.isConnected && this.connection) { |  | ||||||
|                 console.log(`Already connected to SigSocket server for workspace: ${workspaceId}`); |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Disconnect any existing connection first |             // Let WASM handle all connection logic (reuse, switching, etc.) | ||||||
|             if (this.connection) { |             const connectionInfo = await this.wasmModule.SigSocketManager.connect_workspace_with_events( | ||||||
|                 this.disconnect(); |                 workspaceId, | ||||||
|             } |                 this.defaultServerUrl, | ||||||
|  |                 (event) => this.handleSigSocketEvent(event) | ||||||
|  |             ); | ||||||
|  |  | ||||||
|             // Get the workspace default public key |             // Parse connection info | ||||||
|             const publicKeyHex = await this.wasmModule.get_workspace_default_public_key(workspaceId); |             const info = JSON.parse(connectionInfo); | ||||||
|             if (!publicKeyHex) { |             this.currentWorkspace = info.workspace; | ||||||
|                 throw new Error('No public key found for workspace'); |             this.connectedPublicKey = info.public_key; | ||||||
|             } |             this.isConnected = info.is_connected; | ||||||
|  |  | ||||||
|             console.log(`Connecting to SigSocket server for workspace: ${workspaceId} with key: ${publicKeyHex.substring(0, 16)}...`); |             console.log(`✅ SigSocket connection result:`, { | ||||||
|  |                 workspace: this.currentWorkspace, | ||||||
|  |                 publicKey: this.connectedPublicKey?.substring(0, 16) + '...', | ||||||
|  |                 connected: this.isConnected | ||||||
|  |             }); | ||||||
|  |  | ||||||
|             // Create new SigSocket connection |             // Update badge to show current state | ||||||
|             console.log('Creating new SigSocketConnection instance'); |             this.updateBadge(); | ||||||
|             this.connection = new this.wasmModule.SigSocketConnection(); |  | ||||||
|             console.log('SigSocketConnection instance created'); |  | ||||||
|  |  | ||||||
|             // Connect to server |             return this.isConnected; | ||||||
|             await this.connection.connect(this.defaultServerUrl, publicKeyHex); |  | ||||||
|  |  | ||||||
|             this.connectedPublicKey = publicKeyHex; |  | ||||||
|  |  | ||||||
|             // Clear pending requests if switching to a different workspace |  | ||||||
|             if (this.currentWorkspace && this.currentWorkspace !== workspaceId) { |  | ||||||
|                 console.log(`Switching workspace from ${this.currentWorkspace} to ${workspaceId}, clearing pending requests`); |  | ||||||
|                 this.pendingRequests.clear(); |  | ||||||
|                 this.updateBadge(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             this.currentWorkspace = workspaceId; |  | ||||||
|             this.isConnected = true; |  | ||||||
|  |  | ||||||
|             console.log(`Successfully connected to SigSocket server for workspace: ${workspaceId}`); |  | ||||||
|             return true; |  | ||||||
|  |  | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             console.error('Failed to connect to SigSocket server:', error); |             console.error('❌ SigSocket connection failed:', error); | ||||||
|             this.isConnected = false; |             this.isConnected = false; | ||||||
|             this.connectedPublicKey = null; |  | ||||||
|             this.currentWorkspace = null; |             this.currentWorkspace = null; | ||||||
|             if (this.connection) { |             this.connectedPublicKey = null; | ||||||
|                 this.connection.disconnect(); |  | ||||||
|                 this.connection = null; |  | ||||||
|             } |  | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Handle incoming sign request from server |      * Handle events from the WASM SigSocket client | ||||||
|      * @param {string} requestId - Unique request identifier |      * This is called automatically when requests arrive | ||||||
|      * @param {string} messageBase64 - Message to be signed (base64-encoded) |      * @param {Object} event - Event from WASM layer | ||||||
|      */ |      */ | ||||||
|     handleIncomingRequest(requestId, messageBase64) { |     handleSigSocketEvent(event) { | ||||||
|         console.log(`Received sign request: ${requestId}`); |         console.log('📨 Received SigSocket event:', event); | ||||||
|  |  | ||||||
|         // Security check: Only accept requests if we have an active connection |         if (event.type === 'sign_request') { | ||||||
|         if (!this.isConnected || !this.connectedPublicKey || !this.currentWorkspace) { |             console.log(`🔐 New sign request: ${event.request_id}`); | ||||||
|             console.warn(`Rejecting sign request ${requestId}: No active workspace connection`); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Store the request with workspace info |             // The request is automatically stored by WASM | ||||||
|         const requestData = { |             // We just handle UI updates | ||||||
|             id: requestId, |             this.showSignRequestNotification(); | ||||||
|             message: messageBase64, |             this.updateBadge(); | ||||||
|             timestamp: Date.now(), |             this.notifyPopupOfNewRequest(); | ||||||
|             status: 'pending', |  | ||||||
|             workspace: this.currentWorkspace, |  | ||||||
|             connectedPublicKey: this.connectedPublicKey |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         this.pendingRequests.set(requestId, requestData); |  | ||||||
|  |  | ||||||
|         console.log(`Stored sign request for workspace: ${this.currentWorkspace}`); |  | ||||||
|  |  | ||||||
|         // Show notification to user |  | ||||||
|         this.showSignRequestNotification(); |  | ||||||
|  |  | ||||||
|         // Update extension badge |  | ||||||
|         this.updateBadge(); |  | ||||||
|  |  | ||||||
|         // Notify popup about new request if it's open and keyspace is unlocked |  | ||||||
|         this.notifyPopupOfNewRequest(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Handle connection state changes |  | ||||||
|      * @param {boolean} connected - True if connected, false if disconnected |  | ||||||
|      */ |  | ||||||
|     handleConnectionStateChange(connected) { |  | ||||||
|         this.isConnected = connected; |  | ||||||
|         console.log(`SigSocket connection state changed: ${connected ? 'connected' : 'disconnected'}`); |  | ||||||
|          |  | ||||||
|         if (!connected) { |  | ||||||
|             this.connectedPublicKey = null; |  | ||||||
|             this.currentWorkspace = null; |  | ||||||
|             // Optionally attempt reconnection here |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Called when keyspace is unlocked - validate and show/hide approval UI |      * Approve a sign request using WASM APIs | ||||||
|      */ |  | ||||||
|     async onKeypaceUnlocked() { |  | ||||||
|         try { |  | ||||||
|             if (!this.wasmModule) { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Only check keyspace match if we have a connection |  | ||||||
|             if (!this.isConnected || !this.connectedPublicKey) { |  | ||||||
|                 console.log('No SigSocket connection to validate against'); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Get the currently unlocked workspace name |  | ||||||
|             const unlockedWorkspaceName = this.wasmModule.get_current_keyspace_name(); |  | ||||||
|  |  | ||||||
|             // Get workspace default public key for the UNLOCKED workspace (not connected workspace) |  | ||||||
|             const unlockedWorkspacePublicKey = await this.wasmModule.get_workspace_default_public_key(unlockedWorkspaceName); |  | ||||||
|  |  | ||||||
|             // Check if the unlocked workspace matches the connected workspace |  | ||||||
|             const workspaceMatches = unlockedWorkspaceName === this.currentWorkspace; |  | ||||||
|             const publicKeyMatches = unlockedWorkspacePublicKey === this.connectedPublicKey; |  | ||||||
|             const keypaceMatches = workspaceMatches && publicKeyMatches; |  | ||||||
|  |  | ||||||
|             console.log(`Keyspace unlock validation:`); |  | ||||||
|             console.log(`  Connected workspace: ${this.currentWorkspace}`); |  | ||||||
|             console.log(`  Unlocked workspace: ${unlockedWorkspaceName}`); |  | ||||||
|             console.log(`  Connected public key: ${this.connectedPublicKey}`); |  | ||||||
|             console.log(`  Unlocked public key: ${unlockedWorkspacePublicKey}`); |  | ||||||
|             console.log(`  Workspace matches: ${workspaceMatches}`); |  | ||||||
|             console.log(`  Public key matches: ${publicKeyMatches}`); |  | ||||||
|             console.log(`  Overall match: ${keypaceMatches}`); |  | ||||||
|  |  | ||||||
|             // Always get current pending requests (filtered by connected workspace) |  | ||||||
|             const currentPendingRequests = this.getPendingRequests(); |  | ||||||
|  |  | ||||||
|             // Notify popup about keyspace state |  | ||||||
|             console.log(`Sending KEYSPACE_UNLOCKED message to popup: keypaceMatches=${keypaceMatches}, pendingRequests=${currentPendingRequests.length}`); |  | ||||||
|             if (this.popupPort) { |  | ||||||
|                 this.popupPort.postMessage({ |  | ||||||
|                     type: 'KEYSPACE_UNLOCKED', |  | ||||||
|                     keypaceMatches, |  | ||||||
|                     pendingRequests: currentPendingRequests |  | ||||||
|                 }); |  | ||||||
|                 console.log('KEYSPACE_UNLOCKED message sent to popup'); |  | ||||||
|             } else { |  | ||||||
|                 console.log('No popup port available to send KEYSPACE_UNLOCKED message'); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|         } catch (error) { |  | ||||||
|             if (error.message && error.message.includes('Workspace not found')) { |  | ||||||
|                 console.log(`Keyspace unlock: Different workspace unlocked (connected to: ${this.currentWorkspace})`); |  | ||||||
|  |  | ||||||
|                 // Send message with no match and empty requests |  | ||||||
|                 if (this.popupPort) { |  | ||||||
|                     this.popupPort.postMessage({ |  | ||||||
|                         type: 'KEYSPACE_UNLOCKED', |  | ||||||
|                         keypaceMatches: false, |  | ||||||
|                         pendingRequests: [] |  | ||||||
|                     }); |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 console.error('Error handling keyspace unlock:', error); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Approve a sign request |  | ||||||
|      * @param {string} requestId - Request to approve |      * @param {string} requestId - Request to approve | ||||||
|      * @returns {Promise<boolean>} - True if approved successfully |      * @returns {Promise<boolean>} - True if approved successfully | ||||||
|      */ |      */ | ||||||
|     async approveSignRequest(requestId) { |     async approveSignRequest(requestId) { | ||||||
|         try { |         try { | ||||||
|             const request = this.pendingRequests.get(requestId); |             if (!this.wasmModule?.SigSocketManager) { | ||||||
|             if (!request) { |                 throw new Error('WASM SigSocketManager not available'); | ||||||
|                 throw new Error('Request not found'); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Validate request is for current workspace |             console.log(`✅ Approving request: ${requestId}`); | ||||||
|             if (request.workspace !== this.currentWorkspace) { |  | ||||||
|                 throw new Error(`Request is for workspace '${request.workspace}', but current workspace is '${this.currentWorkspace}'`); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (request.connectedPublicKey !== this.connectedPublicKey) { |             // WASM handles all validation, signing, and server communication | ||||||
|                 throw new Error('Request public key does not match current connection'); |             await this.wasmModule.SigSocketManager.approve_request(requestId); | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Validate keyspace is still unlocked and matches |             console.log(`🎉 Request approved successfully: ${requestId}`); | ||||||
|             if (!this.wasmModule.is_unlocked()) { |  | ||||||
|                 throw new Error('Keyspace is locked'); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             const currentPublicKey = await this.wasmModule.get_workspace_default_public_key(this.currentWorkspace); |             // Update UI | ||||||
|             if (currentPublicKey !== this.connectedPublicKey) { |  | ||||||
|                 throw new Error('Keyspace mismatch'); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Decode message from base64 |  | ||||||
|             const messageBytes = atob(request.message).split('').map(c => c.charCodeAt(0)); |  | ||||||
|  |  | ||||||
|             // Sign the message with default keypair (doesn't require selected keypair) |  | ||||||
|             const signatureHex = await this.wasmModule.sign_with_default_keypair(new Uint8Array(messageBytes)); |  | ||||||
|              |  | ||||||
|             // Send response to server |  | ||||||
|             await this.connection.send_response(requestId, request.message, signatureHex); |  | ||||||
|              |  | ||||||
|             // Update request status |  | ||||||
|             request.status = 'approved'; |  | ||||||
|             request.signature = signatureHex; |  | ||||||
|              |  | ||||||
|             // Remove from pending requests |  | ||||||
|             this.pendingRequests.delete(requestId); |  | ||||||
|              |  | ||||||
|             // Update badge |  | ||||||
|             this.updateBadge(); |             this.updateBadge(); | ||||||
|              |             this.notifyPopupOfRequestUpdate(); | ||||||
|             console.log(`Approved sign request: ${requestId}`); |  | ||||||
|             return true; |             return true; | ||||||
|              |  | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             console.error('Failed to approve sign request:', error); |             console.error(`❌ Failed to approve request ${requestId}:`, error); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Reject a sign request |      * Reject a sign request using WASM APIs | ||||||
|      * @param {string} requestId - Request to reject |      * @param {string} requestId - Request to reject | ||||||
|      * @param {string} reason - Reason for rejection (optional) |      * @param {string} reason - Reason for rejection | ||||||
|      * @returns {Promise<boolean>} - True if rejected successfully |      * @returns {Promise<boolean>} - True if rejected successfully | ||||||
|      */ |      */ | ||||||
|     async rejectSignRequest(requestId, reason = 'User rejected') { |     async rejectSignRequest(requestId, reason = 'User rejected') { | ||||||
|         try { |         try { | ||||||
|             const request = this.pendingRequests.get(requestId); |             if (!this.wasmModule?.SigSocketManager) { | ||||||
|             if (!request) { |                 throw new Error('WASM SigSocketManager not available'); | ||||||
|                 throw new Error('Request not found'); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Send rejection to server |             console.log(`❌ Rejecting request: ${requestId}, reason: ${reason}`); | ||||||
|             await this.connection.send_rejection(requestId, reason); |  | ||||||
|              |             // WASM handles rejection and server communication | ||||||
|             // Update request status |             await this.wasmModule.SigSocketManager.reject_request(requestId, reason); | ||||||
|             request.status = 'rejected'; |  | ||||||
|             request.reason = reason; |             console.log(`✅ Request rejected successfully: ${requestId}`); | ||||||
|              |  | ||||||
|             // Remove from pending requests |             // Update UI | ||||||
|             this.pendingRequests.delete(requestId); |  | ||||||
|              |  | ||||||
|             // Update badge |  | ||||||
|             this.updateBadge(); |             this.updateBadge(); | ||||||
|              |             this.notifyPopupOfRequestUpdate(); | ||||||
|             console.log(`Rejected sign request: ${requestId}, reason: ${reason}`); |  | ||||||
|             return true; |             return true; | ||||||
|              |  | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             console.error('Failed to reject sign request:', error); |             console.error(`❌ Failed to reject request ${requestId}:`, error); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Get all pending requests for the current workspace |      * Get pending requests from WASM (filtered by current workspace) | ||||||
|      * @returns {Array} - Array of pending request data for current workspace |      * @returns {Promise<Array>} - Array of pending requests for current workspace | ||||||
|      */ |      */ | ||||||
|     getPendingRequests() { |     async getPendingRequests() { | ||||||
|         const allRequests = Array.from(this.pendingRequests.values()); |         return this.getFilteredRequests(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|         // Filter requests to only include those for the current workspace |     /** | ||||||
|         const filteredRequests = allRequests.filter(request => { |      * Get filtered requests from WASM (workspace-aware) | ||||||
|             const isCurrentWorkspace = request.workspace === this.currentWorkspace; |      * @returns {Promise<Array>} - Array of filtered requests | ||||||
|             const isCurrentPublicKey = request.connectedPublicKey === this.connectedPublicKey; |      */ | ||||||
|  |     async getFilteredRequests() { | ||||||
|             if (!isCurrentWorkspace || !isCurrentPublicKey) { |         try { | ||||||
|                 console.log(`Filtering out request ${request.id}: workspace=${request.workspace} (current=${this.currentWorkspace}), publicKey match=${isCurrentPublicKey}`); |             if (!this.wasmModule?.SigSocketManager) { | ||||||
|  |                 return []; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return isCurrentWorkspace && isCurrentPublicKey; |             const requestsJson = await this.wasmModule.SigSocketManager.get_filtered_requests(); | ||||||
|         }); |             const requests = JSON.parse(requestsJson); | ||||||
|  |  | ||||||
|         console.log(`getPendingRequests: ${allRequests.length} total, ${filteredRequests.length} for current workspace`); |             console.log(`📋 Retrieved ${requests.length} filtered requests for current workspace`); | ||||||
|         return filteredRequests; |             return requests; | ||||||
|  |  | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to get filtered requests:', error); | ||||||
|  |             return []; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Check if a request can be approved (keyspace validation) | ||||||
|  |      * @param {string} requestId - Request ID to check | ||||||
|  |      * @returns {Promise<boolean>} - True if can be approved | ||||||
|  |      */ | ||||||
|  |     async canApproveRequest(requestId) { | ||||||
|  |         try { | ||||||
|  |             if (!this.wasmModule?.SigSocketManager) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return await this.wasmModule.SigSocketManager.can_approve_request(requestId); | ||||||
|  |  | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to check request approval status:', error); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Show notification for new sign request |      * Show notification for new sign request | ||||||
|      */ |      */ | ||||||
|     showSignRequestNotification() { |     showSignRequestNotification() { | ||||||
|         // Create notification |         try { | ||||||
|         chrome.notifications.create({ |             if (chrome.notifications && chrome.notifications.create) { | ||||||
|             type: 'basic', |                 chrome.notifications.create({ | ||||||
|             iconUrl: 'icons/icon48.png', |                     type: 'basic', | ||||||
|             title: 'Sign Request', |                     iconUrl: 'icons/icon48.png', | ||||||
|             message: 'New signature request received. Click to review.' |                     title: 'SigSocket Sign Request', | ||||||
|         }); |                     message: 'New signature request received. Click to review.' | ||||||
|  |                 }); | ||||||
|  |                 console.log('📢 Notification shown for sign request'); | ||||||
|  |             } else { | ||||||
|  |                 console.log('📢 Notifications not available, skipping notification'); | ||||||
|  |             } | ||||||
|  |         } catch (error) { | ||||||
|  |             console.warn('Failed to show notification:', error); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Notify popup about new request if popup is open and keyspace is unlocked |      * Update extension badge with pending request count | ||||||
|  |      */ | ||||||
|  |     async updateBadge() { | ||||||
|  |         try { | ||||||
|  |             const requests = await this.getPendingRequests(); | ||||||
|  |             const count = requests.length; | ||||||
|  |             const badgeText = count > 0 ? count.toString() : ''; | ||||||
|  |  | ||||||
|  |             console.log(`🔢 Updating badge: ${count} pending requests`); | ||||||
|  |  | ||||||
|  |             chrome.action.setBadgeText({ text: badgeText }); | ||||||
|  |             chrome.action.setBadgeBackgroundColor({ color: '#ff6b6b' }); | ||||||
|  |  | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to update badge:', error); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Notify popup about new request | ||||||
|      */ |      */ | ||||||
|     async notifyPopupOfNewRequest() { |     async notifyPopupOfNewRequest() { | ||||||
|         // Only notify if popup is connected |  | ||||||
|         if (!this.popupPort) { |         if (!this.popupPort) { | ||||||
|             console.log('No popup port available, skipping new request notification'); |             console.log('No popup connected, skipping notification'); | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Check if we have WASM module and can validate keyspace |  | ||||||
|         if (!this.wasmModule) { |  | ||||||
|             console.log('WASM module not available, skipping new request notification'); |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             // Check if keyspace is unlocked |             const requests = await this.getPendingRequests(); | ||||||
|             if (!this.wasmModule.is_unlocked()) { |             const canApprove = requests.length > 0 ? await this.canApproveRequest(requests[0].id) : false; | ||||||
|                 console.log('Keyspace is locked, skipping new request notification'); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Get the currently unlocked workspace name |  | ||||||
|             const unlockedWorkspaceName = this.wasmModule.get_current_keyspace_name(); |  | ||||||
|  |  | ||||||
|             // Get workspace default public key for the UNLOCKED workspace |  | ||||||
|             const unlockedWorkspacePublicKey = await this.wasmModule.get_workspace_default_public_key(unlockedWorkspaceName); |  | ||||||
|  |  | ||||||
|             // Check if the unlocked workspace matches the connected workspace |  | ||||||
|             const workspaceMatches = unlockedWorkspaceName === this.currentWorkspace; |  | ||||||
|             const publicKeyMatches = unlockedWorkspacePublicKey === this.connectedPublicKey; |  | ||||||
|             const keypaceMatches = workspaceMatches && publicKeyMatches; |  | ||||||
|  |  | ||||||
|             console.log(`New request notification check: keypaceMatches=${keypaceMatches}, workspace=${unlockedWorkspaceName}, connected=${this.currentWorkspace}`); |  | ||||||
|  |  | ||||||
|             // Get current pending requests (filtered by connected workspace) |  | ||||||
|             const currentPendingRequests = this.getPendingRequests(); |  | ||||||
|  |  | ||||||
|             // SECURITY: Only send requests if workspace matches, otherwise send empty array |  | ||||||
|             const requestsToSend = keypaceMatches ? currentPendingRequests : []; |  | ||||||
|  |  | ||||||
|             // Send update to popup |  | ||||||
|             this.popupPort.postMessage({ |             this.popupPort.postMessage({ | ||||||
|                 type: 'NEW_SIGN_REQUEST', |                 type: 'NEW_SIGN_REQUEST', | ||||||
|                 keypaceMatches, |                 canApprove, | ||||||
|                 pendingRequests: requestsToSend |                 pendingRequests: requests | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|             console.log(`Sent NEW_SIGN_REQUEST message to popup: keypaceMatches=${keypaceMatches}, ${requestsToSend.length} requests (${currentPendingRequests.length} total for connected workspace)`); |             console.log(`📤 Notified popup: ${requests.length} requests, canApprove: ${canApprove}`); | ||||||
|  |  | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             console.log('Error in notifyPopupOfNewRequest:', error); |             console.error('Failed to notify popup:', error); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Update extension badge with pending request count for current workspace |      * Notify popup about request updates | ||||||
|      */ |      */ | ||||||
|     updateBadge() { |     async notifyPopupOfRequestUpdate() { | ||||||
|         // Only count requests for the current workspace |         if (!this.popupPort) return; | ||||||
|         const currentWorkspaceRequests = this.getPendingRequests(); |  | ||||||
|         const count = currentWorkspaceRequests.length; |  | ||||||
|         const badgeText = count > 0 ? count.toString() : ''; |  | ||||||
|  |  | ||||||
|         console.log(`Updating badge: ${this.pendingRequests.size} total requests, ${count} for current workspace, badge text: "${badgeText}"`); |         try { | ||||||
|  |             const requests = await this.getPendingRequests(); | ||||||
|  |  | ||||||
|         chrome.action.setBadgeText({ text: badgeText }); |             this.popupPort.postMessage({ | ||||||
|         chrome.action.setBadgeBackgroundColor({ color: '#ff6b6b' }); |                 type: 'REQUESTS_UPDATED', | ||||||
|  |                 pendingRequests: requests | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to notify popup of update:', error); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Disconnect from SigSocket server |      * Disconnect from SigSocket server | ||||||
|  |      * WASM handles all disconnection logic | ||||||
|      */ |      */ | ||||||
|     disconnect() { |     async disconnect() { | ||||||
|         if (this.connection) { |         try { | ||||||
|             this.connection.disconnect(); |             if (this.wasmModule?.SigSocketManager) { | ||||||
|             this.connection = null; |                 await this.wasmModule.SigSocketManager.disconnect(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Clear local state | ||||||
|  |             this.isConnected = false; | ||||||
|  |             this.currentWorkspace = null; | ||||||
|  |             this.connectedPublicKey = null; | ||||||
|  |  | ||||||
|  |             this.updateBadge(); | ||||||
|  |  | ||||||
|  |             console.log('🔌 SigSocket disconnection requested'); | ||||||
|  |  | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to disconnect:', error); | ||||||
|         } |         } | ||||||
|          |  | ||||||
|         this.isConnected = false; |  | ||||||
|         this.connectedPublicKey = null; |  | ||||||
|         this.currentWorkspace = null; |  | ||||||
|         this.pendingRequests.clear(); |  | ||||||
|         this.updateBadge(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Get connection status |      * Get connection status from WASM | ||||||
|      * @returns {Object} - Connection status information |      * @returns {Promise<Object>} - Connection status information | ||||||
|      */ |      */ | ||||||
|     getStatus() { |     async getStatus() { | ||||||
|         return { |         try { | ||||||
|             isConnected: this.isConnected, |             if (!this.wasmModule?.SigSocketManager) { | ||||||
|             connectedPublicKey: this.connectedPublicKey, |                 return { | ||||||
|             pendingRequestCount: this.getPendingRequests().length, |                     isConnected: false, | ||||||
|             serverUrl: this.defaultServerUrl |                     workspace: null, | ||||||
|         }; |                     publicKey: null, | ||||||
|  |                     pendingRequestCount: 0, | ||||||
|  |                     serverUrl: this.defaultServerUrl | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Let WASM provide the authoritative status | ||||||
|  |             const statusJson = await this.wasmModule.SigSocketManager.get_connection_status(); | ||||||
|  |             const status = JSON.parse(statusJson); | ||||||
|  |             const requests = await this.getPendingRequests(); | ||||||
|  |  | ||||||
|  |             return { | ||||||
|  |                 isConnected: status.is_connected, | ||||||
|  |                 workspace: status.workspace, | ||||||
|  |                 publicKey: status.public_key, | ||||||
|  |                 pendingRequestCount: requests.length, | ||||||
|  |                 serverUrl: this.defaultServerUrl | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to get status:', error); | ||||||
|  |             return { | ||||||
|  |                 isConnected: false, | ||||||
|  |                 workspace: null, | ||||||
|  |                 publicKey: null, | ||||||
|  |                 pendingRequestCount: 0, | ||||||
|  |                 serverUrl: this.defaultServerUrl | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -469,8 +379,32 @@ class SigSocketService { | |||||||
|      */ |      */ | ||||||
|     setPopupPort(port) { |     setPopupPort(port) { | ||||||
|         this.popupPort = port; |         this.popupPort = port; | ||||||
|  |         console.log('📱 Popup connected to SigSocket service'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Called when keyspace is unlocked - notify popup of current state | ||||||
|  |      */ | ||||||
|  |     async onKeypaceUnlocked() { | ||||||
|  |         if (!this.popupPort) return; | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             const requests = await this.getPendingRequests(); | ||||||
|  |             const canApprove = requests.length > 0 ? await this.canApproveRequest(requests[0].id) : false; | ||||||
|  |  | ||||||
|  |             this.popupPort.postMessage({ | ||||||
|  |                 type: 'KEYSPACE_UNLOCKED', | ||||||
|  |                 canApprove, | ||||||
|  |                 pendingRequests: requests | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             console.log(`🔓 Keyspace unlocked notification sent: ${requests.length} requests, canApprove: ${canApprove}`); | ||||||
|  |  | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to handle keyspace unlock:', error); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| // Export for use in background script | // Export for use in background script | ||||||
| export default SigSocketService; | export default SigSocketService; | ||||||
| @@ -6,7 +6,8 @@ | |||||||
|  |  | ||||||
|   "permissions": [ |   "permissions": [ | ||||||
|     "storage", |     "storage", | ||||||
|     "activeTab" |     "activeTab", | ||||||
|  |     "notifications" | ||||||
|   ], |   ], | ||||||
|  |  | ||||||
|   "icons": { |   "icons": { | ||||||
|   | |||||||
| @@ -73,6 +73,50 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  |       <!-- SigSocket Requests Section --> | ||||||
|  |       <div class="card sigsocket-section" id="sigSocketSection"> | ||||||
|  |         <div class="section-header"> | ||||||
|  |           <h3>🔌 SigSocket Requests</h3> | ||||||
|  |           <div class="connection-status" id="connectionStatus"> | ||||||
|  |             <span class="status-dot" id="connectionDot"></span> | ||||||
|  |             <span id="connectionText">Disconnected</span> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div class="requests-container" id="requestsContainer"> | ||||||
|  |           <div class="no-requests" id="noRequestsMessage"> | ||||||
|  |             <div class="empty-state"> | ||||||
|  |               <div class="empty-icon">📝</div> | ||||||
|  |               <p>No pending sign requests</p> | ||||||
|  |               <small>Requests will appear here when received from SigSocket server</small> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|  |           <div class="requests-list hidden" id="requestsList"> | ||||||
|  |             <!-- Requests will be populated here --> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div class="sigsocket-actions"> | ||||||
|  |           <button id="refreshRequestsBtn" class="btn btn-ghost btn-small"> | ||||||
|  |             <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||||
|  |               <polyline points="23 4 23 10 17 10"></polyline> | ||||||
|  |               <polyline points="1 20 1 14 7 14"></polyline> | ||||||
|  |               <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path> | ||||||
|  |             </svg> | ||||||
|  |             Refresh | ||||||
|  |           </button> | ||||||
|  |           <button id="sigSocketStatusBtn" class="btn btn-ghost btn-small"> | ||||||
|  |             <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||||
|  |               <circle cx="12" cy="12" r="10"></circle> | ||||||
|  |               <line x1="12" y1="6" x2="12" y2="12"></line> | ||||||
|  |               <line x1="16" y1="16" x2="12" y2="12"></line> | ||||||
|  |             </svg> | ||||||
|  |             Status | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|       <div class="vault-header"> |       <div class="vault-header"> | ||||||
|         <h2>Your Keypairs</h2> |         <h2>Your Keypairs</h2> | ||||||
|         <button id="toggleAddKeypairBtn" class="btn btn-primary"> |         <button id="toggleAddKeypairBtn" class="btn btn-primary"> | ||||||
|   | |||||||
| @@ -931,4 +931,293 @@ const verifySignature = () => performCryptoOperation({ | |||||||
|               <span>${text}</span> |               <span>${text}</span> | ||||||
|             </div>`; |             </div>`; | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | // SigSocket functionality | ||||||
|  | let sigSocketRequests = []; | ||||||
|  | let sigSocketStatus = { isConnected: false, workspace: null }; | ||||||
|  |  | ||||||
|  | // Initialize SigSocket UI elements | ||||||
|  | const sigSocketElements = { | ||||||
|  |   connectionStatus: document.getElementById('connectionStatus'), | ||||||
|  |   connectionDot: document.getElementById('connectionDot'), | ||||||
|  |   connectionText: document.getElementById('connectionText'), | ||||||
|  |   requestsContainer: document.getElementById('requestsContainer'), | ||||||
|  |   noRequestsMessage: document.getElementById('noRequestsMessage'), | ||||||
|  |   requestsList: document.getElementById('requestsList'), | ||||||
|  |   refreshRequestsBtn: document.getElementById('refreshRequestsBtn'), | ||||||
|  |   sigSocketStatusBtn: document.getElementById('sigSocketStatusBtn') | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Add SigSocket event listeners | ||||||
|  | document.addEventListener('DOMContentLoaded', () => { | ||||||
|  |   // Add SigSocket button listeners | ||||||
|  |   sigSocketElements.refreshRequestsBtn?.addEventListener('click', refreshSigSocketRequests); | ||||||
|  |   sigSocketElements.sigSocketStatusBtn?.addEventListener('click', showSigSocketStatus); | ||||||
|  |  | ||||||
|  |   // Load initial SigSocket state | ||||||
|  |   loadSigSocketState(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | // Listen for messages from background script about SigSocket events | ||||||
|  | if (backgroundPort) { | ||||||
|  |   backgroundPort.onMessage.addListener((message) => { | ||||||
|  |     if (message.type === 'NEW_SIGN_REQUEST') { | ||||||
|  |       handleNewSignRequest(message); | ||||||
|  |     } else if (message.type === 'REQUESTS_UPDATED') { | ||||||
|  |       updateRequestsList(message.pendingRequests); | ||||||
|  |     } else if (message.type === 'KEYSPACE_UNLOCKED') { | ||||||
|  |       handleKeypaceUnlocked(message); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Load SigSocket state when popup opens | ||||||
|  | async function loadSigSocketState() { | ||||||
|  |   try { | ||||||
|  |     // Get SigSocket status | ||||||
|  |     const statusResponse = await sendMessage('getSigSocketStatus'); | ||||||
|  |     if (statusResponse?.success) { | ||||||
|  |       updateConnectionStatus(statusResponse.status); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Get pending requests | ||||||
|  |     const requestsResponse = await sendMessage('getPendingSignRequests'); | ||||||
|  |     if (requestsResponse?.success) { | ||||||
|  |       updateRequestsList(requestsResponse.requests); | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.warn('Failed to load SigSocket state:', error); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Update connection status display | ||||||
|  | function updateConnectionStatus(status) { | ||||||
|  |   sigSocketStatus = status; | ||||||
|  |  | ||||||
|  |   if (sigSocketElements.connectionDot && sigSocketElements.connectionText) { | ||||||
|  |     if (status.isConnected) { | ||||||
|  |       sigSocketElements.connectionDot.classList.add('connected'); | ||||||
|  |       sigSocketElements.connectionText.textContent = `Connected (${status.workspace || 'Unknown'})`; | ||||||
|  |     } else { | ||||||
|  |       sigSocketElements.connectionDot.classList.remove('connected'); | ||||||
|  |       sigSocketElements.connectionText.textContent = 'Disconnected'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Update requests list display | ||||||
|  | function updateRequestsList(requests) { | ||||||
|  |   sigSocketRequests = requests || []; | ||||||
|  |  | ||||||
|  |   if (!sigSocketElements.requestsContainer) return; | ||||||
|  |  | ||||||
|  |   if (sigSocketRequests.length === 0) { | ||||||
|  |     sigSocketElements.noRequestsMessage?.classList.remove('hidden'); | ||||||
|  |     sigSocketElements.requestsList?.classList.add('hidden'); | ||||||
|  |   } else { | ||||||
|  |     sigSocketElements.noRequestsMessage?.classList.add('hidden'); | ||||||
|  |     sigSocketElements.requestsList?.classList.remove('hidden'); | ||||||
|  |  | ||||||
|  |     if (sigSocketElements.requestsList) { | ||||||
|  |       sigSocketElements.requestsList.innerHTML = sigSocketRequests.map(request => | ||||||
|  |         createRequestItem(request) | ||||||
|  |       ).join(''); | ||||||
|  |  | ||||||
|  |       // Add event listeners to approve/reject buttons | ||||||
|  |       addRequestEventListeners(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Create HTML for a single request item | ||||||
|  | function createRequestItem(request) { | ||||||
|  |   const requestTime = new Date(request.timestamp || Date.now()).toLocaleTimeString(); | ||||||
|  |   const shortId = request.id.substring(0, 8) + '...'; | ||||||
|  |   const decodedMessage = request.message ? atob(request.message) : 'No message'; | ||||||
|  |  | ||||||
|  |   return ` | ||||||
|  |     <div class="request-item" data-request-id="${request.id}"> | ||||||
|  |       <div class="request-header"> | ||||||
|  |         <div class="request-id" title="${request.id}">${shortId}</div> | ||||||
|  |         <div class="request-time">${requestTime}</div> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <div class="request-message" title="${decodedMessage}"> | ||||||
|  |         ${decodedMessage.length > 100 ? decodedMessage.substring(0, 100) + '...' : decodedMessage} | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <div class="request-actions"> | ||||||
|  |         <button class="btn-approve" data-request-id="${request.id}"> | ||||||
|  |           ✓ Approve | ||||||
|  |         </button> | ||||||
|  |         <button class="btn-reject" data-request-id="${request.id}"> | ||||||
|  |           ✗ Reject | ||||||
|  |         </button> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   `; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Add event listeners to request action buttons | ||||||
|  | function addRequestEventListeners() { | ||||||
|  |   // Approve buttons | ||||||
|  |   document.querySelectorAll('.btn-approve').forEach(btn => { | ||||||
|  |     btn.addEventListener('click', async (e) => { | ||||||
|  |       const requestId = e.target.getAttribute('data-request-id'); | ||||||
|  |       await approveSignRequest(requestId); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   // Reject buttons | ||||||
|  |   document.querySelectorAll('.btn-reject').forEach(btn => { | ||||||
|  |     btn.addEventListener('click', async (e) => { | ||||||
|  |       const requestId = e.target.getAttribute('data-request-id'); | ||||||
|  |       await rejectSignRequest(requestId); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Handle new sign request notification | ||||||
|  | function handleNewSignRequest(message) { | ||||||
|  |   // Update requests list | ||||||
|  |   if (message.pendingRequests) { | ||||||
|  |     updateRequestsList(message.pendingRequests); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Show notification if workspace doesn't match | ||||||
|  |   if (!message.canApprove) { | ||||||
|  |     showWorkspaceMismatchWarning(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Handle keyspace unlocked event | ||||||
|  | function handleKeypaceUnlocked(message) { | ||||||
|  |   // Update requests list | ||||||
|  |   if (message.pendingRequests) { | ||||||
|  |     updateRequestsList(message.pendingRequests); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Update button states based on whether requests can be approved | ||||||
|  |   updateRequestButtonStates(message.canApprove); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Show workspace mismatch warning | ||||||
|  | function showWorkspaceMismatchWarning() { | ||||||
|  |   const existingWarning = document.querySelector('.workspace-mismatch'); | ||||||
|  |   if (existingWarning) return; // Don't show multiple warnings | ||||||
|  |  | ||||||
|  |   const warning = document.createElement('div'); | ||||||
|  |   warning.className = 'workspace-mismatch'; | ||||||
|  |   warning.innerHTML = ` | ||||||
|  |     ⚠️ Sign requests received for a different workspace. | ||||||
|  |     Switch to the correct workspace to approve requests. | ||||||
|  |   `; | ||||||
|  |  | ||||||
|  |   if (sigSocketElements.requestsContainer) { | ||||||
|  |     sigSocketElements.requestsContainer.insertBefore(warning, sigSocketElements.requestsContainer.firstChild); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Auto-remove warning after 10 seconds | ||||||
|  |   setTimeout(() => { | ||||||
|  |     warning.remove(); | ||||||
|  |   }, 10000); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Update request button states | ||||||
|  | function updateRequestButtonStates(canApprove) { | ||||||
|  |   document.querySelectorAll('.btn-approve, .btn-reject').forEach(btn => { | ||||||
|  |     btn.disabled = !canApprove; | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Approve a sign request | ||||||
|  | async function approveSignRequest(requestId) { | ||||||
|  |   try { | ||||||
|  |     const button = document.querySelector(`[data-request-id="${requestId}"].btn-approve`); | ||||||
|  |     setButtonLoading(button, true); | ||||||
|  |  | ||||||
|  |     const response = await sendMessage('approveSignRequest', { requestId }); | ||||||
|  |  | ||||||
|  |     if (response?.success) { | ||||||
|  |       showToast('Request approved and signed!', 'success'); | ||||||
|  |       await refreshSigSocketRequests(); | ||||||
|  |     } else { | ||||||
|  |       throw new Error(getResponseError(response, 'approve request')); | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     showToast(`Failed to approve request: ${error.message}`, 'error'); | ||||||
|  |   } finally { | ||||||
|  |     const button = document.querySelector(`[data-request-id="${requestId}"].btn-approve`); | ||||||
|  |     setButtonLoading(button, false); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Reject a sign request | ||||||
|  | async function rejectSignRequest(requestId) { | ||||||
|  |   try { | ||||||
|  |     const button = document.querySelector(`[data-request-id="${requestId}"].btn-reject`); | ||||||
|  |     setButtonLoading(button, true); | ||||||
|  |  | ||||||
|  |     const response = await sendMessage('rejectSignRequest', { | ||||||
|  |       requestId, | ||||||
|  |       reason: 'User rejected via extension' | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     if (response?.success) { | ||||||
|  |       showToast('Request rejected', 'info'); | ||||||
|  |       await refreshSigSocketRequests(); | ||||||
|  |     } else { | ||||||
|  |       throw new Error(getResponseError(response, 'reject request')); | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     showToast(`Failed to reject request: ${error.message}`, 'error'); | ||||||
|  |   } finally { | ||||||
|  |     const button = document.querySelector(`[data-request-id="${requestId}"].btn-reject`); | ||||||
|  |     setButtonLoading(button, false); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Refresh SigSocket requests | ||||||
|  | async function refreshSigSocketRequests() { | ||||||
|  |   try { | ||||||
|  |     setButtonLoading(sigSocketElements.refreshRequestsBtn, true); | ||||||
|  |  | ||||||
|  |     const response = await sendMessage('getPendingSignRequests'); | ||||||
|  |     if (response?.success) { | ||||||
|  |       updateRequestsList(response.requests); | ||||||
|  |       showToast('Requests refreshed', 'success'); | ||||||
|  |     } else { | ||||||
|  |       throw new Error(getResponseError(response, 'refresh requests')); | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     showToast(`Failed to refresh requests: ${error.message}`, 'error'); | ||||||
|  |   } finally { | ||||||
|  |     setButtonLoading(sigSocketElements.refreshRequestsBtn, false); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Show SigSocket status | ||||||
|  | async function showSigSocketStatus() { | ||||||
|  |   try { | ||||||
|  |     const response = await sendMessage('getSigSocketStatus'); | ||||||
|  |     if (response?.success) { | ||||||
|  |       const status = response.status; | ||||||
|  |       const statusText = ` | ||||||
|  | SigSocket Status: | ||||||
|  | • Connected: ${status.isConnected ? 'Yes' : 'No'} | ||||||
|  | • Workspace: ${status.workspace || 'None'} | ||||||
|  | • Public Key: ${status.publicKey ? status.publicKey.substring(0, 16) + '...' : 'None'} | ||||||
|  | • Pending Requests: ${status.pendingRequestCount || 0} | ||||||
|  | • Server URL: ${status.serverUrl} | ||||||
|  |       `.trim(); | ||||||
|  |  | ||||||
|  |       showToast(statusText, 'info'); | ||||||
|  |       updateConnectionStatus(status); | ||||||
|  |     } else { | ||||||
|  |       throw new Error(getResponseError(response, 'get status')); | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     showToast(`Failed to get status: ${error.message}`, 'error'); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1069,4 +1069,183 @@ input::placeholder, textarea::placeholder { | |||||||
| .verification-icon svg { | .verification-icon svg { | ||||||
|   width: 20px; |   width: 20px; | ||||||
|   height: 20px; |   height: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* SigSocket Requests Styles */ | ||||||
|  | .sigsocket-section { | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .section-header { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: center; | ||||||
|  |   margin-bottom: 15px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .section-header h3 { | ||||||
|  |   margin: 0; | ||||||
|  |   font-size: 16px; | ||||||
|  |   font-weight: 600; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .connection-status { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   gap: 6px; | ||||||
|  |   font-size: 12px; | ||||||
|  |   color: var(--text-secondary); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .status-dot { | ||||||
|  |   width: 8px; | ||||||
|  |   height: 8px; | ||||||
|  |   border-radius: 50%; | ||||||
|  |   background: var(--accent-error); | ||||||
|  |   transition: background-color 0.3s ease; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .status-dot.connected { | ||||||
|  |   background: var(--accent-success); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .requests-container { | ||||||
|  |   min-height: 80px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .empty-state { | ||||||
|  |   text-align: center; | ||||||
|  |   padding: 20px; | ||||||
|  |   color: var(--text-secondary); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .empty-icon { | ||||||
|  |   font-size: 24px; | ||||||
|  |   margin-bottom: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .empty-state p { | ||||||
|  |   margin: 0 0 4px 0; | ||||||
|  |   font-weight: 500; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .empty-state small { | ||||||
|  |   font-size: 11px; | ||||||
|  |   opacity: 0.8; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-item { | ||||||
|  |   border: 1px solid var(--border-color); | ||||||
|  |   border-radius: 8px; | ||||||
|  |   padding: 12px; | ||||||
|  |   margin-bottom: 8px; | ||||||
|  |   background: var(--bg-secondary); | ||||||
|  |   transition: all 0.2s ease; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-item:hover { | ||||||
|  |   border-color: var(--border-focus); | ||||||
|  |   box-shadow: 0 2px 8px hsla(var(--primary-hue), var(--primary-saturation), 55%, 0.1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-header { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: flex-start; | ||||||
|  |   margin-bottom: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-id { | ||||||
|  |   font-family: 'Courier New', monospace; | ||||||
|  |   font-size: 11px; | ||||||
|  |   color: var(--text-secondary); | ||||||
|  |   background: var(--bg-input); | ||||||
|  |   padding: 2px 6px; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   max-width: 120px; | ||||||
|  |   overflow: hidden; | ||||||
|  |   text-overflow: ellipsis; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-time { | ||||||
|  |   font-size: 11px; | ||||||
|  |   color: var(--text-secondary); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-message { | ||||||
|  |   margin: 8px 0; | ||||||
|  |   padding: 8px; | ||||||
|  |   background: var(--bg-input); | ||||||
|  |   border-radius: 4px; | ||||||
|  |   font-family: 'Courier New', monospace; | ||||||
|  |   font-size: 11px; | ||||||
|  |   word-break: break-all; | ||||||
|  |   max-height: 60px; | ||||||
|  |   overflow-y: auto; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-actions { | ||||||
|  |   display: flex; | ||||||
|  |   gap: 8px; | ||||||
|  |   margin-top: 12px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-approve { | ||||||
|  |   background: var(--accent-success); | ||||||
|  |   color: white; | ||||||
|  |   border: none; | ||||||
|  |   padding: 6px 12px; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   font-size: 12px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   transition: background-color 0.2s ease; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-approve:hover { | ||||||
|  |   background: hsl(var(--accent-hue), 65%, 40%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-reject { | ||||||
|  |   background: var(--accent-error); | ||||||
|  |   color: white; | ||||||
|  |   border: none; | ||||||
|  |   padding: 6px 12px; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   font-size: 12px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   transition: background-color 0.2s ease; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-reject:hover { | ||||||
|  |   background: hsl(0, 70%, 50%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-approve:disabled, | ||||||
|  | .btn-reject:disabled { | ||||||
|  |   opacity: 0.5; | ||||||
|  |   cursor: not-allowed; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .sigsocket-actions { | ||||||
|  |   display: flex; | ||||||
|  |   gap: 8px; | ||||||
|  |   margin-top: 12px; | ||||||
|  |   padding-top: 12px; | ||||||
|  |   border-top: 1px solid var(--border-color); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .workspace-mismatch { | ||||||
|  |   background: hsla(35, 85%, 85%, 0.8); | ||||||
|  |   border: 1px solid hsla(35, 85%, 70%, 0.5); | ||||||
|  |   color: hsl(35, 70%, 30%); | ||||||
|  |   padding: 8px; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   font-size: 12px; | ||||||
|  |   margin-bottom: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | [data-theme="dark"] .workspace-mismatch { | ||||||
|  |   background: hsla(35, 60%, 15%, 0.8); | ||||||
|  |   border-color: hsla(35, 60%, 30%, 0.5); | ||||||
|  |   color: hsl(35, 70%, 70%); | ||||||
| } | } | ||||||
| @@ -277,6 +277,42 @@ export function is_unlocked() { | |||||||
|     return ret !== 0; |     return ret !== 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Get the default public key for a workspace (keyspace) | ||||||
|  |  * This returns the public key of the first keypair in the keyspace | ||||||
|  |  * @param {string} workspace_id | ||||||
|  |  * @returns {Promise<any>} | ||||||
|  |  */ | ||||||
|  | export function get_workspace_default_public_key(workspace_id) { | ||||||
|  |     const ptr0 = passStringToWasm0(workspace_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |     const len0 = WASM_VECTOR_LEN; | ||||||
|  |     const ret = wasm.get_workspace_default_public_key(ptr0, len0); | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Get the current unlocked public key as hex string | ||||||
|  |  * @returns {string} | ||||||
|  |  */ | ||||||
|  | export function get_current_unlocked_public_key() { | ||||||
|  |     let deferred2_0; | ||||||
|  |     let deferred2_1; | ||||||
|  |     try { | ||||||
|  |         const ret = wasm.get_current_unlocked_public_key(); | ||||||
|  |         var ptr1 = ret[0]; | ||||||
|  |         var len1 = ret[1]; | ||||||
|  |         if (ret[3]) { | ||||||
|  |             ptr1 = 0; len1 = 0; | ||||||
|  |             throw takeFromExternrefTable0(ret[2]); | ||||||
|  |         } | ||||||
|  |         deferred2_0 = ptr1; | ||||||
|  |         deferred2_1 = len1; | ||||||
|  |         return getStringFromWasm0(ptr1, len1); | ||||||
|  |     } finally { | ||||||
|  |         wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Get all keypairs from the current session |  * Get all keypairs from the current session | ||||||
|  * Returns an array of keypair objects with id, type, and metadata |  * Returns an array of keypair objects with id, type, and metadata | ||||||
| @@ -323,7 +359,7 @@ function passArray8ToWasm0(arg, malloc) { | |||||||
|     return ptr; |     return ptr; | ||||||
| } | } | ||||||
| /** | /** | ||||||
|  * Sign message with current session |  * Sign message with current session (requires selected keypair) | ||||||
|  * @param {Uint8Array} message |  * @param {Uint8Array} message | ||||||
|  * @returns {Promise<any>} |  * @returns {Promise<any>} | ||||||
|  */ |  */ | ||||||
| @@ -334,6 +370,41 @@ export function sign(message) { | |||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Get the current keyspace name | ||||||
|  |  * @returns {string} | ||||||
|  |  */ | ||||||
|  | export function get_current_keyspace_name() { | ||||||
|  |     let deferred2_0; | ||||||
|  |     let deferred2_1; | ||||||
|  |     try { | ||||||
|  |         const ret = wasm.get_current_keyspace_name(); | ||||||
|  |         var ptr1 = ret[0]; | ||||||
|  |         var len1 = ret[1]; | ||||||
|  |         if (ret[3]) { | ||||||
|  |             ptr1 = 0; len1 = 0; | ||||||
|  |             throw takeFromExternrefTable0(ret[2]); | ||||||
|  |         } | ||||||
|  |         deferred2_0 = ptr1; | ||||||
|  |         deferred2_1 = len1; | ||||||
|  |         return getStringFromWasm0(ptr1, len1); | ||||||
|  |     } finally { | ||||||
|  |         wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Sign message with default keypair (first keypair in keyspace) without changing session state | ||||||
|  |  * @param {Uint8Array} message | ||||||
|  |  * @returns {Promise<any>} | ||||||
|  |  */ | ||||||
|  | export function sign_with_default_keypair(message) { | ||||||
|  |     const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc); | ||||||
|  |     const len0 = WASM_VECTOR_LEN; | ||||||
|  |     const ret = wasm.sign_with_default_keypair(ptr0, len0); | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Verify a signature with the current session's selected keypair |  * Verify a signature with the current session's selected keypair | ||||||
|  * @param {Uint8Array} message |  * @param {Uint8Array} message | ||||||
| @@ -395,24 +466,391 @@ export function run_rhai(script) { | |||||||
|     return takeFromExternrefTable0(ret[0]); |     return takeFromExternrefTable0(ret[0]); | ||||||
| } | } | ||||||
|  |  | ||||||
| function __wbg_adapter_32(arg0, arg1, arg2) { | function __wbg_adapter_34(arg0, arg1, arg2) { | ||||||
|     wasm.closure121_externref_shim(arg0, arg1, arg2); |     wasm.closure174_externref_shim(arg0, arg1, arg2); | ||||||
| } | } | ||||||
|  |  | ||||||
| function __wbg_adapter_35(arg0, arg1, arg2) { | function __wbg_adapter_39(arg0, arg1) { | ||||||
|     wasm.closure150_externref_shim(arg0, arg1, arg2); |     wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha4436a3f79fb1a0f(arg0, arg1); | ||||||
| } | } | ||||||
|  |  | ||||||
| function __wbg_adapter_38(arg0, arg1, arg2) { | function __wbg_adapter_44(arg0, arg1, arg2) { | ||||||
|     wasm.closure227_externref_shim(arg0, arg1, arg2); |     wasm.closure237_externref_shim(arg0, arg1, arg2); | ||||||
| } | } | ||||||
|  |  | ||||||
| function __wbg_adapter_138(arg0, arg1, arg2, arg3) { | function __wbg_adapter_49(arg0, arg1) { | ||||||
|     wasm.closure1879_externref_shim(arg0, arg1, arg2, arg3); |     wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf148c54a4a246cea(arg0, arg1); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function __wbg_adapter_52(arg0, arg1, arg2) { | ||||||
|  |     wasm.closure308_externref_shim(arg0, arg1, arg2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function __wbg_adapter_55(arg0, arg1, arg2) { | ||||||
|  |     wasm.closure392_externref_shim(arg0, arg1, arg2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function __wbg_adapter_207(arg0, arg1, arg2, arg3) { | ||||||
|  |     wasm.closure2046_externref_shim(arg0, arg1, arg2, arg3); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const __wbindgen_enum_BinaryType = ["blob", "arraybuffer"]; | ||||||
|  |  | ||||||
| const __wbindgen_enum_IdbTransactionMode = ["readonly", "readwrite", "versionchange", "readwriteflush", "cleanup"]; | const __wbindgen_enum_IdbTransactionMode = ["readonly", "readwrite", "versionchange", "readwriteflush", "cleanup"]; | ||||||
|  |  | ||||||
|  | const SigSocketConnectionFinalization = (typeof FinalizationRegistry === 'undefined') | ||||||
|  |     ? { register: () => {}, unregister: () => {} } | ||||||
|  |     : new FinalizationRegistry(ptr => wasm.__wbg_sigsocketconnection_free(ptr >>> 0, 1)); | ||||||
|  | /** | ||||||
|  |  * 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 | ||||||
|  |  */ | ||||||
|  | export class SigSocketConnection { | ||||||
|  |  | ||||||
|  |     __destroy_into_raw() { | ||||||
|  |         const ptr = this.__wbg_ptr; | ||||||
|  |         this.__wbg_ptr = 0; | ||||||
|  |         SigSocketConnectionFinalization.unregister(this); | ||||||
|  |         return ptr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     free() { | ||||||
|  |         const ptr = this.__destroy_into_raw(); | ||||||
|  |         wasm.__wbg_sigsocketconnection_free(ptr, 0); | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * Create a new SigSocket connection | ||||||
|  |      */ | ||||||
|  |     constructor() { | ||||||
|  |         const ret = wasm.sigsocketconnection_new(); | ||||||
|  |         this.__wbg_ptr = ret >>> 0; | ||||||
|  |         SigSocketConnectionFinalization.register(this, this.__wbg_ptr, this); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * 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 | ||||||
|  |      * @param {string} server_url | ||||||
|  |      * @param {string} public_key_hex | ||||||
|  |      * @returns {Promise<void>} | ||||||
|  |      */ | ||||||
|  |     connect(server_url, public_key_hex) { | ||||||
|  |         const ptr0 = passStringToWasm0(server_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len0 = WASM_VECTOR_LEN; | ||||||
|  |         const ptr1 = passStringToWasm0(public_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len1 = WASM_VECTOR_LEN; | ||||||
|  |         const ret = wasm.sigsocketconnection_connect(this.__wbg_ptr, ptr0, len0, ptr1, len1); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * 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 | ||||||
|  |      * @param {string} request_id | ||||||
|  |      * @param {string} message_base64 | ||||||
|  |      * @param {string} signature_hex | ||||||
|  |      * @returns {Promise<void>} | ||||||
|  |      */ | ||||||
|  |     send_response(request_id, message_base64, signature_hex) { | ||||||
|  |         const ptr0 = passStringToWasm0(request_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len0 = WASM_VECTOR_LEN; | ||||||
|  |         const ptr1 = passStringToWasm0(message_base64, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len1 = WASM_VECTOR_LEN; | ||||||
|  |         const ptr2 = passStringToWasm0(signature_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len2 = WASM_VECTOR_LEN; | ||||||
|  |         const ret = wasm.sigsocketconnection_send_response(this.__wbg_ptr, ptr0, len0, ptr1, len1, ptr2, len2); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * 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 | ||||||
|  |      * @param {string} request_id | ||||||
|  |      * @param {string} reason | ||||||
|  |      * @returns {Promise<void>} | ||||||
|  |      */ | ||||||
|  |     send_rejection(request_id, reason) { | ||||||
|  |         const ptr0 = passStringToWasm0(request_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len0 = WASM_VECTOR_LEN; | ||||||
|  |         const ptr1 = passStringToWasm0(reason, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len1 = WASM_VECTOR_LEN; | ||||||
|  |         const ret = wasm.sigsocketconnection_send_rejection(this.__wbg_ptr, ptr0, len0, ptr1, len1); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * Disconnect from the SigSocket server | ||||||
|  |      */ | ||||||
|  |     disconnect() { | ||||||
|  |         wasm.sigsocketconnection_disconnect(this.__wbg_ptr); | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * Check if connected to the server | ||||||
|  |      * @returns {boolean} | ||||||
|  |      */ | ||||||
|  |     is_connected() { | ||||||
|  |         const ret = wasm.sigsocketconnection_is_connected(this.__wbg_ptr); | ||||||
|  |         return ret !== 0; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const SigSocketManagerFinalization = (typeof FinalizationRegistry === 'undefined') | ||||||
|  |     ? { register: () => {}, unregister: () => {} } | ||||||
|  |     : new FinalizationRegistry(ptr => wasm.__wbg_sigsocketmanager_free(ptr >>> 0, 1)); | ||||||
|  | /** | ||||||
|  |  * SigSocket manager for high-level operations | ||||||
|  |  */ | ||||||
|  | export class SigSocketManager { | ||||||
|  |  | ||||||
|  |     __destroy_into_raw() { | ||||||
|  |         const ptr = this.__wbg_ptr; | ||||||
|  |         this.__wbg_ptr = 0; | ||||||
|  |         SigSocketManagerFinalization.unregister(this); | ||||||
|  |         return ptr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     free() { | ||||||
|  |         const ptr = this.__destroy_into_raw(); | ||||||
|  |         wasm.__wbg_sigsocketmanager_free(ptr, 0); | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * Connect to SigSocket server with smart connection management | ||||||
|  |      * | ||||||
|  |      * This handles all connection logic: | ||||||
|  |      * - Reuses existing connection if same workspace | ||||||
|  |      * - Switches connection if different workspace | ||||||
|  |      * - Creates new connection if none exists | ||||||
|  |      * | ||||||
|  |      * # 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 | ||||||
|  |      * @param {string} workspace | ||||||
|  |      * @param {string} server_url | ||||||
|  |      * @param {Function} event_callback | ||||||
|  |      * @returns {Promise<string>} | ||||||
|  |      */ | ||||||
|  |     static connect_workspace_with_events(workspace, server_url, event_callback) { | ||||||
|  |         const ptr0 = passStringToWasm0(workspace, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len0 = WASM_VECTOR_LEN; | ||||||
|  |         const ptr1 = passStringToWasm0(server_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len1 = WASM_VECTOR_LEN; | ||||||
|  |         const ret = wasm.sigsocketmanager_connect_workspace_with_events(ptr0, len0, ptr1, len1, event_callback); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * 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 | ||||||
|  |      * @param {string} workspace | ||||||
|  |      * @param {string} server_url | ||||||
|  |      * @returns {Promise<string>} | ||||||
|  |      */ | ||||||
|  |     static connect_workspace(workspace, server_url) { | ||||||
|  |         const ptr0 = passStringToWasm0(workspace, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len0 = WASM_VECTOR_LEN; | ||||||
|  |         const ptr1 = passStringToWasm0(server_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len1 = WASM_VECTOR_LEN; | ||||||
|  |         const ret = wasm.sigsocketmanager_connect_workspace(ptr0, len0, ptr1, len1); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * Disconnect from SigSocket server | ||||||
|  |      * | ||||||
|  |      * # Returns | ||||||
|  |      * * `Ok(())` - Successfully disconnected | ||||||
|  |      * * `Err(error)` - If disconnect failed | ||||||
|  |      * @returns {Promise<void>} | ||||||
|  |      */ | ||||||
|  |     static disconnect() { | ||||||
|  |         const ret = wasm.sigsocketmanager_disconnect(); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * 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 | ||||||
|  |      * @param {string} request_id | ||||||
|  |      * @returns {Promise<boolean>} | ||||||
|  |      */ | ||||||
|  |     static can_approve_request(request_id) { | ||||||
|  |         const ptr0 = passStringToWasm0(request_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len0 = WASM_VECTOR_LEN; | ||||||
|  |         const ret = wasm.sigsocketmanager_can_approve_request(ptr0, len0); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * 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 | ||||||
|  |      * @param {string} request_id | ||||||
|  |      * @returns {Promise<string>} | ||||||
|  |      */ | ||||||
|  |     static approve_request(request_id) { | ||||||
|  |         const ptr0 = passStringToWasm0(request_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len0 = WASM_VECTOR_LEN; | ||||||
|  |         const ret = wasm.sigsocketmanager_approve_request(ptr0, len0); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * 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 | ||||||
|  |      * @param {string} request_id | ||||||
|  |      * @param {string} reason | ||||||
|  |      * @returns {Promise<void>} | ||||||
|  |      */ | ||||||
|  |     static reject_request(request_id, reason) { | ||||||
|  |         const ptr0 = passStringToWasm0(request_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len0 = WASM_VECTOR_LEN; | ||||||
|  |         const ptr1 = passStringToWasm0(reason, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len1 = WASM_VECTOR_LEN; | ||||||
|  |         const ret = wasm.sigsocketmanager_reject_request(ptr0, len0, ptr1, len1); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * 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 | ||||||
|  |      * @returns {Promise<string>} | ||||||
|  |      */ | ||||||
|  |     static get_filtered_requests() { | ||||||
|  |         const ret = wasm.sigsocketmanager_get_filtered_requests(); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * 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 | ||||||
|  |      * @param {string} request_json | ||||||
|  |      */ | ||||||
|  |     static add_pending_request(request_json) { | ||||||
|  |         const ptr0 = passStringToWasm0(request_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         const len0 = WASM_VECTOR_LEN; | ||||||
|  |         const ret = wasm.sigsocketmanager_add_pending_request(ptr0, len0); | ||||||
|  |         if (ret[1]) { | ||||||
|  |             throw takeFromExternrefTable0(ret[0]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * Get connection status | ||||||
|  |      * | ||||||
|  |      * # Returns | ||||||
|  |      * * `Ok(status_json)` - JSON object with connection status | ||||||
|  |      * * `Err(error)` - If getting status failed | ||||||
|  |      * @returns {string} | ||||||
|  |      */ | ||||||
|  |     static get_connection_status() { | ||||||
|  |         let deferred2_0; | ||||||
|  |         let deferred2_1; | ||||||
|  |         try { | ||||||
|  |             const ret = wasm.sigsocketmanager_get_connection_status(); | ||||||
|  |             var ptr1 = ret[0]; | ||||||
|  |             var len1 = ret[1]; | ||||||
|  |             if (ret[3]) { | ||||||
|  |                 ptr1 = 0; len1 = 0; | ||||||
|  |                 throw takeFromExternrefTable0(ret[2]); | ||||||
|  |             } | ||||||
|  |             deferred2_0 = ptr1; | ||||||
|  |             deferred2_1 = len1; | ||||||
|  |             return getStringFromWasm0(ptr1, len1); | ||||||
|  |         } finally { | ||||||
|  |             wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * Clear all pending requests | ||||||
|  |      * | ||||||
|  |      * # Returns | ||||||
|  |      * * `Ok(())` - Requests cleared successfully | ||||||
|  |      */ | ||||||
|  |     static clear_pending_requests() { | ||||||
|  |         const ret = wasm.sigsocketmanager_clear_pending_requests(); | ||||||
|  |         if (ret[1]) { | ||||||
|  |             throw takeFromExternrefTable0(ret[0]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| async function __wbg_load(module, imports) { | async function __wbg_load(module, imports) { | ||||||
|     if (typeof Response === 'function' && module instanceof Response) { |     if (typeof Response === 'function' && module instanceof Response) { | ||||||
|         if (typeof WebAssembly.instantiateStreaming === 'function') { |         if (typeof WebAssembly.instantiateStreaming === 'function') { | ||||||
| @@ -459,6 +897,9 @@ function __wbg_get_imports() { | |||||||
|         const ret = arg0.call(arg1, arg2); |         const ret = arg0.call(arg1, arg2); | ||||||
|         return ret; |         return ret; | ||||||
|     }, arguments) }; |     }, arguments) }; | ||||||
|  |     imports.wbg.__wbg_close_2893b7d056a0627d = function() { return handleError(function (arg0) { | ||||||
|  |         arg0.close(); | ||||||
|  |     }, arguments) }; | ||||||
|     imports.wbg.__wbg_createObjectStore_d2f9e1016f4d81b9 = function() { return handleError(function (arg0, arg1, arg2, arg3) { |     imports.wbg.__wbg_createObjectStore_d2f9e1016f4d81b9 = function() { return handleError(function (arg0, arg1, arg2, arg3) { | ||||||
|         const ret = arg0.createObjectStore(getStringFromWasm0(arg1, arg2), arg3); |         const ret = arg0.createObjectStore(getStringFromWasm0(arg1, arg2), arg3); | ||||||
|         return ret; |         return ret; | ||||||
| @@ -467,6 +908,10 @@ function __wbg_get_imports() { | |||||||
|         const ret = arg0.crypto; |         const ret = arg0.crypto; | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|  |     imports.wbg.__wbg_data_432d9c3df2630942 = function(arg0) { | ||||||
|  |         const ret = arg0.data; | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|     imports.wbg.__wbg_error_524f506f44df1645 = function(arg0) { |     imports.wbg.__wbg_error_524f506f44df1645 = function(arg0) { | ||||||
|         console.error(arg0); |         console.error(arg0); | ||||||
|     }; |     }; | ||||||
| @@ -539,10 +984,23 @@ function __wbg_get_imports() { | |||||||
|         const ret = result; |         const ret = result; | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|  |     imports.wbg.__wbg_instanceof_Window_def73ea0955fc569 = function(arg0) { | ||||||
|  |         let result; | ||||||
|  |         try { | ||||||
|  |             result = arg0 instanceof Window; | ||||||
|  |         } catch (_) { | ||||||
|  |             result = false; | ||||||
|  |         } | ||||||
|  |         const ret = result; | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|     imports.wbg.__wbg_length_52b6c4580c5ec934 = function(arg0) { |     imports.wbg.__wbg_length_52b6c4580c5ec934 = function(arg0) { | ||||||
|         const ret = arg0.length; |         const ret = arg0.length; | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|  |     imports.wbg.__wbg_log_c222819a41e063d3 = function(arg0) { | ||||||
|  |         console.log(arg0); | ||||||
|  |     }; | ||||||
|     imports.wbg.__wbg_msCrypto_a61aeb35a24c1329 = function(arg0) { |     imports.wbg.__wbg_msCrypto_a61aeb35a24c1329 = function(arg0) { | ||||||
|         const ret = arg0.msCrypto; |         const ret = arg0.msCrypto; | ||||||
|         return ret; |         return ret; | ||||||
| @@ -558,7 +1016,7 @@ function __wbg_get_imports() { | |||||||
|                 const a = state0.a; |                 const a = state0.a; | ||||||
|                 state0.a = 0; |                 state0.a = 0; | ||||||
|                 try { |                 try { | ||||||
|                     return __wbg_adapter_138(a, state0.b, arg0, arg1); |                     return __wbg_adapter_207(a, state0.b, arg0, arg1); | ||||||
|                 } finally { |                 } finally { | ||||||
|                     state0.a = a; |                     state0.a = a; | ||||||
|                 } |                 } | ||||||
| @@ -577,6 +1035,10 @@ function __wbg_get_imports() { | |||||||
|         const ret = new Array(); |         const ret = new Array(); | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|  |     imports.wbg.__wbg_new_92c54fc74574ef55 = function() { return handleError(function (arg0, arg1) { | ||||||
|  |         const ret = new WebSocket(getStringFromWasm0(arg0, arg1)); | ||||||
|  |         return ret; | ||||||
|  |     }, arguments) }; | ||||||
|     imports.wbg.__wbg_new_a12002a7f91c75be = function(arg0) { |     imports.wbg.__wbg_new_a12002a7f91c75be = function(arg0) { | ||||||
|         const ret = new Uint8Array(arg0); |         const ret = new Uint8Array(arg0); | ||||||
|         return ret; |         return ret; | ||||||
| @@ -609,6 +1071,12 @@ function __wbg_get_imports() { | |||||||
|         const ret = arg0.objectStore(getStringFromWasm0(arg1, arg2)); |         const ret = arg0.objectStore(getStringFromWasm0(arg1, arg2)); | ||||||
|         return ret; |         return ret; | ||||||
|     }, arguments) }; |     }, arguments) }; | ||||||
|  |     imports.wbg.__wbg_onConnectionStateChanged_b0dc098522afadba = function(arg0) { | ||||||
|  |         onConnectionStateChanged(arg0 !== 0); | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbg_onSignRequestReceived_93232ba7a0919705 = function(arg0, arg1, arg2, arg3) { | ||||||
|  |         onSignRequestReceived(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3)); | ||||||
|  |     }; | ||||||
|     imports.wbg.__wbg_open_88b1390d99a7c691 = function() { return handleError(function (arg0, arg1, arg2) { |     imports.wbg.__wbg_open_88b1390d99a7c691 = function() { return handleError(function (arg0, arg1, arg2) { | ||||||
|         const ret = arg0.open(getStringFromWasm0(arg1, arg2)); |         const ret = arg0.open(getStringFromWasm0(arg1, arg2)); | ||||||
|         return ret; |         return ret; | ||||||
| @@ -643,6 +1111,10 @@ function __wbg_get_imports() { | |||||||
|     imports.wbg.__wbg_randomFillSync_ac0988aba3254290 = function() { return handleError(function (arg0, arg1) { |     imports.wbg.__wbg_randomFillSync_ac0988aba3254290 = function() { return handleError(function (arg0, arg1) { | ||||||
|         arg0.randomFillSync(arg1); |         arg0.randomFillSync(arg1); | ||||||
|     }, arguments) }; |     }, arguments) }; | ||||||
|  |     imports.wbg.__wbg_readyState_7ef6e63c349899ed = function(arg0) { | ||||||
|  |         const ret = arg0.readyState; | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|     imports.wbg.__wbg_require_60cc747a6bc5215a = function() { return handleError(function () { |     imports.wbg.__wbg_require_60cc747a6bc5215a = function() { return handleError(function () { | ||||||
|         const ret = module.require; |         const ret = module.require; | ||||||
|         return ret; |         return ret; | ||||||
| @@ -655,12 +1127,38 @@ function __wbg_get_imports() { | |||||||
|         const ret = arg0.result; |         const ret = arg0.result; | ||||||
|         return ret; |         return ret; | ||||||
|     }, arguments) }; |     }, arguments) }; | ||||||
|  |     imports.wbg.__wbg_send_0293179ba074ffb4 = function() { return handleError(function (arg0, arg1, arg2) { | ||||||
|  |         arg0.send(getStringFromWasm0(arg1, arg2)); | ||||||
|  |     }, arguments) }; | ||||||
|  |     imports.wbg.__wbg_setTimeout_f2fe5af8e3debeb3 = function() { return handleError(function (arg0, arg1, arg2) { | ||||||
|  |         const ret = arg0.setTimeout(arg1, arg2); | ||||||
|  |         return ret; | ||||||
|  |     }, arguments) }; | ||||||
|     imports.wbg.__wbg_set_65595bdd868b3009 = function(arg0, arg1, arg2) { |     imports.wbg.__wbg_set_65595bdd868b3009 = function(arg0, arg1, arg2) { | ||||||
|         arg0.set(arg1, arg2 >>> 0); |         arg0.set(arg1, arg2 >>> 0); | ||||||
|     }; |     }; | ||||||
|  |     imports.wbg.__wbg_set_bb8cecf6a62b9f46 = function() { return handleError(function (arg0, arg1, arg2) { | ||||||
|  |         const ret = Reflect.set(arg0, arg1, arg2); | ||||||
|  |         return ret; | ||||||
|  |     }, arguments) }; | ||||||
|  |     imports.wbg.__wbg_setbinaryType_92fa1ffd873b327c = function(arg0, arg1) { | ||||||
|  |         arg0.binaryType = __wbindgen_enum_BinaryType[arg1]; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbg_setonclose_14fc475a49d488fc = function(arg0, arg1) { | ||||||
|  |         arg0.onclose = arg1; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbg_setonerror_8639efe354b947cd = function(arg0, arg1) { | ||||||
|  |         arg0.onerror = arg1; | ||||||
|  |     }; | ||||||
|     imports.wbg.__wbg_setonerror_d7e3056cc6e56085 = function(arg0, arg1) { |     imports.wbg.__wbg_setonerror_d7e3056cc6e56085 = function(arg0, arg1) { | ||||||
|         arg0.onerror = arg1; |         arg0.onerror = arg1; | ||||||
|     }; |     }; | ||||||
|  |     imports.wbg.__wbg_setonmessage_6eccab530a8fb4c7 = function(arg0, arg1) { | ||||||
|  |         arg0.onmessage = arg1; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbg_setonopen_2da654e1f39745d5 = function(arg0, arg1) { | ||||||
|  |         arg0.onopen = arg1; | ||||||
|  |     }; | ||||||
|     imports.wbg.__wbg_setonsuccess_afa464ee777a396d = function(arg0, arg1) { |     imports.wbg.__wbg_setonsuccess_afa464ee777a396d = function(arg0, arg1) { | ||||||
|         arg0.onsuccess = arg1; |         arg0.onsuccess = arg1; | ||||||
|     }; |     }; | ||||||
| @@ -695,6 +1193,10 @@ function __wbg_get_imports() { | |||||||
|         const ret = arg0.then(arg1); |         const ret = arg0.then(arg1); | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|  |     imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = arg0.then(arg1, arg2); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|     imports.wbg.__wbg_transaction_d6d07c3c9963c49e = function() { return handleError(function (arg0, arg1, arg2) { |     imports.wbg.__wbg_transaction_d6d07c3c9963c49e = function() { return handleError(function (arg0, arg1, arg2) { | ||||||
|         const ret = arg0.transaction(arg1, __wbindgen_enum_IdbTransactionMode[arg2]); |         const ret = arg0.transaction(arg1, __wbindgen_enum_IdbTransactionMode[arg2]); | ||||||
|         return ret; |         return ret; | ||||||
| @@ -703,6 +1205,9 @@ function __wbg_get_imports() { | |||||||
|         const ret = arg0.versions; |         const ret = arg0.versions; | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|  |     imports.wbg.__wbg_warn_4ca3906c248c47c4 = function(arg0) { | ||||||
|  |         console.warn(arg0); | ||||||
|  |     }; | ||||||
|     imports.wbg.__wbindgen_cb_drop = function(arg0) { |     imports.wbg.__wbindgen_cb_drop = function(arg0) { | ||||||
|         const obj = arg0.original; |         const obj = arg0.original; | ||||||
|         if (obj.cnt-- == 1) { |         if (obj.cnt-- == 1) { | ||||||
| @@ -712,16 +1217,40 @@ function __wbg_get_imports() { | |||||||
|         const ret = false; |         const ret = false; | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|     imports.wbg.__wbindgen_closure_wrapper378 = function(arg0, arg1, arg2) { |     imports.wbg.__wbindgen_closure_wrapper1015 = function(arg0, arg1, arg2) { | ||||||
|         const ret = makeMutClosure(arg0, arg1, 122, __wbg_adapter_32); |         const ret = makeMutClosure(arg0, arg1, 309, __wbg_adapter_52); | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|     imports.wbg.__wbindgen_closure_wrapper549 = function(arg0, arg1, arg2) { |     imports.wbg.__wbindgen_closure_wrapper1320 = function(arg0, arg1, arg2) { | ||||||
|         const ret = makeMutClosure(arg0, arg1, 151, __wbg_adapter_35); |         const ret = makeMutClosure(arg0, arg1, 393, __wbg_adapter_55); | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|     imports.wbg.__wbindgen_closure_wrapper857 = function(arg0, arg1, arg2) { |     imports.wbg.__wbindgen_closure_wrapper423 = function(arg0, arg1, arg2) { | ||||||
|         const ret = makeMutClosure(arg0, arg1, 228, __wbg_adapter_38); |         const ret = makeMutClosure(arg0, arg1, 172, __wbg_adapter_34); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper424 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 172, __wbg_adapter_34); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper425 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 172, __wbg_adapter_39); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper428 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 172, __wbg_adapter_34); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper766 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 238, __wbg_adapter_44); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper767 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 238, __wbg_adapter_44); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper770 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 238, __wbg_adapter_49); | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|     imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { |     imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { | ||||||
| @@ -778,6 +1307,14 @@ function __wbg_get_imports() { | |||||||
|         const ret = wasm.memory; |         const ret = wasm.memory; | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|  |     imports.wbg.__wbindgen_string_get = function(arg0, arg1) { | ||||||
|  |         const obj = arg1; | ||||||
|  |         const ret = typeof(obj) === 'string' ? obj : undefined; | ||||||
|  |         var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |         var len1 = WASM_VECTOR_LEN; | ||||||
|  |         getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); | ||||||
|  |         getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); | ||||||
|  |     }; | ||||||
|     imports.wbg.__wbindgen_string_new = function(arg0, arg1) { |     imports.wbg.__wbindgen_string_new = function(arg0, arg1) { | ||||||
|         const ret = getStringFromWasm0(arg0, arg1); |         const ret = getStringFromWasm0(arg0, arg1); | ||||||
|         return ret; |         return ret; | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| @@ -525,7 +525,13 @@ impl WasmClient { | |||||||
|  |  | ||||||
| impl Drop for WasmClient { | impl Drop for WasmClient { | ||||||
|     fn drop(&mut self) { |     fn drop(&mut self) { | ||||||
|         // Cleanup will be handled by the WebSocket close |         // Close WebSocket connection if it exists | ||||||
|  |         if let Some(ws) = self.websocket.take() { | ||||||
|  |             ws.close().unwrap_or_else(|e| { | ||||||
|  |                 web_sys::console::warn_1(&format!("Failed to close WebSocket: {:?}", e).into()); | ||||||
|  |             }); | ||||||
|  |             web_sys::console::log_1(&"🔌 WebSocket connection closed on drop".into()); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ use wasm_bindgen::prelude::*; | |||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use sigsocket_client::{SigSocketClient, SignRequest, SignRequestHandler, Result as SigSocketResult, SigSocketError}; | use sigsocket_client::{SigSocketClient, SignRequest, SignRequestHandler, Result as SigSocketResult, SigSocketError}; | ||||||
| use web_sys::console; | use web_sys::console; | ||||||
|  | use base64::prelude::*; | ||||||
|  |  | ||||||
| use crate::vault_bindings::{get_workspace_default_public_key, get_current_keyspace_name, is_unlocked, sign_with_default_keypair}; | use crate::vault_bindings::{get_workspace_default_public_key, get_current_keyspace_name, is_unlocked, sign_with_default_keypair}; | ||||||
|  |  | ||||||
| @@ -31,7 +32,32 @@ impl ExtensionNotificationHandler { | |||||||
|  |  | ||||||
| impl SignRequestHandler for ExtensionNotificationHandler { | impl SignRequestHandler for ExtensionNotificationHandler { | ||||||
|     fn handle_sign_request(&self, request: &SignRequest) -> SigSocketResult<Vec<u8>> { |     fn handle_sign_request(&self, request: &SignRequest) -> SigSocketResult<Vec<u8>> { | ||||||
|         // Create event object for JavaScript |         console_log!("📨 WASM: Handling sign request: {}", request.id); | ||||||
|  |  | ||||||
|  |         // First, store the request in the WASM client | ||||||
|  |         let store_result = SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |             let mut client_opt = c.borrow_mut(); | ||||||
|  |             if let Some(client) = client_opt.as_mut() { | ||||||
|  |                 // Get the connected public key as the target | ||||||
|  |                 if let Some(target_public_key) = client.connected_public_key() { | ||||||
|  |                     client.add_pending_request(request.clone(), target_public_key.to_string()); | ||||||
|  |                     console_log!("✅ WASM: Stored sign request: {}", request.id); | ||||||
|  |                     Ok(()) | ||||||
|  |                 } else { | ||||||
|  |                     Err(SigSocketError::Other("No connected public key".to_string())) | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Err(SigSocketError::Other("No SigSocket client available".to_string())) | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // If storage failed, return error | ||||||
|  |         if let Err(e) = store_result { | ||||||
|  |             console_log!("❌ WASM: Failed to store request: {:?}", e); | ||||||
|  |             return Err(e); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Create event object for JavaScript notification | ||||||
|         let event = js_sys::Object::new(); |         let event = js_sys::Object::new(); | ||||||
|         js_sys::Reflect::set(&event, &"type".into(), &"sign_request".into()) |         js_sys::Reflect::set(&event, &"type".into(), &"sign_request".into()) | ||||||
|             .map_err(|_| SigSocketError::Other("Failed to set event type".to_string()))?; |             .map_err(|_| SigSocketError::Other("Failed to set event type".to_string()))?; | ||||||
| @@ -40,17 +66,16 @@ impl SignRequestHandler for ExtensionNotificationHandler { | |||||||
|         js_sys::Reflect::set(&event, &"message".into(), &request.message.clone().into()) |         js_sys::Reflect::set(&event, &"message".into(), &request.message.clone().into()) | ||||||
|             .map_err(|_| SigSocketError::Other("Failed to set message".to_string()))?; |             .map_err(|_| SigSocketError::Other("Failed to set message".to_string()))?; | ||||||
|  |  | ||||||
|         // Store the request in our pending requests (this will be done by the client) |         // Notify the extension | ||||||
|         // and notify the extension |  | ||||||
|         match self.callback.call1(&wasm_bindgen::JsValue::NULL, &event) { |         match self.callback.call1(&wasm_bindgen::JsValue::NULL, &event) { | ||||||
|             Ok(_) => { |             Ok(_) => { | ||||||
|                 console_log!("Notified extension about sign request: {}", request.id); |                 console_log!("✅ WASM: Notified extension about sign request: {}", request.id); | ||||||
|                 // Return an error to indicate this request should not be auto-signed |                 // Return an error to indicate this request should not be auto-signed | ||||||
|                 // The extension will handle the approval flow |                 // The extension will handle the approval flow | ||||||
|                 Err(SigSocketError::Other("Request forwarded to extension for approval".to_string())) |                 Err(SigSocketError::Other("Request forwarded to extension for approval".to_string())) | ||||||
|             } |             } | ||||||
|             Err(e) => { |             Err(e) => { | ||||||
|                 console_log!("Failed to notify extension: {:?}", e); |                 console_log!("❌ WASM: Failed to notify extension: {:?}", e); | ||||||
|                 Err(SigSocketError::Other("Extension notification failed".to_string())) |                 Err(SigSocketError::Other("Extension notification failed".to_string())) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -72,10 +97,12 @@ pub struct SigSocketManager; | |||||||
|  |  | ||||||
| #[wasm_bindgen] | #[wasm_bindgen] | ||||||
| impl SigSocketManager { | impl SigSocketManager { | ||||||
|     /// Connect to SigSocket server with a specific workspace and event callback |     /// Connect to SigSocket server with smart connection management | ||||||
|     /// |     /// | ||||||
|     /// This establishes a real WebSocket connection using the workspace's default public key |     /// This handles all connection logic: | ||||||
|     /// and integrates with the vault system for security validation. |     /// - Reuses existing connection if same workspace | ||||||
|  |     /// - Switches connection if different workspace | ||||||
|  |     /// - Creates new connection if none exists | ||||||
|     /// |     /// | ||||||
|     /// # Arguments |     /// # Arguments | ||||||
|     /// * `workspace` - The workspace name to connect with |     /// * `workspace` - The workspace name to connect with | ||||||
| @@ -98,25 +125,56 @@ impl SigSocketManager { | |||||||
|         let public_key_bytes = hex::decode(&public_key_hex) |         let public_key_bytes = hex::decode(&public_key_hex) | ||||||
|             .map_err(|e| JsValue::from_str(&format!("Invalid public key format: {}", e)))?; |             .map_err(|e| JsValue::from_str(&format!("Invalid public key format: {}", e)))?; | ||||||
|          |          | ||||||
|         // 3. Create SigSocket client with extension notification handler |         // 3. Check if already connected to same workspace and handle disconnection | ||||||
|         let mut client = SigSocketClient::new(server_url, public_key_bytes) |         let should_connect = SIGSOCKET_CLIENT.with(|c| { | ||||||
|             .map_err(|e| JsValue::from_str(&format!("Failed to create client: {:?}", e)))?; |             let mut client_opt = c.borrow_mut(); | ||||||
|  |  | ||||||
|         // Set up extension notification handler using existing API |             // Check if we already have a client for this workspace | ||||||
|         let handler = ExtensionNotificationHandler::new(event_callback.clone()); |             if let Some(existing_client) = client_opt.as_ref() { | ||||||
|         client.set_sign_handler(handler); |                 if let Some(existing_key) = existing_client.connected_public_key() { | ||||||
|  |                     if existing_key == hex::encode(&public_key_bytes) && existing_client.is_connected() { | ||||||
|  |                         console_log!("🔄 WASM: Already connected to workspace: {}", workspace); | ||||||
|  |                         return false; // Reuse existing connection | ||||||
|  |                     } else { | ||||||
|  |                         console_log!("🔄 WASM: Switching workspace from {} to {}", | ||||||
|  |                                    existing_key, hex::encode(&public_key_bytes)); | ||||||
|  |  | ||||||
|         // 4. Connect to the WebSocket server |                         // Disconnect the old client | ||||||
|         client.connect().await |                         *client_opt = None; // This will drop the old client and close WebSocket | ||||||
|             .map_err(|e| JsValue::from_str(&format!("Connection failed: {:?}", e)))?; |                         console_log!("🔌 WASM: Disconnected from old workspace"); | ||||||
|  |  | ||||||
|         console_log!("SigSocket connected successfully to {}", server_url); |                         return true; // Need new connection | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|         // 5. Store the connected client |             true // Need new connection, no old one to disconnect | ||||||
|         SIGSOCKET_CLIENT.with(|c| { |  | ||||||
|             *c.borrow_mut() = Some(client); |  | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         // 4. Create and connect if needed | ||||||
|  |         if should_connect { | ||||||
|  |             console_log!("🔗 WASM: Creating new connection for workspace: {}", workspace); | ||||||
|  |  | ||||||
|  |             // Create new client | ||||||
|  |             let mut client = SigSocketClient::new(server_url, public_key_bytes.clone()) | ||||||
|  |                 .map_err(|e| JsValue::from_str(&format!("Failed to create client: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |             // Set up extension notification handler | ||||||
|  |             let handler = ExtensionNotificationHandler::new(event_callback.clone()); | ||||||
|  |             client.set_sign_handler(handler); | ||||||
|  |  | ||||||
|  |             // Connect to the WebSocket server | ||||||
|  |             client.connect().await | ||||||
|  |                 .map_err(|e| JsValue::from_str(&format!("Connection failed: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |             console_log!("✅ WASM: Connected to SigSocket server for workspace: {}", workspace); | ||||||
|  |  | ||||||
|  |             // Store the connected client | ||||||
|  |             SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |                 *c.borrow_mut() = Some(client); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // 6. Return connection info |         // 6. Return connection info | ||||||
|         let connection_info = SigSocketConnectionInfo { |         let connection_info = SigSocketConnectionInfo { | ||||||
|             workspace: workspace.to_string(), |             workspace: workspace.to_string(), | ||||||
| @@ -150,7 +208,7 @@ impl SigSocketManager { | |||||||
|     } |     } | ||||||
|      |      | ||||||
|     /// Disconnect from SigSocket server |     /// Disconnect from SigSocket server | ||||||
|     ///  |     /// | ||||||
|     /// # Returns |     /// # Returns | ||||||
|     /// * `Ok(())` - Successfully disconnected |     /// * `Ok(())` - Successfully disconnected | ||||||
|     /// * `Err(error)` - If disconnect failed |     /// * `Err(error)` - If disconnect failed | ||||||
| @@ -158,9 +216,16 @@ impl SigSocketManager { | |||||||
|     pub async fn disconnect() -> Result<(), JsValue> { |     pub async fn disconnect() -> Result<(), JsValue> { | ||||||
|         SIGSOCKET_CLIENT.with(|c| { |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|             let mut client_opt = c.borrow_mut(); |             let mut client_opt = c.borrow_mut(); | ||||||
|             if let Some(_client) = client_opt.take() { |             if let Some(client) = client_opt.take() { | ||||||
|                 // client.disconnect().await?; // Will be async in real implementation |                 let workspace_info = client.connected_public_key() | ||||||
|                 console_log!("SigSocket client disconnected"); |                     .map(|key| key[..16].to_string()) | ||||||
|  |                     .unwrap_or_else(|| "unknown".to_string()); | ||||||
|  |  | ||||||
|  |                 // Dropping the client will close the WebSocket connection | ||||||
|  |                 drop(client); | ||||||
|  |                 console_log!("🔌 WASM: Disconnected SigSocket client (was: {}...)", workspace_info); | ||||||
|  |             } else { | ||||||
|  |                 console_log!("🔌 WASM: No SigSocket client to disconnect"); | ||||||
|             } |             } | ||||||
|             Ok(()) |             Ok(()) | ||||||
|         }) |         }) | ||||||
| @@ -254,27 +319,51 @@ impl SigSocketManager { | |||||||
|          |          | ||||||
|         // 3. Sign with vault |         // 3. Sign with vault | ||||||
|         let signature_result = sign_with_default_keypair(&message_bytes).await?; |         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()) |         let signature_hex = signature_result.as_string() | ||||||
|             .map_err(|e| JsValue::from_str(&format!("Failed to parse signature: {}", e)))?; |             .ok_or_else(|| JsValue::from_str("Signature result is not a string"))?; | ||||||
|  |  | ||||||
|  |         // Convert hex signature to base64 for SigSocket protocol | ||||||
|  |         let signature_bytes = hex::decode(&signature_hex) | ||||||
|  |             .map_err(|e| JsValue::from_str(&format!("Invalid hex signature: {}", e)))?; | ||||||
|  |         let signature_base64 = base64::prelude::BASE64_STANDARD.encode(&signature_bytes); | ||||||
|          |          | ||||||
|         let signature_base64 = signature_obj["signature"].as_str() |         // 4. Get original message for response | ||||||
|             .ok_or_else(|| JsValue::from_str("Invalid signature format"))?; |         let original_message = SIGSOCKET_CLIENT.with(|c| { | ||||||
|          |             let client = c.borrow(); | ||||||
|         // 4. Send response to server and remove request |             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"))?; | ||||||
|  |  | ||||||
|  |             Ok::<String, JsValue>(request.request.message.clone()) | ||||||
|  |         })?; | ||||||
|  |  | ||||||
|  |         // 5. Send response to server (create a new scope to avoid borrowing issues) | ||||||
|  |         { | ||||||
|  |             let client_ref = SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |                 c.borrow().as_ref().map(|client| client as *const SigSocketClient) | ||||||
|  |             }).ok_or_else(|| JsValue::from_str("Not connected"))?; | ||||||
|  |  | ||||||
|  |             // SAFETY: We know the client exists and we're using it synchronously | ||||||
|  |             let client = unsafe { &*client_ref }; | ||||||
|  |  | ||||||
|  |             client.send_response(request_id, &original_message, &signature_base64).await | ||||||
|  |                 .map_err(|e| JsValue::from_str(&format!("Failed to send response: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |             console_log!("✅ WASM: Sent signature response to server for request: {}", request_id); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 6. Remove the request after successful send | ||||||
|         SIGSOCKET_CLIENT.with(|c| { |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|             let mut client = c.borrow_mut(); |             let mut client = c.borrow_mut(); | ||||||
|             let client = client.as_mut().ok_or_else(|| JsValue::from_str("Not connected"))?; |             if let Some(client) = client.as_mut() { | ||||||
|              |                 client.remove_pending_request(request_id); | ||||||
|             // Send response (will be async in real implementation) |                 console_log!("✅ WASM: Removed request from pending list: {}", request_id); | ||||||
|             // client.send_response(request_id, &original_request.message, signature_base64).await?; |             } | ||||||
|              |         }); | ||||||
|             // Remove the request |  | ||||||
|             client.remove_pending_request(request_id); |         console_log!("🎉 WASM: Successfully approved and sent signature for request: {}", request_id); | ||||||
|              |         Ok(signature_base64) | ||||||
|             console_log!("Approved and sent signature for request: {}", request_id); |  | ||||||
|              |  | ||||||
|             Ok(signature_base64.to_string()) |  | ||||||
|         }) |  | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     /// Reject a sign request |     /// Reject a sign request | ||||||
| @@ -288,20 +377,32 @@ impl SigSocketManager { | |||||||
|     /// * `Err(error)` - If rejection failed |     /// * `Err(error)` - If rejection failed | ||||||
|     #[wasm_bindgen] |     #[wasm_bindgen] | ||||||
|     pub async fn reject_request(request_id: &str, reason: &str) -> Result<(), JsValue> { |     pub async fn reject_request(request_id: &str, reason: &str) -> Result<(), JsValue> { | ||||||
|  |         // Send rejection to server first | ||||||
|  |         { | ||||||
|  |             let client_ref = SIGSOCKET_CLIENT.with(|c| { | ||||||
|  |                 c.borrow().as_ref().map(|client| client as *const SigSocketClient) | ||||||
|  |             }).ok_or_else(|| JsValue::from_str("Not connected"))?; | ||||||
|  |  | ||||||
|  |             // SAFETY: We know the client exists and we're using it synchronously | ||||||
|  |             let client = unsafe { &*client_ref }; | ||||||
|  |  | ||||||
|  |             client.send_rejection(request_id, reason).await | ||||||
|  |                 .map_err(|e| JsValue::from_str(&format!("Failed to send rejection: {:?}", e)))?; | ||||||
|  |  | ||||||
|  |             console_log!("✅ WASM: Sent rejection to server for request: {}", request_id); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Remove the request after successful send | ||||||
|         SIGSOCKET_CLIENT.with(|c| { |         SIGSOCKET_CLIENT.with(|c| { | ||||||
|             let mut client = c.borrow_mut(); |             let mut client = c.borrow_mut(); | ||||||
|             let client = client.as_mut().ok_or_else(|| JsValue::from_str("Not connected"))?; |             if let Some(client) = client.as_mut() { | ||||||
|  |                 client.remove_pending_request(request_id); | ||||||
|  |                 console_log!("✅ WASM: Removed rejected request from pending list: {}", request_id); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|             // Send rejection (will be async in real implementation) |         console_log!("🚫 WASM: Successfully rejected request: {} (reason: {})", request_id, reason); | ||||||
|             // client.send_rejection(request_id, reason).await?; |         Ok(()) | ||||||
|  |  | ||||||
|             // Remove the request |  | ||||||
|             client.remove_pending_request(request_id); |  | ||||||
|  |  | ||||||
|             console_log!("Rejected request {}: {}", request_id, reason); |  | ||||||
|  |  | ||||||
|             Ok(()) |  | ||||||
|         }) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Get pending requests filtered by current workspace |     /// Get pending requests filtered by current workspace | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user