/** * SigSocket Service - Clean Implementation with New WASM APIs * * This service provides a clean interface for SigSocket functionality using * the new WASM-based APIs that handle all WebSocket management, request storage, * and security validation internally. * * Architecture: * - WASM handles: WebSocket connection, message parsing, request storage, security * - Extension handles: UI notifications, badge updates, user interactions */ class SigSocketService { constructor() { // Connection state this.isConnected = false; this.currentWorkspace = null; this.connectedPublicKey = null; // Configuration this.defaultServerUrl = "ws://localhost:8080/ws"; // WASM module reference this.wasmModule = null; // UI communication this.popupPort = null; } /** * Initialize the service with WASM module * @param {Object} wasmModule - The loaded WASM module with SigSocketManager */ async initialize(wasmModule) { this.wasmModule = wasmModule; // Load server URL from storage try { const result = await chrome.storage.local.get(['sigSocketUrl']); if (result.sigSocketUrl) { this.defaultServerUrl = result.sigSocketUrl; } } catch (error) { console.warn('Failed to load SigSocket URL from storage:', error); } console.log('🔌 SigSocket service initialized with WASM APIs'); } /** * Connect to SigSocket server using WASM APIs * WASM handles all connection logic (reuse, switching, etc.) * @param {string} workspaceId - The workspace/keyspace identifier * @returns {Promise} - True if connected successfully */ async connectToServer(workspaceId) { try { if (!this.wasmModule?.SigSocketManager) { throw new Error('WASM SigSocketManager not available'); } console.log(`🔗 Requesting SigSocket connection for workspace: ${workspaceId}`); // Let WASM handle all connection logic (reuse, switching, etc.) const connectionInfo = await this.wasmModule.SigSocketManager.connect_workspace_with_events( workspaceId, this.defaultServerUrl, (event) => this.handleSigSocketEvent(event) ); // Parse connection info const info = JSON.parse(connectionInfo); this.currentWorkspace = info.workspace; this.connectedPublicKey = info.public_key; this.isConnected = info.is_connected; console.log(`✅ SigSocket connection result:`, { workspace: this.currentWorkspace, publicKey: this.connectedPublicKey?.substring(0, 16) + '...', connected: this.isConnected }); // Update badge to show current state this.updateBadge(); return this.isConnected; } catch (error) { console.error('❌ SigSocket connection failed:', error); this.isConnected = false; this.currentWorkspace = null; this.connectedPublicKey = null; return false; } } /** * Handle events from the WASM SigSocket client * This is called automatically when requests arrive * @param {Object} event - Event from WASM layer */ handleSigSocketEvent(event) { console.log('📨 Received SigSocket event:', event); if (event.type === 'sign_request') { console.log(`🔐 New sign request: ${event.request_id}`); // The request is automatically stored by WASM // We just handle UI updates this.showSignRequestNotification(); this.updateBadge(); this.notifyPopupOfNewRequest(); } } /** * Approve a sign request using WASM APIs * @param {string} requestId - Request to approve * @returns {Promise} - True if approved successfully */ async approveSignRequest(requestId) { try { if (!this.wasmModule?.SigSocketManager) { throw new Error('WASM SigSocketManager not available'); } console.log(`✅ Approving request: ${requestId}`); // WASM handles all validation, signing, and server communication await this.wasmModule.SigSocketManager.approve_request(requestId); console.log(`🎉 Request approved successfully: ${requestId}`); // Update UI this.updateBadge(); this.notifyPopupOfRequestUpdate(); return true; } catch (error) { console.error(`❌ Failed to approve request ${requestId}:`, error); return false; } } /** * Reject a sign request using WASM APIs * @param {string} requestId - Request to reject * @param {string} reason - Reason for rejection * @returns {Promise} - True if rejected successfully */ async rejectSignRequest(requestId, reason = 'User rejected') { try { if (!this.wasmModule?.SigSocketManager) { throw new Error('WASM SigSocketManager not available'); } console.log(`❌ Rejecting request: ${requestId}, reason: ${reason}`); // WASM handles rejection and server communication await this.wasmModule.SigSocketManager.reject_request(requestId, reason); console.log(`✅ Request rejected successfully: ${requestId}`); // Update UI this.updateBadge(); this.notifyPopupOfRequestUpdate(); return true; } catch (error) { console.error(`❌ Failed to reject request ${requestId}:`, error); return false; } } /** * Get pending requests from WASM (filtered by current workspace) * @returns {Promise} - Array of pending requests for current workspace */ async getPendingRequests() { return this.getFilteredRequests(); } /** * Get filtered requests from WASM (workspace-aware) * @returns {Promise} - Array of filtered requests */ async getFilteredRequests() { try { if (!this.wasmModule?.SigSocketManager) { return []; } const requestsJson = await this.wasmModule.SigSocketManager.get_filtered_requests(); const requests = JSON.parse(requestsJson); console.log(`📋 Retrieved ${requests.length} filtered requests for current workspace`); 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} - 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 */ showSignRequestNotification() { try { if (chrome.notifications && chrome.notifications.create) { chrome.notifications.create({ type: 'basic', iconUrl: 'icons/icon48.png', 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); } } /** * 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() { if (!this.popupPort) { console.log('No popup connected, skipping notification'); return; } try { const requests = await this.getPendingRequests(); const canApprove = requests.length > 0 ? await this.canApproveRequest(requests[0].id) : false; this.popupPort.postMessage({ type: 'NEW_SIGN_REQUEST', canApprove, pendingRequests: requests }); console.log(`📤 Notified popup: ${requests.length} requests, canApprove: ${canApprove}`); } catch (error) { console.error('Failed to notify popup:', error); } } /** * Notify popup about request updates */ async notifyPopupOfRequestUpdate() { if (!this.popupPort) return; try { const requests = await this.getPendingRequests(); this.popupPort.postMessage({ type: 'REQUESTS_UPDATED', pendingRequests: requests }); } catch (error) { console.error('Failed to notify popup of update:', error); } } /** * Disconnect from SigSocket server * WASM handles all disconnection logic */ async disconnect() { try { if (this.wasmModule?.SigSocketManager) { 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); } } /** * Get connection status from WASM * @returns {Promise} - Connection status information */ async getStatus() { try { if (!this.wasmModule?.SigSocketManager) { return { isConnected: false, 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 }; } } /** * Set the popup port for communication * @param {chrome.runtime.Port} port - The popup port */ setPopupPort(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 default SigSocketService;