feat: Implement Sign Request Manager component for handling sign requests in the popup (WIP)
- Added SignRequestManager.js to manage sign requests, including UI states for keyspace lock, mismatch, and approval. - Implemented methods for loading state, rendering UI, and handling user interactions (approve/reject requests). - Integrated background message listeners for keyspace unlock events and request updates.
This commit is contained in:
		| @@ -5,6 +5,7 @@ let keepAliveInterval = null; | |||||||
| let sessionTimeoutDuration = 15; // Default 15 seconds | 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 | ||||||
|  | let sigSocketService = null; // SigSocket service instance | ||||||
|  |  | ||||||
| // Utility function to convert Uint8Array to hex | // Utility function to convert Uint8Array to hex | ||||||
| function toHex(uint8Array) { | function toHex(uint8Array) { | ||||||
| @@ -138,6 +139,9 @@ async function restoreSession() { | |||||||
| // Import WASM module functions | // Import WASM module functions | ||||||
| import init, * as wasmFunctions from './wasm/wasm_app.js'; | import init, * as wasmFunctions from './wasm/wasm_app.js'; | ||||||
|  |  | ||||||
|  | // Import SigSocket service | ||||||
|  | import SigSocketService from './background/sigsocket.js'; | ||||||
|  |  | ||||||
| // Initialize WASM module | // Initialize WASM module | ||||||
| async function initVault() { | async function initVault() { | ||||||
|   try { |   try { | ||||||
| @@ -151,6 +155,9 @@ async function initVault() { | |||||||
|     vault = wasmFunctions; |     vault = wasmFunctions; | ||||||
|     isInitialized = true; |     isInitialized = true; | ||||||
|  |  | ||||||
|  |     // Initialize SigSocket service | ||||||
|  |     await initSigSocketService(); | ||||||
|  |  | ||||||
|     // Try to restore previous session |     // Try to restore previous session | ||||||
|     await restoreSession(); |     await restoreSession(); | ||||||
|  |  | ||||||
| @@ -161,6 +168,17 @@ async function initVault() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Initialize SigSocket service | ||||||
|  | async function initSigSocketService() { | ||||||
|  |   try { | ||||||
|  |     sigSocketService = new SigSocketService(); | ||||||
|  |     await sigSocketService.initialize(vault); | ||||||
|  |     console.log('SigSocket service initialized'); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('Failed to initialize SigSocket service:', error); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| // Consolidated message handlers | // Consolidated message handlers | ||||||
| const messageHandlers = { | const messageHandlers = { | ||||||
| @@ -172,6 +190,16 @@ 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); | ||||||
|  |  | ||||||
|  |     // Auto-connect to SigSocket server when session is initialized (only if not already connected) | ||||||
|  |     if (sigSocketService && !sigSocketService.isConnected) { | ||||||
|  |       console.log(`Attempting to connect to SigSocket for keyspace: ${request.keyspace}`); | ||||||
|  |       const connected = await sigSocketService.connectToServer(request.keyspace); | ||||||
|  |       console.log(`SigSocket connection result: ${connected}`); | ||||||
|  |     } else if (sigSocketService && sigSocketService.isConnected) { | ||||||
|  |       console.log('SigSocket already connected, skipping connection attempt'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return { success: true }; |     return { success: true }; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
| @@ -261,6 +289,52 @@ 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 message handlers | ||||||
|  |   connectSigSocket: async (request) => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not available' }; | ||||||
|  |     } | ||||||
|  |     const connected = await sigSocketService.connectToServer(request.workspaceId); | ||||||
|  |     return { success: connected }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   getPendingRequests: () => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not available' }; | ||||||
|  |     } | ||||||
|  |     return { success: true, requests: sigSocketService.getPendingRequests() }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   keypaceUnlocked: async () => { | ||||||
|  |     if (sigSocketService) { | ||||||
|  |       await sigSocketService.onKeypaceUnlocked(); | ||||||
|  |     } | ||||||
|  |     return { success: true }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   approveSignRequest: async (request) => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not available' }; | ||||||
|  |     } | ||||||
|  |     const approved = await sigSocketService.approveSignRequest(request.requestId); | ||||||
|  |     return { success: approved }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   rejectSignRequest: async (request) => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not available' }; | ||||||
|  |     } | ||||||
|  |     const rejected = await sigSocketService.rejectSignRequest(request.requestId, request.reason); | ||||||
|  |     return { success: rejected }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   getSigSocketStatus: () => { | ||||||
|  |     if (!sigSocketService) { | ||||||
|  |       return { success: false, error: 'SigSocket service not available' }; | ||||||
|  |     } | ||||||
|  |     return { success: true, status: sigSocketService.getStatus() }; | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -302,6 +376,11 @@ chrome.runtime.onConnect.addListener((port) => { | |||||||
|     // Track popup connection |     // Track popup connection | ||||||
|     popupPort = port; |     popupPort = port; | ||||||
|  |  | ||||||
|  |     // Set popup port in SigSocket service | ||||||
|  |     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(); | ||||||
| @@ -310,6 +389,9 @@ chrome.runtime.onConnect.addListener((port) => { | |||||||
|     port.onDisconnect.addListener(() => { |     port.onDisconnect.addListener(() => { | ||||||
|       // Popup closed, clear reference and stop keep-alive |       // Popup closed, clear reference and stop keep-alive | ||||||
|       popupPort = null; |       popupPort = null; | ||||||
|  |       if (sigSocketService) { | ||||||
|  |         sigSocketService.setPopupPort(null); | ||||||
|  |       } | ||||||
|       stopKeepAlive(); |       stopKeepAlive(); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										476
									
								
								crypto_vault_extension/background/sigsocket.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										476
									
								
								crypto_vault_extension/background/sigsocket.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,476 @@ | |||||||
|  | /** | ||||||
|  |  * SigSocket Service for Browser Extension | ||||||
|  |  *  | ||||||
|  |  * Handles SigSocket client functionality including: | ||||||
|  |  * - Auto-connecting to SigSocket server when workspace is created | ||||||
|  |  * - Managing pending sign requests | ||||||
|  |  * - Handling user approval/rejection flow | ||||||
|  |  * - Validating keyspace matches before showing approval UI | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | class SigSocketService { | ||||||
|  |     constructor() { | ||||||
|  |         this.connection = null; | ||||||
|  |         this.pendingRequests = new Map(); // requestId -> SignRequestData | ||||||
|  |         this.connectedPublicKey = null; | ||||||
|  |         this.isConnected = false; | ||||||
|  |         this.defaultServerUrl = "ws://localhost:8080/ws"; | ||||||
|  |  | ||||||
|  |         // Initialize WASM module reference | ||||||
|  |         this.wasmModule = null; | ||||||
|  |  | ||||||
|  |         // Reference to popup port for communication | ||||||
|  |         this.popupPort = null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initialize the service with WASM module | ||||||
|  |      * @param {Object} wasmModule - The loaded WASM module | ||||||
|  |      */ | ||||||
|  |     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); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Set up global callbacks for WASM | ||||||
|  |         globalThis.onSignRequestReceived = this.handleIncomingRequest.bind(this); | ||||||
|  |         globalThis.onConnectionStateChanged = this.handleConnectionStateChange.bind(this); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Connect to SigSocket server for a workspace | ||||||
|  |      * @param {string} workspaceId - The workspace/keyspace identifier | ||||||
|  |      * @returns {Promise<boolean>} - True if connected successfully | ||||||
|  |      */ | ||||||
|  |     async connectToServer(workspaceId) { | ||||||
|  |         try { | ||||||
|  |             if (!this.wasmModule) { | ||||||
|  |                 throw new Error('WASM module not initialized'); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Check if already connected to this workspace | ||||||
|  |             if (this.isConnected && this.connection) { | ||||||
|  |                 console.log(`Already connected to SigSocket server for workspace: ${workspaceId}`); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Disconnect any existing connection first | ||||||
|  |             if (this.connection) { | ||||||
|  |                 this.disconnect(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Get the workspace default public key | ||||||
|  |             const publicKeyHex = await this.wasmModule.get_workspace_default_public_key(workspaceId); | ||||||
|  |             if (!publicKeyHex) { | ||||||
|  |                 throw new Error('No public key found for workspace'); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             console.log(`Connecting to SigSocket server for workspace: ${workspaceId} with key: ${publicKeyHex.substring(0, 16)}...`); | ||||||
|  |  | ||||||
|  |             // Create new SigSocket connection | ||||||
|  |             console.log('Creating new SigSocketConnection instance'); | ||||||
|  |             this.connection = new this.wasmModule.SigSocketConnection(); | ||||||
|  |             console.log('SigSocketConnection instance created'); | ||||||
|  |  | ||||||
|  |             // Connect to server | ||||||
|  |             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) { | ||||||
|  |             console.error('Failed to connect to SigSocket server:', error); | ||||||
|  |             this.isConnected = false; | ||||||
|  |             this.connectedPublicKey = null; | ||||||
|  |             this.currentWorkspace = null; | ||||||
|  |             if (this.connection) { | ||||||
|  |                 this.connection.disconnect(); | ||||||
|  |                 this.connection = null; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handle incoming sign request from server | ||||||
|  |      * @param {string} requestId - Unique request identifier | ||||||
|  |      * @param {string} messageBase64 - Message to be signed (base64-encoded) | ||||||
|  |      */ | ||||||
|  |     handleIncomingRequest(requestId, messageBase64) { | ||||||
|  |         console.log(`Received sign request: ${requestId}`); | ||||||
|  |  | ||||||
|  |         // Security check: Only accept requests if we have an active connection | ||||||
|  |         if (!this.isConnected || !this.connectedPublicKey || !this.currentWorkspace) { | ||||||
|  |             console.warn(`Rejecting sign request ${requestId}: No active workspace connection`); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Store the request with workspace info | ||||||
|  |         const requestData = { | ||||||
|  |             id: requestId, | ||||||
|  |             message: messageBase64, | ||||||
|  |             timestamp: Date.now(), | ||||||
|  |             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 | ||||||
|  |      */ | ||||||
|  |     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 | ||||||
|  |      * @returns {Promise<boolean>} - True if approved successfully | ||||||
|  |      */ | ||||||
|  |     async approveSignRequest(requestId) { | ||||||
|  |         try { | ||||||
|  |             const request = this.pendingRequests.get(requestId); | ||||||
|  |             if (!request) { | ||||||
|  |                 throw new Error('Request not found'); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Validate request is for current workspace | ||||||
|  |             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) { | ||||||
|  |                 throw new Error('Request public key does not match current connection'); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Validate keyspace is still unlocked and matches | ||||||
|  |             if (!this.wasmModule.is_unlocked()) { | ||||||
|  |                 throw new Error('Keyspace is locked'); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const currentPublicKey = await this.wasmModule.get_workspace_default_public_key(this.currentWorkspace); | ||||||
|  |             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(); | ||||||
|  |              | ||||||
|  |             console.log(`Approved sign request: ${requestId}`); | ||||||
|  |             return true; | ||||||
|  |              | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to approve sign request:', error); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Reject a sign request | ||||||
|  |      * @param {string} requestId - Request to reject | ||||||
|  |      * @param {string} reason - Reason for rejection (optional) | ||||||
|  |      * @returns {Promise<boolean>} - True if rejected successfully | ||||||
|  |      */ | ||||||
|  |     async rejectSignRequest(requestId, reason = 'User rejected') { | ||||||
|  |         try { | ||||||
|  |             const request = this.pendingRequests.get(requestId); | ||||||
|  |             if (!request) { | ||||||
|  |                 throw new Error('Request not found'); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Send rejection to server | ||||||
|  |             await this.connection.send_rejection(requestId, reason); | ||||||
|  |              | ||||||
|  |             // Update request status | ||||||
|  |             request.status = 'rejected'; | ||||||
|  |             request.reason = reason; | ||||||
|  |              | ||||||
|  |             // Remove from pending requests | ||||||
|  |             this.pendingRequests.delete(requestId); | ||||||
|  |              | ||||||
|  |             // Update badge | ||||||
|  |             this.updateBadge(); | ||||||
|  |              | ||||||
|  |             console.log(`Rejected sign request: ${requestId}, reason: ${reason}`); | ||||||
|  |             return true; | ||||||
|  |              | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to reject sign request:', error); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Get all pending requests for the current workspace | ||||||
|  |      * @returns {Array} - Array of pending request data for current workspace | ||||||
|  |      */ | ||||||
|  |     getPendingRequests() { | ||||||
|  |         const allRequests = Array.from(this.pendingRequests.values()); | ||||||
|  |  | ||||||
|  |         // Filter requests to only include those for the current workspace | ||||||
|  |         const filteredRequests = allRequests.filter(request => { | ||||||
|  |             const isCurrentWorkspace = request.workspace === this.currentWorkspace; | ||||||
|  |             const isCurrentPublicKey = request.connectedPublicKey === this.connectedPublicKey; | ||||||
|  |  | ||||||
|  |             if (!isCurrentWorkspace || !isCurrentPublicKey) { | ||||||
|  |                 console.log(`Filtering out request ${request.id}: workspace=${request.workspace} (current=${this.currentWorkspace}), publicKey match=${isCurrentPublicKey}`); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return isCurrentWorkspace && isCurrentPublicKey; | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         console.log(`getPendingRequests: ${allRequests.length} total, ${filteredRequests.length} for current workspace`); | ||||||
|  |         return filteredRequests; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Show notification for new sign request | ||||||
|  |      */ | ||||||
|  |     showSignRequestNotification() { | ||||||
|  |         // Create notification | ||||||
|  |         chrome.notifications.create({ | ||||||
|  |             type: 'basic', | ||||||
|  |             iconUrl: 'icons/icon48.png', | ||||||
|  |             title: 'Sign Request', | ||||||
|  |             message: 'New signature request received. Click to review.' | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Notify popup about new request if popup is open and keyspace is unlocked | ||||||
|  |      */ | ||||||
|  |     async notifyPopupOfNewRequest() { | ||||||
|  |         // Only notify if popup is connected | ||||||
|  |         if (!this.popupPort) { | ||||||
|  |             console.log('No popup port available, skipping new request 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; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             // Check if keyspace is unlocked | ||||||
|  |             if (!this.wasmModule.is_unlocked()) { | ||||||
|  |                 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({ | ||||||
|  |                 type: 'NEW_SIGN_REQUEST', | ||||||
|  |                 keypaceMatches, | ||||||
|  |                 pendingRequests: requestsToSend | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             console.log(`Sent NEW_SIGN_REQUEST message to popup: keypaceMatches=${keypaceMatches}, ${requestsToSend.length} requests (${currentPendingRequests.length} total for connected workspace)`); | ||||||
|  |  | ||||||
|  |         } catch (error) { | ||||||
|  |             console.log('Error in notifyPopupOfNewRequest:', error); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Update extension badge with pending request count for current workspace | ||||||
|  |      */ | ||||||
|  |     updateBadge() { | ||||||
|  |         // Only count requests for the current workspace | ||||||
|  |         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}"`); | ||||||
|  |  | ||||||
|  |         chrome.action.setBadgeText({ text: badgeText }); | ||||||
|  |         chrome.action.setBadgeBackgroundColor({ color: '#ff6b6b' }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Disconnect from SigSocket server | ||||||
|  |      */ | ||||||
|  |     disconnect() { | ||||||
|  |         if (this.connection) { | ||||||
|  |             this.connection.disconnect(); | ||||||
|  |             this.connection = null; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         this.isConnected = false; | ||||||
|  |         this.connectedPublicKey = null; | ||||||
|  |         this.currentWorkspace = null; | ||||||
|  |         this.pendingRequests.clear(); | ||||||
|  |         this.updateBadge(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Get connection status | ||||||
|  |      * @returns {Object} - Connection status information | ||||||
|  |      */ | ||||||
|  |     getStatus() { | ||||||
|  |         return { | ||||||
|  |             isConnected: this.isConnected, | ||||||
|  |             connectedPublicKey: this.connectedPublicKey, | ||||||
|  |             pendingRequestCount: this.getPendingRequests().length, | ||||||
|  |             serverUrl: this.defaultServerUrl | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Set the popup port for communication | ||||||
|  |      * @param {chrome.runtime.Port} port - The popup port | ||||||
|  |      */ | ||||||
|  |     setPopupPort(port) { | ||||||
|  |         this.popupPort = port; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Export for use in background script | ||||||
|  | export default SigSocketService; | ||||||
							
								
								
									
										75
									
								
								crypto_vault_extension/demo/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								crypto_vault_extension/demo/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | # Mock SigSocket Server Demo | ||||||
|  |  | ||||||
|  | This directory contains a mock SigSocket server for testing the browser extension functionality. | ||||||
|  |  | ||||||
|  | ## Setup | ||||||
|  |  | ||||||
|  | 1. Install dependencies: | ||||||
|  |    ```bash | ||||||
|  |    npm install | ||||||
|  |    ``` | ||||||
|  |  | ||||||
|  | 2. Start the mock server: | ||||||
|  |    ```bash | ||||||
|  |    npm start | ||||||
|  |    ``` | ||||||
|  |  | ||||||
|  | The server will listen on `ws://localhost:8080/ws` | ||||||
|  |  | ||||||
|  | ## Usage | ||||||
|  |  | ||||||
|  | ### Interactive Commands | ||||||
|  |  | ||||||
|  | Once the server is running, you can use these commands: | ||||||
|  |  | ||||||
|  | - `test` - Send a test sign request to all connected clients | ||||||
|  | - `status` - Show server status and connected clients | ||||||
|  | - `quit` - Shutdown the server | ||||||
|  |  | ||||||
|  | ### Testing Flow | ||||||
|  |  | ||||||
|  | 1. Start the mock server | ||||||
|  | 2. Load the browser extension in Chrome | ||||||
|  | 3. Create a keyspace and keypair in the extension | ||||||
|  | 4. The extension should automatically connect to the server | ||||||
|  | 5. The server will send a test sign request after 3 seconds | ||||||
|  | 6. Use the extension popup to approve or reject the request | ||||||
|  | 7. The server will log the response and send another request after 10 seconds | ||||||
|  |  | ||||||
|  | ### Expected Output | ||||||
|  |  | ||||||
|  | When a client connects: | ||||||
|  | ``` | ||||||
|  | New WebSocket connection from: ::1 | ||||||
|  | Received message: 04a8b2c3d4e5f6... | ||||||
|  | Client registered: client_1234567890_abc123 with public key: 04a8b2c3d4e5f6... | ||||||
|  | 📝 Sending sign request to client_1234567890_abc123: req_1_1234567890 | ||||||
|  |    Message: "Test message 1 - 2024-01-01T12:00:00.000Z" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | When a sign response is received: | ||||||
|  | ``` | ||||||
|  | Received sign response from client_1234567890_abc123: { | ||||||
|  |   id: 'req_1_1234567890', | ||||||
|  |   message: 'VGVzdCBtZXNzYWdlIDEgLSAyMDI0LTAxLTAxVDEyOjAwOjAwLjAwMFo=', | ||||||
|  |   signature: '3045022100...' | ||||||
|  | } | ||||||
|  | ✅ Sign request req_1_1234567890 completed successfully | ||||||
|  |    Signature: 3045022100... | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Protocol | ||||||
|  |  | ||||||
|  | The mock server implements a simplified version of the SigSocket protocol: | ||||||
|  |  | ||||||
|  | 1. **Client Introduction**: Client sends hex-encoded public key | ||||||
|  | 2. **Welcome Message**: Server responds with welcome JSON | ||||||
|  | 3. **Sign Requests**: Server sends JSON with `id` and `message` (base64) | ||||||
|  | 4. **Sign Responses**: Client sends JSON with `id`, `message`, and `signature` | ||||||
|  |  | ||||||
|  | ## Troubleshooting | ||||||
|  |  | ||||||
|  | - **Connection refused**: Make sure the server is running on port 8080 | ||||||
|  | - **No sign requests**: Check that the extension is properly connected | ||||||
|  | - **Extension errors**: Check the browser console for JavaScript errors | ||||||
|  | - **WASM errors**: Ensure the WASM files are properly built and loaded | ||||||
							
								
								
									
										232
									
								
								crypto_vault_extension/demo/mock_sigsocket_server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								crypto_vault_extension/demo/mock_sigsocket_server.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,232 @@ | |||||||
|  | #!/usr/bin/env node | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Mock SigSocket Server for Testing Browser Extension | ||||||
|  |  *  | ||||||
|  |  * This is a simple WebSocket server that simulates the SigSocket protocol | ||||||
|  |  * for testing the browser extension functionality. | ||||||
|  |  *  | ||||||
|  |  * Usage: | ||||||
|  |  *   node mock_sigsocket_server.js | ||||||
|  |  *  | ||||||
|  |  * The server will listen on ws://localhost:8080/ws | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | const WebSocket = require('ws'); | ||||||
|  | const http = require('http'); | ||||||
|  |  | ||||||
|  | class MockSigSocketServer { | ||||||
|  |     constructor(port = 8080) { | ||||||
|  |         this.port = port; | ||||||
|  |         this.clients = new Map(); // clientId -> { ws, publicKey } | ||||||
|  |         this.requestCounter = 0; | ||||||
|  |          | ||||||
|  |         this.setupServer(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     setupServer() { | ||||||
|  |         // Create HTTP server | ||||||
|  |         this.httpServer = http.createServer(); | ||||||
|  |          | ||||||
|  |         // Create WebSocket server | ||||||
|  |         this.wss = new WebSocket.Server({  | ||||||
|  |             server: this.httpServer, | ||||||
|  |             path: '/ws' | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         this.wss.on('connection', (ws, req) => { | ||||||
|  |             console.log('New WebSocket connection from:', req.socket.remoteAddress); | ||||||
|  |             this.handleConnection(ws); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         this.httpServer.listen(this.port, () => { | ||||||
|  |             console.log(`Mock SigSocket Server listening on ws://localhost:${this.port}/ws`); | ||||||
|  |             console.log('Waiting for browser extension connections...'); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     handleConnection(ws) { | ||||||
|  |         let clientId = null; | ||||||
|  |         let publicKey = null; | ||||||
|  |  | ||||||
|  |         ws.on('message', (data) => { | ||||||
|  |             try { | ||||||
|  |                 const message = data.toString(); | ||||||
|  |                 console.log('Received message:', message); | ||||||
|  |  | ||||||
|  |                 // Check if this is a client introduction (hex-encoded public key) | ||||||
|  |                 if (!clientId && this.isHexString(message)) { | ||||||
|  |                     publicKey = message; | ||||||
|  |                     clientId = this.generateClientId(); | ||||||
|  |                      | ||||||
|  |                     this.clients.set(clientId, { ws, publicKey }); | ||||||
|  |                      | ||||||
|  |                     console.log(`Client registered: ${clientId} with public key: ${publicKey.substring(0, 16)}...`); | ||||||
|  |                      | ||||||
|  |                     // Send welcome message | ||||||
|  |                     ws.send(JSON.stringify({ | ||||||
|  |                         type: 'welcome', | ||||||
|  |                         clientId: clientId, | ||||||
|  |                         message: 'Connected to Mock SigSocket Server' | ||||||
|  |                     })); | ||||||
|  |                      | ||||||
|  |                     // Schedule a test sign request after 3 seconds | ||||||
|  |                     setTimeout(() => { | ||||||
|  |                         this.sendTestSignRequest(clientId); | ||||||
|  |                     }, 3000); | ||||||
|  |                      | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Try to parse as JSON (sign response) | ||||||
|  |                 try { | ||||||
|  |                     const jsonMessage = JSON.parse(message); | ||||||
|  |                     this.handleSignResponse(clientId, jsonMessage); | ||||||
|  |                 } catch (e) { | ||||||
|  |                     console.log('Received non-JSON message:', message); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |             } catch (error) { | ||||||
|  |                 console.error('Error handling message:', error); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         ws.on('close', () => { | ||||||
|  |             if (clientId) { | ||||||
|  |                 this.clients.delete(clientId); | ||||||
|  |                 console.log(`Client disconnected: ${clientId}`); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         ws.on('error', (error) => { | ||||||
|  |             console.error('WebSocket error:', error); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     handleSignResponse(clientId, response) { | ||||||
|  |         console.log(`Received sign response from ${clientId}:`, response); | ||||||
|  |          | ||||||
|  |         if (response.id && response.signature) { | ||||||
|  |             console.log(`✅ Sign request ${response.id} completed successfully`); | ||||||
|  |             console.log(`   Signature: ${response.signature.substring(0, 32)}...`); | ||||||
|  |              | ||||||
|  |             // Send another test request after 10 seconds | ||||||
|  |             setTimeout(() => { | ||||||
|  |                 this.sendTestSignRequest(clientId); | ||||||
|  |             }, 10000); | ||||||
|  |         } else { | ||||||
|  |             console.log('❌ Invalid sign response format'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sendTestSignRequest(clientId) { | ||||||
|  |         const client = this.clients.get(clientId); | ||||||
|  |         if (!client) { | ||||||
|  |             console.log(`Client ${clientId} not found`); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.requestCounter++; | ||||||
|  |         const requestId = `req_${this.requestCounter}_${Date.now()}`; | ||||||
|  |         const testMessage = `Test message ${this.requestCounter} - ${new Date().toISOString()}`; | ||||||
|  |         const messageBase64 = Buffer.from(testMessage).toString('base64'); | ||||||
|  |  | ||||||
|  |         const signRequest = { | ||||||
|  |             id: requestId, | ||||||
|  |             message: messageBase64 | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         console.log(`📝 Sending sign request to ${clientId}:`, requestId); | ||||||
|  |         console.log(`   Message: "${testMessage}"`); | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             client.ws.send(JSON.stringify(signRequest)); | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error(`Failed to send sign request to ${clientId}:`, error); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     isHexString(str) { | ||||||
|  |         return /^[0-9a-fA-F]+$/.test(str) && str.length >= 32; // At least 16 bytes | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     generateClientId() { | ||||||
|  |         return `client_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Send a test request to all connected clients | ||||||
|  |     broadcastTestRequest() { | ||||||
|  |         console.log('\n📢 Broadcasting test sign request to all clients...'); | ||||||
|  |         for (const [clientId] of this.clients) { | ||||||
|  |             this.sendTestSignRequest(clientId); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Get server status | ||||||
|  |     getStatus() { | ||||||
|  |         return { | ||||||
|  |             port: this.port, | ||||||
|  |             connectedClients: this.clients.size, | ||||||
|  |             clients: Array.from(this.clients.keys()) | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Create and start the server | ||||||
|  | const server = new MockSigSocketServer(); | ||||||
|  |  | ||||||
|  | // Handle graceful shutdown | ||||||
|  | process.on('SIGINT', () => { | ||||||
|  |     console.log('\n🛑 Shutting down Mock SigSocket Server...'); | ||||||
|  |     server.httpServer.close(() => { | ||||||
|  |         console.log('Server closed'); | ||||||
|  |         process.exit(0); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | // Add some interactive commands | ||||||
|  | process.stdin.setEncoding('utf8'); | ||||||
|  | console.log('\n📋 Available commands:'); | ||||||
|  | console.log('  "test" - Send test sign request to all clients'); | ||||||
|  | console.log('  "status" - Show server status'); | ||||||
|  | console.log('  "quit" - Shutdown server'); | ||||||
|  | console.log('  Type a command and press Enter\n'); | ||||||
|  |  | ||||||
|  | process.stdin.on('readable', () => { | ||||||
|  |     const chunk = process.stdin.read(); | ||||||
|  |     if (chunk !== null) { | ||||||
|  |         const command = chunk.trim().toLowerCase(); | ||||||
|  |          | ||||||
|  |         switch (command) { | ||||||
|  |             case 'test': | ||||||
|  |                 server.broadcastTestRequest(); | ||||||
|  |                 break; | ||||||
|  |                  | ||||||
|  |             case 'status': | ||||||
|  |                 const status = server.getStatus(); | ||||||
|  |                 console.log('\n📊 Server Status:'); | ||||||
|  |                 console.log(`   Port: ${status.port}`); | ||||||
|  |                 console.log(`   Connected clients: ${status.connectedClients}`); | ||||||
|  |                 if (status.clients.length > 0) { | ||||||
|  |                     console.log(`   Client IDs: ${status.clients.join(', ')}`); | ||||||
|  |                 } | ||||||
|  |                 console.log(''); | ||||||
|  |                 break; | ||||||
|  |                  | ||||||
|  |             case 'quit': | ||||||
|  |             case 'exit': | ||||||
|  |                 process.emit('SIGINT'); | ||||||
|  |                 break; | ||||||
|  |                  | ||||||
|  |             case '': | ||||||
|  |                 break; | ||||||
|  |                  | ||||||
|  |             default: | ||||||
|  |                 console.log(`Unknown command: ${command}`); | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | // Export for testing | ||||||
|  | module.exports = MockSigSocketServer; | ||||||
							
								
								
									
										21
									
								
								crypto_vault_extension/demo/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								crypto_vault_extension/demo/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | { | ||||||
|  |   "name": "mock-sigsocket-server", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "description": "Mock SigSocket server for testing browser extension", | ||||||
|  |   "main": "mock_sigsocket_server.js", | ||||||
|  |   "scripts": { | ||||||
|  |     "start": "node mock_sigsocket_server.js", | ||||||
|  |     "test": "echo \"Error: no test specified\" && exit 1" | ||||||
|  |   }, | ||||||
|  |   "dependencies": { | ||||||
|  |     "ws": "^8.14.0" | ||||||
|  |   }, | ||||||
|  |   "keywords": [ | ||||||
|  |     "websocket", | ||||||
|  |     "sigsocket", | ||||||
|  |     "testing", | ||||||
|  |     "mock" | ||||||
|  |   ], | ||||||
|  |   "author": "", | ||||||
|  |   "license": "MIT" | ||||||
|  | } | ||||||
| @@ -6,7 +6,8 @@ | |||||||
|  |  | ||||||
|   "permissions": [ |   "permissions": [ | ||||||
|     "storage", |     "storage", | ||||||
|     "activeTab" |     "activeTab", | ||||||
|  |     "notifications" | ||||||
|   ], |   ], | ||||||
|  |  | ||||||
|   "icons": { |   "icons": { | ||||||
|   | |||||||
| @@ -27,6 +27,10 @@ | |||||||
|                 <span>seconds</span> |                 <span>seconds</span> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|  |             <div class="settings-item"> | ||||||
|  |               <label for="sigSocketUrlInput">SigSocket Server</label> | ||||||
|  |               <input type="text" id="sigSocketUrlInput" placeholder="ws://localhost:8080/ws" value="ws://localhost:8080/ws"> | ||||||
|  |             </div> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         <button id="themeToggle" class="btn-icon-only" title="Switch to dark mode"> |         <button id="themeToggle" class="btn-icon-only" title="Switch to dark mode"> | ||||||
| @@ -189,6 +193,11 @@ | |||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  |       <!-- Sign Request Manager --> | ||||||
|  |       <div id="signRequestContainer"> | ||||||
|  |         <!-- Sign request manager will be rendered here --> | ||||||
|  |       </div> | ||||||
|     </section> |     </section> | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -198,6 +207,7 @@ | |||||||
|  |  | ||||||
|   <!-- Enhanced JavaScript modules --> |   <!-- Enhanced JavaScript modules --> | ||||||
|   <script src="js/errorHandler.js"></script> |   <script src="js/errorHandler.js"></script> | ||||||
|  |   <script src="popup/components/SignRequestManager.js"></script> | ||||||
|   <script src="popup.js"></script> |   <script src="popup.js"></script> | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
| @@ -129,6 +129,7 @@ const elements = { | |||||||
|   settingsToggle: document.getElementById('settingsToggle'), |   settingsToggle: document.getElementById('settingsToggle'), | ||||||
|   settingsDropdown: document.getElementById('settingsDropdown'), |   settingsDropdown: document.getElementById('settingsDropdown'), | ||||||
|   timeoutInput: document.getElementById('timeoutInput'), |   timeoutInput: document.getElementById('timeoutInput'), | ||||||
|  |   sigSocketUrlInput: document.getElementById('sigSocketUrlInput'), | ||||||
|  |  | ||||||
|   // Keypair management elements |   // Keypair management elements | ||||||
|   toggleAddKeypairBtn: document.getElementById('toggleAddKeypairBtn'), |   toggleAddKeypairBtn: document.getElementById('toggleAddKeypairBtn'), | ||||||
| @@ -167,6 +168,7 @@ let currentKeyspace = null; | |||||||
| let selectedKeypairId = null; | let selectedKeypairId = null; | ||||||
| let backgroundPort = null; | let backgroundPort = null; | ||||||
| let sessionTimeoutDuration = 15; // Default 15 seconds | let sessionTimeoutDuration = 15; // Default 15 seconds | ||||||
|  | let signRequestManager = null; // Sign request manager instance | ||||||
|  |  | ||||||
| // Session timeout management | // Session timeout management | ||||||
| function handleError(error, context, shouldShowToast = true) { | function handleError(error, context, shouldShowToast = true) { | ||||||
| @@ -198,11 +200,14 @@ function validateInput(value, fieldName, options = {}) { | |||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
| async function loadTimeoutSetting() { | async function loadTimeoutSetting() { | ||||||
|   const result = await chrome.storage.local.get(['sessionTimeout']); |   const result = await chrome.storage.local.get(['sessionTimeout', 'sigSocketUrl']); | ||||||
|   sessionTimeoutDuration = result.sessionTimeout || 15; |   sessionTimeoutDuration = result.sessionTimeout || 15; | ||||||
|   if (elements.timeoutInput) { |   if (elements.timeoutInput) { | ||||||
|     elements.timeoutInput.value = sessionTimeoutDuration; |     elements.timeoutInput.value = sessionTimeoutDuration; | ||||||
|   } |   } | ||||||
|  |   if (elements.sigSocketUrlInput) { | ||||||
|  |     elements.sigSocketUrlInput.value = result.sigSocketUrl || 'ws://localhost:8080/ws'; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function checkSessionTimeout() { | async function checkSessionTimeout() { | ||||||
| @@ -307,6 +312,39 @@ function connectToBackground() { | |||||||
|  |  | ||||||
|       // Show timeout notification |       // Show timeout notification | ||||||
|       showToast(message.message, 'info'); |       showToast(message.message, 'info'); | ||||||
|  |  | ||||||
|  |       // Update sign request manager | ||||||
|  |       if (signRequestManager) { | ||||||
|  |         signRequestManager.updateState({ isKeypaceUnlocked: false }); | ||||||
|  |       } | ||||||
|  |     } else if (message.type === 'KEYSPACE_UNLOCKED') { | ||||||
|  |       // Handle keyspace unlock from SigSocket service | ||||||
|  |       console.log('Popup received KEYSPACE_UNLOCKED message:', message); | ||||||
|  |       if (signRequestManager) { | ||||||
|  |         console.log('Updating SignRequestManager state with keypaceMatches:', message.keypaceMatches); | ||||||
|  |         signRequestManager.updateState({ | ||||||
|  |           isKeypaceUnlocked: true, | ||||||
|  |           keypaceMatches: message.keypaceMatches, | ||||||
|  |           pendingRequests: message.pendingRequests | ||||||
|  |         }); | ||||||
|  |         console.log('SignRequestManager state updated'); | ||||||
|  |       } else { | ||||||
|  |         console.log('No SignRequestManager available to update'); | ||||||
|  |       } | ||||||
|  |     } else if (message.type === 'NEW_SIGN_REQUEST') { | ||||||
|  |       // Handle new sign request when keyspace is already unlocked | ||||||
|  |       console.log('Popup received NEW_SIGN_REQUEST message:', message); | ||||||
|  |       if (signRequestManager) { | ||||||
|  |         console.log('Updating SignRequestManager with new request, keypaceMatches:', message.keypaceMatches); | ||||||
|  |         // Only update if we have a valid keyspace match, otherwise requests array will be empty for security | ||||||
|  |         signRequestManager.updateState({ | ||||||
|  |           keypaceMatches: message.keypaceMatches, | ||||||
|  |           pendingRequests: message.pendingRequests | ||||||
|  |         }); | ||||||
|  |         console.log(`SignRequestManager updated with new request: ${message.pendingRequests.length} requests visible`); | ||||||
|  |       } else { | ||||||
|  |         console.log('No SignRequestManager available to update with new request'); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
| @@ -315,6 +353,22 @@ function connectToBackground() { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Initialize Sign Request Manager | ||||||
|  | async function initializeSignRequestManager() { | ||||||
|  |   try { | ||||||
|  |     const container = document.getElementById('signRequestContainer'); | ||||||
|  |     if (container && window.SignRequestManager) { | ||||||
|  |       signRequestManager = new window.SignRequestManager(); | ||||||
|  |       await signRequestManager.initialize(container); | ||||||
|  |       console.log('Sign Request Manager initialized'); | ||||||
|  |     } else { | ||||||
|  |       console.warn('SignRequestManager not available or container not found'); | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('Failed to initialize Sign Request Manager:', error); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| // Initialize | // Initialize | ||||||
| document.addEventListener('DOMContentLoaded', async function() { | document.addEventListener('DOMContentLoaded', async function() { | ||||||
|   // Initialize theme first |   // Initialize theme first | ||||||
| @@ -332,6 +386,9 @@ document.addEventListener('DOMContentLoaded', async function() { | |||||||
|   // Connect to background script for keep-alive |   // Connect to background script for keep-alive | ||||||
|   connectToBackground(); |   connectToBackground(); | ||||||
|  |  | ||||||
|  |   // Initialize Sign Request Manager | ||||||
|  |   await initializeSignRequestManager(); | ||||||
|  |  | ||||||
|   // Consolidated event listeners |   // Consolidated event listeners | ||||||
|   const eventMap = { |   const eventMap = { | ||||||
|     createKeyspaceBtn: createKeyspace, |     createKeyspaceBtn: createKeyspace, | ||||||
| @@ -376,6 +433,18 @@ document.addEventListener('DOMContentLoaded', async function() { | |||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   // SigSocket URL setting event listener | ||||||
|  |   elements.sigSocketUrlInput?.addEventListener('change', async (e) => { | ||||||
|  |     const url = e.target.value.trim(); | ||||||
|  |     if (url) { | ||||||
|  |       await chrome.storage.local.set({ sigSocketUrl: url }); | ||||||
|  |       // Update the SigSocket service with new URL if it exists | ||||||
|  |       if (signRequestManager && signRequestManager.sigSocketService) { | ||||||
|  |         signRequestManager.sigSocketService.defaultServerUrl = url; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   // Activity detection - reset timeout on any interaction |   // Activity detection - reset timeout on any interaction | ||||||
|   document.addEventListener('click', (e) => { |   document.addEventListener('click', (e) => { | ||||||
|     resetSessionTimeout(); |     resetSessionTimeout(); | ||||||
| @@ -644,6 +713,12 @@ async function login() { | |||||||
|           clearVaultState(); |           clearVaultState(); | ||||||
|           await loadKeypairs(); |           await loadKeypairs(); | ||||||
|  |  | ||||||
|  |           // Notify sign request manager about keyspace unlock | ||||||
|  |           if (signRequestManager) { | ||||||
|  |             signRequestManager.updateState({ isKeypaceUnlocked: true }); | ||||||
|  |             await signRequestManager.refresh(); | ||||||
|  |           } | ||||||
|  |  | ||||||
|           return response; |           return response; | ||||||
|         } else { |         } else { | ||||||
|           throw new Error(getResponseError(response, 'login')); |           throw new Error(getResponseError(response, 'login')); | ||||||
| @@ -673,6 +748,11 @@ async function lockSession() { | |||||||
|     elements.passwordInput.value = ''; |     elements.passwordInput.value = ''; | ||||||
|     clearVaultState(); |     clearVaultState(); | ||||||
|  |  | ||||||
|  |     // Update sign request manager | ||||||
|  |     if (signRequestManager) { | ||||||
|  |       signRequestManager.updateState({ isKeypaceUnlocked: false }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     showToast('Session locked', 'info'); |     showToast('Session locked', 'info'); | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     showToast('Error: ' + error.message, 'error'); |     showToast('Error: ' + error.message, 'error'); | ||||||
|   | |||||||
							
								
								
									
										443
									
								
								crypto_vault_extension/popup/components/SignRequestManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										443
									
								
								crypto_vault_extension/popup/components/SignRequestManager.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,443 @@ | |||||||
|  | /** | ||||||
|  |  * Sign Request Manager Component | ||||||
|  |  *  | ||||||
|  |  * Handles the display and management of SigSocket sign requests in the popup. | ||||||
|  |  * Manages different UI states: | ||||||
|  |  * 1. Keyspace locked: Show unlock form | ||||||
|  |  * 2. Wrong keyspace: Show mismatch message   | ||||||
|  |  * 3. Correct keyspace: Show approval UI | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | class SignRequestManager { | ||||||
|  |     constructor() { | ||||||
|  |         this.pendingRequests = []; | ||||||
|  |         this.isKeypaceUnlocked = false; | ||||||
|  |         this.keypaceMatch = false; | ||||||
|  |         this.connectionStatus = { isConnected: false }; | ||||||
|  |          | ||||||
|  |         this.container = null; | ||||||
|  |         this.initialized = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initialize the component | ||||||
|  |      * @param {HTMLElement} container - Container element to render into | ||||||
|  |      */ | ||||||
|  |     async initialize(container) { | ||||||
|  |         this.container = container; | ||||||
|  |         this.initialized = true; | ||||||
|  |          | ||||||
|  |         // Load initial state | ||||||
|  |         await this.loadState(); | ||||||
|  |          | ||||||
|  |         // Render initial UI | ||||||
|  |         this.render(); | ||||||
|  |          | ||||||
|  |         // Set up event listeners | ||||||
|  |         this.setupEventListeners(); | ||||||
|  |          | ||||||
|  |         // Listen for background messages | ||||||
|  |         this.setupBackgroundListener(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Load current state from background script | ||||||
|  |      */ | ||||||
|  |     async loadState() { | ||||||
|  |         try { | ||||||
|  |             // Check if keyspace is unlocked | ||||||
|  |             const unlockedResponse = await this.sendMessage('isUnlocked'); | ||||||
|  |             this.isKeypaceUnlocked = unlockedResponse?.unlocked || false; | ||||||
|  |              | ||||||
|  |             // Get pending requests | ||||||
|  |             const requestsResponse = await this.sendMessage('getPendingRequests'); | ||||||
|  |             this.pendingRequests = requestsResponse?.requests || []; | ||||||
|  |              | ||||||
|  |             // Get SigSocket status | ||||||
|  |             const statusResponse = await this.sendMessage('getSigSocketStatus'); | ||||||
|  |             this.connectionStatus = statusResponse?.status || { isConnected: false }; | ||||||
|  |              | ||||||
|  |             // If keyspace is unlocked, notify background to check keyspace match | ||||||
|  |             if (this.isKeypaceUnlocked) { | ||||||
|  |                 await this.sendMessage('keypaceUnlocked'); | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to load sign request state:', error); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Render the component UI | ||||||
|  |      */ | ||||||
|  |     render() { | ||||||
|  |         if (!this.container) return; | ||||||
|  |          | ||||||
|  |         const hasRequests = this.pendingRequests.length > 0; | ||||||
|  |          | ||||||
|  |         if (!hasRequests) { | ||||||
|  |             this.renderNoRequests(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         if (!this.isKeypaceUnlocked) { | ||||||
|  |             this.renderUnlockPrompt(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         if (!this.keypaceMatch) { | ||||||
|  |             this.renderKeypaceMismatch(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         this.renderApprovalUI(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Render no requests state | ||||||
|  |      */ | ||||||
|  |     renderNoRequests() { | ||||||
|  |         this.container.innerHTML = ` | ||||||
|  |             <div class="sign-request-manager"> | ||||||
|  |                 <div class="connection-status ${this.connectionStatus.isConnected ? 'connected' : 'disconnected'}"> | ||||||
|  |                     <span class="status-indicator"></span> | ||||||
|  |                     SigSocket: ${this.connectionStatus.isConnected ? 'Connected' : 'Disconnected'} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="no-requests"> | ||||||
|  |                     <p>No pending sign requests</p> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         `; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Render unlock prompt | ||||||
|  |      */ | ||||||
|  |     renderUnlockPrompt() { | ||||||
|  |         const requestCount = this.pendingRequests.length; | ||||||
|  |         this.container.innerHTML = ` | ||||||
|  |             <div class="sign-request-manager"> | ||||||
|  |                 <div class="connection-status ${this.connectionStatus.isConnected ? 'connected' : 'disconnected'}"> | ||||||
|  |                     <span class="status-indicator"></span> | ||||||
|  |                     SigSocket: ${this.connectionStatus.isConnected ? 'Connected' : 'Disconnected'} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="unlock-prompt"> | ||||||
|  |                     <h3>🔒 Unlock Keyspace</h3> | ||||||
|  |                     <p>Unlock your keyspace to see ${requestCount} pending sign request${requestCount !== 1 ? 's' : ''}.</p> | ||||||
|  |                     <p class="hint">Use the login form above to unlock your keyspace.</p> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         `; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Render keyspace mismatch message | ||||||
|  |      */ | ||||||
|  |     renderKeypaceMismatch() { | ||||||
|  |         this.container.innerHTML = ` | ||||||
|  |             <div class="sign-request-manager"> | ||||||
|  |                 <div class="connection-status ${this.connectionStatus.isConnected ? 'connected' : 'disconnected'}"> | ||||||
|  |                     <span class="status-indicator"></span> | ||||||
|  |                     SigSocket: ${this.connectionStatus.isConnected ? 'Connected' : 'Disconnected'} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="keyspace-mismatch"> | ||||||
|  |                     <h3>⚠️ Wrong Keyspace</h3> | ||||||
|  |                     <p>The unlocked keyspace doesn't match the connected SigSocket session.</p> | ||||||
|  |                     <p class="hint">Please unlock the correct keyspace to approve sign requests.</p> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         `; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Render approval UI with pending requests | ||||||
|  |      */ | ||||||
|  |     renderApprovalUI() { | ||||||
|  |         const requestsHtml = this.pendingRequests.map(request => this.renderSignRequestCard(request)).join(''); | ||||||
|  |          | ||||||
|  |         this.container.innerHTML = ` | ||||||
|  |             <div class="sign-request-manager"> | ||||||
|  |                 <div class="connection-status connected"> | ||||||
|  |                     <span class="status-indicator"></span> | ||||||
|  |                     SigSocket: Connected | ||||||
|  |                 </div> | ||||||
|  |                 <div class="requests-header"> | ||||||
|  |                     <h3>📝 Sign Requests (${this.pendingRequests.length})</h3> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="requests-list"> | ||||||
|  |                     ${requestsHtml} | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         `; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Render individual sign request card | ||||||
|  |      * @param {Object} request - Sign request data | ||||||
|  |      * @returns {string} - HTML string for the request card | ||||||
|  |      */ | ||||||
|  |     renderSignRequestCard(request) { | ||||||
|  |         const timestamp = new Date(request.timestamp).toLocaleTimeString(); | ||||||
|  |         const messagePreview = this.getMessagePreview(request.message); | ||||||
|  |          | ||||||
|  |         return ` | ||||||
|  |             <div class="sign-request-card" data-request-id="${request.id}"> | ||||||
|  |                 <div class="request-header"> | ||||||
|  |                     <div class="request-id">Request: ${request.id.substring(0, 8)}...</div> | ||||||
|  |                     <div class="request-time">${timestamp}</div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="request-message"> | ||||||
|  |                     <label>Message:</label> | ||||||
|  |                     <div class="message-content"> | ||||||
|  |                         <div class="message-preview">${messagePreview}</div> | ||||||
|  |                         <button class="expand-message" data-request-id="${request.id}"> | ||||||
|  |                             <span class="expand-text">Show Full</span> | ||||||
|  |                         </button> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="request-actions"> | ||||||
|  |                     <button class="btn-reject" data-request-id="${request.id}"> | ||||||
|  |                         ❌ Reject | ||||||
|  |                     </button> | ||||||
|  |                     <button class="btn-approve" data-request-id="${request.id}"> | ||||||
|  |                         ✅ Approve & Sign | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         `; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Get a preview of the message content | ||||||
|  |      * @param {string} messageBase64 - Base64 encoded message | ||||||
|  |      * @returns {string} - Preview text | ||||||
|  |      */ | ||||||
|  |     getMessagePreview(messageBase64) { | ||||||
|  |         try { | ||||||
|  |             const decoded = atob(messageBase64); | ||||||
|  |             const preview = decoded.length > 50 ? decoded.substring(0, 50) + '...' : decoded; | ||||||
|  |             return preview; | ||||||
|  |         } catch (error) { | ||||||
|  |             return `Base64: ${messageBase64.substring(0, 20)}...`; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Set up event listeners | ||||||
|  |      */ | ||||||
|  |     setupEventListeners() { | ||||||
|  |         if (!this.container) return; | ||||||
|  |          | ||||||
|  |         // Use event delegation for dynamic content | ||||||
|  |         this.container.addEventListener('click', (e) => { | ||||||
|  |             const target = e.target; | ||||||
|  |              | ||||||
|  |             if (target.classList.contains('btn-approve')) { | ||||||
|  |                 const requestId = target.getAttribute('data-request-id'); | ||||||
|  |                 this.approveRequest(requestId); | ||||||
|  |             } else if (target.classList.contains('btn-reject')) { | ||||||
|  |                 const requestId = target.getAttribute('data-request-id'); | ||||||
|  |                 this.rejectRequest(requestId); | ||||||
|  |             } else if (target.classList.contains('expand-message')) { | ||||||
|  |                 const requestId = target.getAttribute('data-request-id'); | ||||||
|  |                 this.toggleMessageExpansion(requestId); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Set up listener for background script messages | ||||||
|  |      */ | ||||||
|  |     setupBackgroundListener() { | ||||||
|  |         // Listen for keyspace unlock events | ||||||
|  |         chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | ||||||
|  |             if (message.type === 'KEYSPACE_UNLOCKED') { | ||||||
|  |                 this.isKeypaceUnlocked = true; | ||||||
|  |                 this.keypaceMatch = message.keypaceMatches; | ||||||
|  |                 this.pendingRequests = message.pendingRequests || []; | ||||||
|  |                 this.render(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Approve a sign request | ||||||
|  |      * @param {string} requestId - Request ID to approve | ||||||
|  |      */ | ||||||
|  |     async approveRequest(requestId) { | ||||||
|  |         try { | ||||||
|  |             const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-approve`); | ||||||
|  |             if (button) { | ||||||
|  |                 button.disabled = true; | ||||||
|  |                 button.textContent = 'Signing...'; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             const response = await this.sendMessage('approveSignRequest', { requestId }); | ||||||
|  |              | ||||||
|  |             if (response?.success) { | ||||||
|  |                 // Remove the request from UI | ||||||
|  |                 this.pendingRequests = this.pendingRequests.filter(r => r.id !== requestId); | ||||||
|  |                 this.render(); | ||||||
|  |                  | ||||||
|  |                 // Show success message | ||||||
|  |                 this.showToast('Sign request approved successfully!', 'success'); | ||||||
|  |             } else { | ||||||
|  |                 throw new Error(response?.error || 'Failed to approve request'); | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to approve request:', error); | ||||||
|  |             this.showToast('Failed to approve request: ' + error.message, 'error'); | ||||||
|  |              | ||||||
|  |             // Re-enable button | ||||||
|  |             const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-approve`); | ||||||
|  |             if (button) { | ||||||
|  |                 button.disabled = false; | ||||||
|  |                 button.textContent = '✅ Approve & Sign'; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Reject a sign request | ||||||
|  |      * @param {string} requestId - Request ID to reject | ||||||
|  |      */ | ||||||
|  |     async rejectRequest(requestId) { | ||||||
|  |         try { | ||||||
|  |             const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-reject`); | ||||||
|  |             if (button) { | ||||||
|  |                 button.disabled = true; | ||||||
|  |                 button.textContent = 'Rejecting...'; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             const response = await this.sendMessage('rejectSignRequest', {  | ||||||
|  |                 requestId,  | ||||||
|  |                 reason: 'User rejected'  | ||||||
|  |             }); | ||||||
|  |              | ||||||
|  |             if (response?.success) { | ||||||
|  |                 // Remove the request from UI | ||||||
|  |                 this.pendingRequests = this.pendingRequests.filter(r => r.id !== requestId); | ||||||
|  |                 this.render(); | ||||||
|  |                  | ||||||
|  |                 // Show success message | ||||||
|  |                 this.showToast('Sign request rejected', 'info'); | ||||||
|  |             } else { | ||||||
|  |                 throw new Error(response?.error || 'Failed to reject request'); | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to reject request:', error); | ||||||
|  |             this.showToast('Failed to reject request: ' + error.message, 'error'); | ||||||
|  |              | ||||||
|  |             // Re-enable button | ||||||
|  |             const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-reject`); | ||||||
|  |             if (button) { | ||||||
|  |                 button.disabled = false; | ||||||
|  |                 button.textContent = '❌ Reject'; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Toggle message expansion | ||||||
|  |      * @param {string} requestId - Request ID | ||||||
|  |      */ | ||||||
|  |     toggleMessageExpansion(requestId) { | ||||||
|  |         const request = this.pendingRequests.find(r => r.id === requestId); | ||||||
|  |         if (!request) return; | ||||||
|  |          | ||||||
|  |         const card = this.container.querySelector(`[data-request-id="${requestId}"]`); | ||||||
|  |         const messageContent = card.querySelector('.message-content'); | ||||||
|  |         const expandButton = card.querySelector('.expand-message'); | ||||||
|  |          | ||||||
|  |         const isExpanded = messageContent.classList.contains('expanded'); | ||||||
|  |          | ||||||
|  |         if (isExpanded) { | ||||||
|  |             messageContent.classList.remove('expanded'); | ||||||
|  |             messageContent.querySelector('.message-preview').textContent = this.getMessagePreview(request.message); | ||||||
|  |             expandButton.querySelector('.expand-text').textContent = 'Show Full'; | ||||||
|  |         } else { | ||||||
|  |             messageContent.classList.add('expanded'); | ||||||
|  |             try { | ||||||
|  |                 const fullMessage = atob(request.message); | ||||||
|  |                 messageContent.querySelector('.message-preview').textContent = fullMessage; | ||||||
|  |             } catch (error) { | ||||||
|  |                 messageContent.querySelector('.message-preview').textContent = `Base64: ${request.message}`; | ||||||
|  |             } | ||||||
|  |             expandButton.querySelector('.expand-text').textContent = 'Show Less'; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Send message to background script | ||||||
|  |      * @param {string} action - Action to perform | ||||||
|  |      * @param {Object} data - Additional data | ||||||
|  |      * @returns {Promise<Object>} - Response from background script | ||||||
|  |      */ | ||||||
|  |     async sendMessage(action, data = {}) { | ||||||
|  |         return new Promise((resolve) => { | ||||||
|  |             chrome.runtime.sendMessage({ action, ...data }, resolve); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Show toast notification | ||||||
|  |      * @param {string} message - Message to show | ||||||
|  |      * @param {string} type - Toast type (success, error, info) | ||||||
|  |      */ | ||||||
|  |     showToast(message, type = 'info') { | ||||||
|  |         // Use the existing toast system from popup.js | ||||||
|  |         if (typeof showToast === 'function') { | ||||||
|  |             showToast(message, type); | ||||||
|  |         } else { | ||||||
|  |             console.log(`[${type.toUpperCase()}] ${message}`); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Update component state | ||||||
|  |      * @param {Object} newState - New state data | ||||||
|  |      */ | ||||||
|  |     updateState(newState) { | ||||||
|  |         console.log('SignRequestManager.updateState called with:', newState); | ||||||
|  |         console.log('Current state before update:', { | ||||||
|  |             isKeypaceUnlocked: this.isKeypaceUnlocked, | ||||||
|  |             keypaceMatch: this.keypaceMatch, | ||||||
|  |             pendingRequests: this.pendingRequests.length | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         Object.assign(this, newState); | ||||||
|  |  | ||||||
|  |         // Fix the property name mismatch | ||||||
|  |         if (newState.keypaceMatches !== undefined) { | ||||||
|  |             this.keypaceMatch = newState.keypaceMatches; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         console.log('State after update:', { | ||||||
|  |             isKeypaceUnlocked: this.isKeypaceUnlocked, | ||||||
|  |             keypaceMatch: this.keypaceMatch, | ||||||
|  |             pendingRequests: this.pendingRequests.length | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         if (this.initialized) { | ||||||
|  |             console.log('Rendering SignRequestManager with new state'); | ||||||
|  |             this.render(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Refresh component data | ||||||
|  |      */ | ||||||
|  |     async refresh() { | ||||||
|  |         await this.loadState(); | ||||||
|  |         this.render(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Export for use in popup | ||||||
|  | if (typeof module !== 'undefined' && module.exports) { | ||||||
|  |     module.exports = SignRequestManager; | ||||||
|  | } else { | ||||||
|  |     window.SignRequestManager = SignRequestManager; | ||||||
|  | } | ||||||
| @@ -1069,4 +1069,195 @@ input::placeholder, textarea::placeholder { | |||||||
| .verification-icon svg { | .verification-icon svg { | ||||||
|   width: 20px; |   width: 20px; | ||||||
|   height: 20px; |   height: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Sign Request Manager Styles */ | ||||||
|  | .sign-request-manager { | ||||||
|  |   margin-top: 16px; | ||||||
|  |   padding: 16px; | ||||||
|  |   background: var(--bg-secondary); | ||||||
|  |   border-radius: 8px; | ||||||
|  |   border: 1px solid var(--border-color); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .connection-status { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   gap: 8px; | ||||||
|  |   margin-bottom: 12px; | ||||||
|  |   font-size: 12px; | ||||||
|  |   font-weight: 500; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .status-indicator { | ||||||
|  |   width: 8px; | ||||||
|  |   height: 8px; | ||||||
|  |   border-radius: 50%; | ||||||
|  |   background: #ef4444; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .connection-status.connected .status-indicator { | ||||||
|  |   background: #10b981; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .no-requests { | ||||||
|  |   text-align: center; | ||||||
|  |   padding: 20px; | ||||||
|  |   color: var(--text-secondary); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .unlock-prompt, .keyspace-mismatch { | ||||||
|  |   text-align: center; | ||||||
|  |   padding: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .unlock-prompt h3, .keyspace-mismatch h3 { | ||||||
|  |   margin: 0 0 12px 0; | ||||||
|  |   font-size: 16px; | ||||||
|  |   color: var(--text-primary); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .unlock-prompt p, .keyspace-mismatch p { | ||||||
|  |   margin: 8px 0; | ||||||
|  |   color: var(--text-secondary); | ||||||
|  |   font-size: 14px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .hint { | ||||||
|  |   font-style: italic; | ||||||
|  |   font-size: 12px !important; | ||||||
|  |   color: var(--text-tertiary) !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .requests-header { | ||||||
|  |   margin-bottom: 12px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .requests-header h3 { | ||||||
|  |   margin: 0; | ||||||
|  |   font-size: 16px; | ||||||
|  |   color: var(--text-primary); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .requests-list { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   gap: 12px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .sign-request-card { | ||||||
|  |   background: var(--bg-primary); | ||||||
|  |   border: 1px solid var(--border-color); | ||||||
|  |   border-radius: 6px; | ||||||
|  |   padding: 12px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-header { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: center; | ||||||
|  |   margin-bottom: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-id { | ||||||
|  |   font-family: monospace; | ||||||
|  |   font-size: 12px; | ||||||
|  |   color: var(--text-secondary); | ||||||
|  |   font-weight: 500; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-time { | ||||||
|  |   font-size: 11px; | ||||||
|  |   color: var(--text-tertiary); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-message { | ||||||
|  |   margin-bottom: 12px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-message label { | ||||||
|  |   display: block; | ||||||
|  |   font-size: 12px; | ||||||
|  |   font-weight: 500; | ||||||
|  |   color: var(--text-secondary); | ||||||
|  |   margin-bottom: 4px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .message-content { | ||||||
|  |   background: var(--bg-secondary); | ||||||
|  |   border: 1px solid var(--border-color); | ||||||
|  |   border-radius: 4px; | ||||||
|  |   padding: 8px; | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .message-preview { | ||||||
|  |   font-family: monospace; | ||||||
|  |   font-size: 11px; | ||||||
|  |   color: var(--text-primary); | ||||||
|  |   word-break: break-all; | ||||||
|  |   line-height: 1.4; | ||||||
|  |   max-height: 60px; | ||||||
|  |   overflow: hidden; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .message-content.expanded .message-preview { | ||||||
|  |   max-height: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .expand-message { | ||||||
|  |   position: absolute; | ||||||
|  |   top: 4px; | ||||||
|  |   right: 4px; | ||||||
|  |   background: var(--accent-color); | ||||||
|  |   color: white; | ||||||
|  |   border: none; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   padding: 2px 6px; | ||||||
|  |   font-size: 10px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   transition: background-color 0.2s; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .expand-message:hover { | ||||||
|  |   background: var(--accent-hover); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .request-actions { | ||||||
|  |   display: flex; | ||||||
|  |   gap: 8px; | ||||||
|  |   justify-content: flex-end; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-approve, .btn-reject { | ||||||
|  |   padding: 6px 12px; | ||||||
|  |   border: none; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   font-size: 12px; | ||||||
|  |   font-weight: 500; | ||||||
|  |   cursor: pointer; | ||||||
|  |   transition: all 0.2s; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-approve { | ||||||
|  |   background: #10b981; | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-approve:hover:not(:disabled) { | ||||||
|  |   background: #059669; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-reject { | ||||||
|  |   background: #ef4444; | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-reject:hover:not(:disabled) { | ||||||
|  |   background: #dc2626; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-approve:disabled, .btn-reject:disabled { | ||||||
|  |   opacity: 0.6; | ||||||
|  |   cursor: not-allowed; | ||||||
| } | } | ||||||
							
								
								
									
										114
									
								
								crypto_vault_extension/test_extension.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								crypto_vault_extension/test_extension.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | |||||||
|  | # Testing the SigSocket Browser Extension | ||||||
|  |  | ||||||
|  | ## Prerequisites | ||||||
|  |  | ||||||
|  | 1. **SigSocket Server**: You need a running SigSocket server at `ws://localhost:8080/ws` | ||||||
|  | 2. **Browser**: Chrome or Chromium-based browser with developer mode enabled | ||||||
|  |  | ||||||
|  | ## Test Steps | ||||||
|  |  | ||||||
|  | ### 1. Load the Extension | ||||||
|  |  | ||||||
|  | 1. Open Chrome and go to `chrome://extensions/` | ||||||
|  | 2. Enable "Developer mode" in the top right | ||||||
|  | 3. Click "Load unpacked" and select the `crypto_vault_extension` directory | ||||||
|  | 4. The CryptoVault extension should appear in your extensions list | ||||||
|  |  | ||||||
|  | ### 2. Basic Functionality Test | ||||||
|  |  | ||||||
|  | 1. Click the CryptoVault extension icon in the toolbar | ||||||
|  | 2. Create a new keyspace: | ||||||
|  |    - Enter a keyspace name (e.g., "test-workspace") | ||||||
|  |    - Enter a password | ||||||
|  |    - Click "Create New" | ||||||
|  | 3. The extension should automatically connect to the SigSocket server | ||||||
|  | 4. Add a keypair: | ||||||
|  |    - Click "Add Keypair" | ||||||
|  |    - Enter a name for the keypair | ||||||
|  |    - Click "Create Keypair" | ||||||
|  |  | ||||||
|  | ### 3. SigSocket Integration Test | ||||||
|  |  | ||||||
|  | 1. **Check Connection Status**: | ||||||
|  |    - Look for the SigSocket connection status at the bottom of the popup | ||||||
|  |    - It should show "SigSocket: Connected" with a green indicator | ||||||
|  |  | ||||||
|  | 2. **Test Sign Request Flow**: | ||||||
|  |    - Send a sign request to the SigSocket server (you'll need to implement this on the server side) | ||||||
|  |    - The extension should show a notification | ||||||
|  |    - The extension badge should show the number of pending requests | ||||||
|  |    - Open the extension popup to see the sign request | ||||||
|  |  | ||||||
|  | 3. **Test Approval Flow**: | ||||||
|  |    - If keyspace is locked, you should see "Unlock keyspace to see X pending requests" | ||||||
|  |    - Unlock the keyspace using the login form | ||||||
|  |    - You should see the sign request details | ||||||
|  |    - Click "Approve & Sign" to approve the request | ||||||
|  |    - The request should be signed and sent back to the server | ||||||
|  |  | ||||||
|  | ### 4. Settings Test | ||||||
|  |  | ||||||
|  | 1. Click the settings gear icon in the extension popup | ||||||
|  | 2. Change the SigSocket server URL if needed | ||||||
|  | 3. Adjust the session timeout if desired | ||||||
|  |  | ||||||
|  | ## Expected Behavior | ||||||
|  |  | ||||||
|  | - ✅ Extension loads without errors | ||||||
|  | - ✅ Can create keyspaces and keypairs | ||||||
|  | - ✅ SigSocket connection is established automatically | ||||||
|  | - ✅ Sign requests are received and displayed | ||||||
|  | - ✅ Approval flow works correctly | ||||||
|  | - ✅ Settings can be configured | ||||||
|  |  | ||||||
|  | ## Troubleshooting | ||||||
|  |  | ||||||
|  | ### Common Issues | ||||||
|  |  | ||||||
|  | 1. **Extension won't load**: Check the console for JavaScript errors | ||||||
|  | 2. **SigSocket won't connect**: Verify the server is running and the URL is correct | ||||||
|  | 3. **WASM errors**: Check that the WASM files are properly built and copied | ||||||
|  | 4. **Sign requests not appearing**: Check the browser console for callback errors | ||||||
|  |  | ||||||
|  | ### Debug Steps | ||||||
|  |  | ||||||
|  | 1. Open Chrome DevTools | ||||||
|  | 2. Go to the Extensions tab | ||||||
|  | 3. Find CryptoVault and click "Inspect views: background page" | ||||||
|  | 4. Check the console for any errors | ||||||
|  | 5. Also inspect the popup by right-clicking the extension icon and selecting "Inspect popup" | ||||||
|  |  | ||||||
|  | ## Server-Side Testing | ||||||
|  |  | ||||||
|  | To fully test the extension, you'll need a SigSocket server that can: | ||||||
|  |  | ||||||
|  | 1. Accept WebSocket connections at `/ws` | ||||||
|  | 2. Handle client introduction messages (hex-encoded public keys) | ||||||
|  | 3. Send sign requests in the format: | ||||||
|  |  | ||||||
|  |    ```json | ||||||
|  |    { | ||||||
|  |      "id": "unique-request-id", | ||||||
|  |      "message": "base64-encoded-message" | ||||||
|  |    } | ||||||
|  |    ``` | ||||||
|  |  | ||||||
|  | 4. Receive sign responses in the format: | ||||||
|  |  | ||||||
|  |    ```json | ||||||
|  |    { | ||||||
|  |      "id": "request-id", | ||||||
|  |      "message": "base64-encoded-message", | ||||||
|  |      "signature": "base64-encoded-signature" | ||||||
|  |    } | ||||||
|  |    ``` | ||||||
|  |  | ||||||
|  | ## Next Steps | ||||||
|  |  | ||||||
|  | If basic functionality works: | ||||||
|  |  | ||||||
|  | 1. Test with multiple concurrent sign requests | ||||||
|  | 2. Test connection recovery after network issues | ||||||
|  | 3. Test with different keyspace configurations | ||||||
|  | 4. Test the rejection flow | ||||||
|  | 5. Test session timeout behavior | ||||||
| @@ -202,6 +202,33 @@ function debugString(val) { | |||||||
|     // TODO we could test for more things here, like `Set`s and `Map`s. |     // TODO we could test for more things here, like `Set`s and `Map`s. | ||||||
|     return className; |     return className; | ||||||
| } | } | ||||||
|  | /** | ||||||
|  |  * Initialize the scripting environment (must be called before run_rhai) | ||||||
|  |  */ | ||||||
|  | export function init_rhai_env() { | ||||||
|  |     wasm.init_rhai_env(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function takeFromExternrefTable0(idx) { | ||||||
|  |     const value = wasm.__wbindgen_export_2.get(idx); | ||||||
|  |     wasm.__externref_table_dealloc(idx); | ||||||
|  |     return value; | ||||||
|  | } | ||||||
|  | /** | ||||||
|  |  * Securely run a Rhai script in the extension context (must be called only after user approval) | ||||||
|  |  * @param {string} script | ||||||
|  |  * @returns {any} | ||||||
|  |  */ | ||||||
|  | export function run_rhai(script) { | ||||||
|  |     const ptr0 = passStringToWasm0(script, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||||
|  |     const len0 = WASM_VECTOR_LEN; | ||||||
|  |     const ret = wasm.run_rhai(ptr0, len0); | ||||||
|  |     if (ret[2]) { | ||||||
|  |         throw takeFromExternrefTable0(ret[1]); | ||||||
|  |     } | ||||||
|  |     return takeFromExternrefTable0(ret[0]); | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Create and unlock a new keyspace with the given name and password |  * Create and unlock a new keyspace with the given name and password | ||||||
|  * @param {string} keyspace |  * @param {string} keyspace | ||||||
| @@ -239,11 +266,6 @@ export function lock_session() { | |||||||
|     wasm.lock_session(); |     wasm.lock_session(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function takeFromExternrefTable0(idx) { |  | ||||||
|     const value = wasm.__wbindgen_export_2.get(idx); |  | ||||||
|     wasm.__externref_table_dealloc(idx); |  | ||||||
|     return value; |  | ||||||
| } |  | ||||||
| /** | /** | ||||||
|  * Get metadata of the currently selected keypair |  * Get metadata of the currently selected keypair | ||||||
|  * @returns {any} |  * @returns {any} | ||||||
| @@ -277,6 +299,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 +381,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 +392,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 | ||||||
| @@ -373,46 +466,162 @@ export function decrypt_data(encrypted) { | |||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | function __wbg_adapter_34(arg0, arg1, arg2) { | ||||||
|  * Initialize the scripting environment (must be called before run_rhai) |     wasm.closure135_externref_shim(arg0, arg1, arg2); | ||||||
|  */ |  | ||||||
| export function init_rhai_env() { |  | ||||||
|     wasm.init_rhai_env(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | function __wbg_adapter_39(arg0, arg1) { | ||||||
|  * Securely run a Rhai script in the extension context (must be called only after user approval) |     wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha4436a3f79fb1a0f(arg0, arg1); | ||||||
|  * @param {string} script |  | ||||||
|  * @returns {any} |  | ||||||
|  */ |  | ||||||
| export function run_rhai(script) { |  | ||||||
|     const ptr0 = passStringToWasm0(script, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); |  | ||||||
|     const len0 = WASM_VECTOR_LEN; |  | ||||||
|     const ret = wasm.run_rhai(ptr0, len0); |  | ||||||
|     if (ret[2]) { |  | ||||||
|         throw takeFromExternrefTable0(ret[1]); |  | ||||||
|     } |  | ||||||
|     return takeFromExternrefTable0(ret[0]); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function __wbg_adapter_32(arg0, arg1, arg2) { | function __wbg_adapter_44(arg0, arg1, arg2) { | ||||||
|     wasm.closure121_externref_shim(arg0, arg1, arg2); |     wasm.closure199_externref_shim(arg0, arg1, arg2); | ||||||
| } | } | ||||||
|  |  | ||||||
| function __wbg_adapter_35(arg0, arg1, arg2) { | function __wbg_adapter_49(arg0, arg1) { | ||||||
|     wasm.closure150_externref_shim(arg0, arg1, arg2); |     wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf148c54a4a246cea(arg0, arg1); | ||||||
| } | } | ||||||
|  |  | ||||||
| function __wbg_adapter_38(arg0, arg1, arg2) { | function __wbg_adapter_52(arg0, arg1, arg2) { | ||||||
|     wasm.closure227_externref_shim(arg0, arg1, arg2); |     wasm.closure264_externref_shim(arg0, arg1, arg2); | ||||||
| } | } | ||||||
|  |  | ||||||
| function __wbg_adapter_138(arg0, arg1, arg2, arg3) { | function __wbg_adapter_55(arg0, arg1, arg2) { | ||||||
|     wasm.closure1879_externref_shim(arg0, arg1, arg2, arg3); |     wasm.closure349_externref_shim(arg0, arg1, arg2); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function __wbg_adapter_195(arg0, arg1, arg2, arg3) { | ||||||
|  |     wasm.closure2004_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; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| 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') { | ||||||
| @@ -467,6 +676,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 +752,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 +784,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_195(a, state0.b, arg0, arg1); | ||||||
|                 } finally { |                 } finally { | ||||||
|                     state0.a = a; |                     state0.a = a; | ||||||
|                 } |                 } | ||||||
| @@ -577,6 +803,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 +839,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 +879,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 +895,34 @@ 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_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 +957,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 +969,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 +981,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_wrapper1181 = function(arg0, arg1, arg2) { | ||||||
|         const ret = makeMutClosure(arg0, arg1, 122, __wbg_adapter_32); |         const ret = makeMutClosure(arg0, arg1, 350, __wbg_adapter_55); | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|     imports.wbg.__wbindgen_closure_wrapper549 = function(arg0, arg1, arg2) { |     imports.wbg.__wbindgen_closure_wrapper335 = function(arg0, arg1, arg2) { | ||||||
|         const ret = makeMutClosure(arg0, arg1, 151, __wbg_adapter_35); |         const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_34); | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|     imports.wbg.__wbindgen_closure_wrapper857 = function(arg0, arg1, arg2) { |     imports.wbg.__wbindgen_closure_wrapper336 = function(arg0, arg1, arg2) { | ||||||
|         const ret = makeMutClosure(arg0, arg1, 228, __wbg_adapter_38); |         const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_34); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper337 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_39); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper340 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_34); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper657 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 200, __wbg_adapter_44); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper658 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 200, __wbg_adapter_44); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper661 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 200, __wbg_adapter_49); | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  |     imports.wbg.__wbindgen_closure_wrapper876 = function(arg0, arg1, arg2) { | ||||||
|  |         const ret = makeMutClosure(arg0, arg1, 265, __wbg_adapter_52); | ||||||
|         return ret; |         return ret; | ||||||
|     }; |     }; | ||||||
|     imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { |     imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { | ||||||
| @@ -778,6 +1071,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.
										
									
								
							
		Reference in New Issue
	
	Block a user