Support encrypt, decrypt & verify + add dark theme + better error handling & UI enhancements
This commit is contained in:
		| @@ -1,6 +1,7 @@ | ||||
| let vault = null; | ||||
| let isInitialized = false; | ||||
| let currentSession = null; | ||||
| let keepAliveInterval = null; | ||||
|  | ||||
| // Utility function to convert Uint8Array to hex | ||||
| function toHex(uint8Array) { | ||||
| @@ -12,16 +13,31 @@ function toHex(uint8Array) { | ||||
| // Session persistence functions | ||||
| async function saveSession(keyspace) { | ||||
|   currentSession = { keyspace, timestamp: Date.now() }; | ||||
|   await chrome.storage.session.set({ cryptoVaultSession: currentSession }); | ||||
|   console.log('Session saved:', currentSession); | ||||
|  | ||||
|   // Save to both session and local storage for better persistence | ||||
|   try { | ||||
|     await chrome.storage.session.set({ cryptoVaultSession: currentSession }); | ||||
|     await chrome.storage.local.set({ cryptoVaultSessionBackup: currentSession }); | ||||
|   } catch (error) { | ||||
|     console.error('Failed to save session:', error); | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function loadSession() { | ||||
|   try { | ||||
|     const result = await chrome.storage.session.get(['cryptoVaultSession']); | ||||
|     // Try session storage first | ||||
|     let result = await chrome.storage.session.get(['cryptoVaultSession']); | ||||
|     if (result.cryptoVaultSession) { | ||||
|       currentSession = result.cryptoVaultSession; | ||||
|       console.log('Session loaded:', currentSession); | ||||
|       return currentSession; | ||||
|     } | ||||
|  | ||||
|     // Fallback to local storage | ||||
|     result = await chrome.storage.local.get(['cryptoVaultSessionBackup']); | ||||
|     if (result.cryptoVaultSessionBackup) { | ||||
|       currentSession = result.cryptoVaultSessionBackup; | ||||
|       // Restore to session storage | ||||
|       await chrome.storage.session.set({ cryptoVaultSession: currentSession }); | ||||
|       return currentSession; | ||||
|     } | ||||
|   } catch (error) { | ||||
| @@ -32,8 +48,45 @@ async function loadSession() { | ||||
|  | ||||
| async function clearSession() { | ||||
|   currentSession = null; | ||||
|   await chrome.storage.session.remove(['cryptoVaultSession']); | ||||
|   console.log('Session cleared'); | ||||
|   try { | ||||
|     await chrome.storage.session.remove(['cryptoVaultSession']); | ||||
|     await chrome.storage.local.remove(['cryptoVaultSessionBackup']); | ||||
|   } catch (error) { | ||||
|     console.error('Failed to clear session:', error); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Keep service worker alive | ||||
| function startKeepAlive() { | ||||
|   if (keepAliveInterval) { | ||||
|     clearInterval(keepAliveInterval); | ||||
|   } | ||||
|  | ||||
|   // Ping every 20 seconds to keep service worker alive | ||||
|   keepAliveInterval = setInterval(() => { | ||||
|     // Simple operation to keep service worker active | ||||
|     chrome.storage.session.get(['keepAlive']).catch(() => { | ||||
|       // Ignore errors | ||||
|     }); | ||||
|   }, 20000); | ||||
| } | ||||
|  | ||||
| function stopKeepAlive() { | ||||
|   if (keepAliveInterval) { | ||||
|     clearInterval(keepAliveInterval); | ||||
|     keepAliveInterval = null; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Enhanced session management with keep-alive | ||||
| async function saveSessionWithKeepAlive(keyspace) { | ||||
|   await saveSession(keyspace); | ||||
|   startKeepAlive(); | ||||
| } | ||||
|  | ||||
| async function clearSessionWithKeepAlive() { | ||||
|   await clearSession(); | ||||
|   stopKeepAlive(); | ||||
| } | ||||
|  | ||||
| async function restoreSession() { | ||||
| @@ -43,15 +96,15 @@ async function restoreSession() { | ||||
|       // Check if the session is still valid by testing if vault is unlocked | ||||
|       const isUnlocked = vault.is_unlocked(); | ||||
|       if (isUnlocked) { | ||||
|         console.log('Session restored successfully for keyspace:', session.keyspace); | ||||
|         // Restart keep-alive for restored session | ||||
|         startKeepAlive(); | ||||
|         return session; | ||||
|       } else { | ||||
|         console.log('Session expired, clearing...'); | ||||
|         await clearSession(); | ||||
|         await clearSessionWithKeepAlive(); | ||||
|       } | ||||
|     } catch (error) { | ||||
|       console.error('Error checking session validity:', error); | ||||
|       await clearSession(); | ||||
|       await clearSessionWithKeepAlive(); | ||||
|     } | ||||
|   } | ||||
|   return null; | ||||
| @@ -68,6 +121,9 @@ import init, { | ||||
|   current_keypair_metadata, | ||||
|   current_keypair_public_key, | ||||
|   sign, | ||||
|   verify, | ||||
|   encrypt_data, | ||||
|   decrypt_data, | ||||
|   lock_session | ||||
| } from './wasm/wasm_app.js'; | ||||
|  | ||||
| @@ -91,11 +147,13 @@ async function initVault() { | ||||
|       current_keypair_metadata, | ||||
|       current_keypair_public_key, | ||||
|       sign, | ||||
|       verify, | ||||
|       encrypt_data, | ||||
|       decrypt_data, | ||||
|       lock_session | ||||
|     }; | ||||
|  | ||||
|     isInitialized = true; | ||||
|     console.log('CryptoVault initialized successfully'); | ||||
|  | ||||
|     // Try to restore previous session | ||||
|     await restoreSession(); | ||||
| @@ -107,8 +165,23 @@ async function initVault() { | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Handle popup connection/disconnection | ||||
| chrome.runtime.onConnect.addListener((port) => { | ||||
|   if (port.name === 'popup') { | ||||
|     // If we have an active session, ensure keep-alive is running | ||||
|     if (currentSession) { | ||||
|       startKeepAlive(); | ||||
|     } | ||||
|  | ||||
|     port.onDisconnect.addListener(() => { | ||||
|       // Keep the keep-alive running even after popup disconnects | ||||
|       // This ensures session persistence across popup closes | ||||
|     }); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| // Handle messages from popup and content scripts | ||||
| chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { | ||||
| chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { | ||||
|   const handleRequest = async () => { | ||||
|     try { | ||||
|       if (!vault) { | ||||
| @@ -122,7 +195,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { | ||||
|  | ||||
|         case 'initSession': | ||||
|           await vault.init_session(request.keyspace, request.password); | ||||
|           await saveSession(request.keyspace); | ||||
|           await saveSessionWithKeepAlive(request.keyspace); | ||||
|           return { success: true }; | ||||
|  | ||||
|         case 'isUnlocked': | ||||
| @@ -134,37 +207,24 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { | ||||
|           return { success: true, result }; | ||||
|  | ||||
|         case 'listKeypairs': | ||||
|           console.log('Background: listing keypairs...'); | ||||
|           console.log('Background: vault object:', vault); | ||||
|           console.log('Background: vault.list_keypairs function:', vault.list_keypairs); | ||||
|  | ||||
|           // Check if session is unlocked first | ||||
|           const isUnlocked = vault.is_unlocked(); | ||||
|           console.log('Background: is session unlocked?', isUnlocked); | ||||
|  | ||||
|           if (!isUnlocked) { | ||||
|             console.log('Background: Session is not unlocked, cannot list keypairs'); | ||||
|             return { success: false, error: 'Session is not unlocked' }; | ||||
|           } | ||||
|  | ||||
|           try { | ||||
|             const keypairsRaw = await vault.list_keypairs(); | ||||
|             console.log('Background: keypairs raw result:', keypairsRaw); | ||||
|             console.log('Background: keypairs type:', typeof keypairsRaw); | ||||
|  | ||||
|             // Parse JSON string if needed | ||||
|             let keypairs; | ||||
|             if (typeof keypairsRaw === 'string') { | ||||
|               console.log('Background: Parsing JSON string...'); | ||||
|               keypairs = JSON.parse(keypairsRaw); | ||||
|             } else { | ||||
|               keypairs = keypairsRaw; | ||||
|             } | ||||
|  | ||||
|             console.log('Background: parsed keypairs:', keypairs); | ||||
|             console.log('Background: parsed keypairs type:', typeof keypairs); | ||||
|             console.log('Background: keypairs array length:', Array.isArray(keypairs) ? keypairs.length : 'not an array'); | ||||
|  | ||||
|             return { success: true, keypairs }; | ||||
|           } catch (listError) { | ||||
|             console.error('Background: Error calling list_keypairs:', listError); | ||||
| @@ -188,9 +248,67 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { | ||||
|           const signature = await vault.sign(new Uint8Array(request.message)); | ||||
|           return { success: true, signature }; | ||||
|  | ||||
|         case 'encrypt': | ||||
|           // Check if session is unlocked | ||||
|           if (!vault.is_unlocked()) { | ||||
|             return { success: false, error: 'Session is not unlocked' }; | ||||
|           } | ||||
|  | ||||
|           try { | ||||
|             // Convert message to Uint8Array for WASM | ||||
|             const messageBytes = new TextEncoder().encode(request.message); | ||||
|  | ||||
|             // Use WASM encrypt_data function with ChaCha20-Poly1305 | ||||
|             const encryptedData = await vault.encrypt_data(messageBytes); | ||||
|  | ||||
|             // Convert result to base64 for easy handling | ||||
|             const encryptedMessage = btoa(String.fromCharCode(...new Uint8Array(encryptedData))); | ||||
|             return { success: true, encryptedMessage }; | ||||
|           } catch (error) { | ||||
|             console.error('Encryption error:', error); | ||||
|             return { success: false, error: error.message }; | ||||
|           } | ||||
|  | ||||
|         case 'decrypt': | ||||
|           // Check if session is unlocked | ||||
|           if (!vault.is_unlocked()) { | ||||
|             return { success: false, error: 'Session is not unlocked' }; | ||||
|           } | ||||
|  | ||||
|           try { | ||||
|             // Convert base64 back to Uint8Array | ||||
|             const encryptedBytes = new Uint8Array(atob(request.encryptedMessage).split('').map(c => c.charCodeAt(0))); | ||||
|  | ||||
|             // Use WASM decrypt_data function with ChaCha20-Poly1305 | ||||
|             const decryptedData = await vault.decrypt_data(encryptedBytes); | ||||
|  | ||||
|             // Convert result back to string | ||||
|             const decryptedMessage = new TextDecoder().decode(new Uint8Array(decryptedData)); | ||||
|             return { success: true, decryptedMessage }; | ||||
|           } catch (error) { | ||||
|             console.error('Decryption error:', error); | ||||
|             return { success: false, error: error.message }; | ||||
|           } | ||||
|  | ||||
|         case 'verify': | ||||
|           // Check if a keypair is selected | ||||
|           try { | ||||
|             const metadata = vault.current_keypair_metadata(); | ||||
|             if (!metadata) { | ||||
|               return { success: false, error: 'No keypair selected' }; | ||||
|             } | ||||
|  | ||||
|             // Use WASM verify function | ||||
|             const isValid = await vault.verify(new Uint8Array(request.message), request.signature); | ||||
|             return { success: true, isValid }; | ||||
|           } catch (error) { | ||||
|             console.error('Verification error:', error); | ||||
|             return { success: false, error: error.message }; | ||||
|           } | ||||
|  | ||||
|         case 'lockSession': | ||||
|           vault.lock_session(); | ||||
|           await clearSession(); | ||||
|           await clearSessionWithKeepAlive(); | ||||
|           return { success: true }; | ||||
|  | ||||
|         case 'getStatus': | ||||
|   | ||||
							
								
								
									
										280
									
								
								crypto_vault_extension/js/errorHandler.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								crypto_vault_extension/js/errorHandler.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,280 @@ | ||||
| // Enhanced Error Handling System for CryptoVault Extension | ||||
|  | ||||
| class CryptoVaultError extends Error { | ||||
|   constructor(message, code, retryable = false, userMessage = null) { | ||||
|     super(message); | ||||
|     this.name = 'CryptoVaultError'; | ||||
|     this.code = code; | ||||
|     this.retryable = retryable; | ||||
|     this.userMessage = userMessage || message; | ||||
|     this.timestamp = Date.now(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Error codes for different types of errors | ||||
| const ERROR_CODES = { | ||||
|   // Network/Connection errors (retryable) | ||||
|   NETWORK_ERROR: 'NETWORK_ERROR', | ||||
|   TIMEOUT_ERROR: 'TIMEOUT_ERROR', | ||||
|   SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE', | ||||
|  | ||||
|   // Authentication errors (not retryable) | ||||
|   INVALID_PASSWORD: 'INVALID_PASSWORD', | ||||
|   SESSION_EXPIRED: 'SESSION_EXPIRED', | ||||
|   UNAUTHORIZED: 'UNAUTHORIZED', | ||||
|  | ||||
|   // Crypto errors (not retryable) | ||||
|   CRYPTO_ERROR: 'CRYPTO_ERROR', | ||||
|   INVALID_SIGNATURE: 'INVALID_SIGNATURE', | ||||
|   ENCRYPTION_FAILED: 'ENCRYPTION_FAILED', | ||||
|  | ||||
|   // Validation errors (not retryable) | ||||
|   INVALID_INPUT: 'INVALID_INPUT', | ||||
|   MISSING_KEYPAIR: 'MISSING_KEYPAIR', | ||||
|   INVALID_FORMAT: 'INVALID_FORMAT', | ||||
|  | ||||
|   // System errors (sometimes retryable) | ||||
|   WASM_ERROR: 'WASM_ERROR', | ||||
|   STORAGE_ERROR: 'STORAGE_ERROR', | ||||
|   UNKNOWN_ERROR: 'UNKNOWN_ERROR' | ||||
| }; | ||||
|  | ||||
| // User-friendly error messages | ||||
| const ERROR_MESSAGES = { | ||||
|   [ERROR_CODES.NETWORK_ERROR]: 'Connection failed. Please check your internet connection and try again.', | ||||
|   [ERROR_CODES.TIMEOUT_ERROR]: 'Operation timed out. Please try again.', | ||||
|   [ERROR_CODES.SERVICE_UNAVAILABLE]: 'Service is temporarily unavailable. Please try again later.', | ||||
|  | ||||
|   [ERROR_CODES.INVALID_PASSWORD]: 'Invalid password. Please check your password and try again.', | ||||
|   [ERROR_CODES.SESSION_EXPIRED]: 'Your session has expired. Please log in again.', | ||||
|   [ERROR_CODES.UNAUTHORIZED]: 'You are not authorized to perform this action.', | ||||
|  | ||||
|   [ERROR_CODES.CRYPTO_ERROR]: 'Cryptographic operation failed. Please try again.', | ||||
|   [ERROR_CODES.INVALID_SIGNATURE]: 'Invalid signature. Please verify your input.', | ||||
|   [ERROR_CODES.ENCRYPTION_FAILED]: 'Encryption failed. Please try again.', | ||||
|  | ||||
|   [ERROR_CODES.INVALID_INPUT]: 'Invalid input. Please check your data and try again.', | ||||
|   [ERROR_CODES.MISSING_KEYPAIR]: 'No keypair selected. Please select a keypair first.', | ||||
|   [ERROR_CODES.INVALID_FORMAT]: 'Invalid data format. Please check your input.', | ||||
|  | ||||
|   [ERROR_CODES.WASM_ERROR]: 'System error occurred. Please refresh and try again.', | ||||
|   [ERROR_CODES.STORAGE_ERROR]: 'Storage error occurred. Please try again.', | ||||
|   [ERROR_CODES.UNKNOWN_ERROR]: 'An unexpected error occurred. Please try again.' | ||||
| }; | ||||
|  | ||||
| // Determine if an error is retryable | ||||
| const RETRYABLE_ERRORS = new Set([ | ||||
|   ERROR_CODES.NETWORK_ERROR, | ||||
|   ERROR_CODES.TIMEOUT_ERROR, | ||||
|   ERROR_CODES.SERVICE_UNAVAILABLE, | ||||
|   ERROR_CODES.WASM_ERROR, | ||||
|   ERROR_CODES.STORAGE_ERROR | ||||
| ]); | ||||
|  | ||||
| // Enhanced error classification | ||||
| function classifyError(error) { | ||||
|   const errorMessage = getErrorMessage(error); | ||||
|  | ||||
|   // Network/Connection errors | ||||
|   if (errorMessage.includes('fetch') || errorMessage.includes('network') || errorMessage.includes('connection')) { | ||||
|     return new CryptoVaultError( | ||||
|       errorMessage, | ||||
|       ERROR_CODES.NETWORK_ERROR, | ||||
|       true, | ||||
|       ERROR_MESSAGES[ERROR_CODES.NETWORK_ERROR] | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Authentication errors | ||||
|   if (errorMessage.includes('password') || errorMessage.includes('Invalid password')) { | ||||
|     return new CryptoVaultError( | ||||
|       errorMessage, | ||||
|       ERROR_CODES.INVALID_PASSWORD, | ||||
|       false, | ||||
|       ERROR_MESSAGES[ERROR_CODES.INVALID_PASSWORD] | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   if (errorMessage.includes('session') || errorMessage.includes('not unlocked') || errorMessage.includes('expired')) { | ||||
|     return new CryptoVaultError( | ||||
|       errorMessage, | ||||
|       ERROR_CODES.SESSION_EXPIRED, | ||||
|       false, | ||||
|       ERROR_MESSAGES[ERROR_CODES.SESSION_EXPIRED] | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Crypto errors | ||||
|   if (errorMessage.includes('decryption error') || errorMessage.includes('aead::Error')) { | ||||
|     return new CryptoVaultError( | ||||
|       errorMessage, | ||||
|       ERROR_CODES.CRYPTO_ERROR, | ||||
|       false, | ||||
|       'Invalid password or corrupted data. Please check your password.' | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   if (errorMessage.includes('Crypto error') || errorMessage.includes('encryption')) { | ||||
|     return new CryptoVaultError( | ||||
|       errorMessage, | ||||
|       ERROR_CODES.CRYPTO_ERROR, | ||||
|       false, | ||||
|       ERROR_MESSAGES[ERROR_CODES.CRYPTO_ERROR] | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Validation errors | ||||
|   if (errorMessage.includes('No keypair selected')) { | ||||
|     return new CryptoVaultError( | ||||
|       errorMessage, | ||||
|       ERROR_CODES.MISSING_KEYPAIR, | ||||
|       false, | ||||
|       ERROR_MESSAGES[ERROR_CODES.MISSING_KEYPAIR] | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // WASM errors | ||||
|   if (errorMessage.includes('wasm') || errorMessage.includes('WASM')) { | ||||
|     return new CryptoVaultError( | ||||
|       errorMessage, | ||||
|       ERROR_CODES.WASM_ERROR, | ||||
|       true, | ||||
|       ERROR_MESSAGES[ERROR_CODES.WASM_ERROR] | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Default to unknown error | ||||
|   return new CryptoVaultError( | ||||
|     errorMessage, | ||||
|     ERROR_CODES.UNKNOWN_ERROR, | ||||
|     false, | ||||
|     ERROR_MESSAGES[ERROR_CODES.UNKNOWN_ERROR] | ||||
|   ); | ||||
| } | ||||
|  | ||||
| // Get error message from various error types | ||||
| function getErrorMessage(error) { | ||||
|   if (!error) return 'Unknown error'; | ||||
|  | ||||
|   if (typeof error === 'string') { | ||||
|     return error.trim(); | ||||
|   } | ||||
|  | ||||
|   if (error instanceof Error) { | ||||
|     return error.message; | ||||
|   } | ||||
|  | ||||
|   if (error.error) { | ||||
|     return getErrorMessage(error.error); | ||||
|   } | ||||
|  | ||||
|   if (error.message) { | ||||
|     return error.message; | ||||
|   } | ||||
|  | ||||
|   if (typeof error === 'object') { | ||||
|     try { | ||||
|       const stringified = JSON.stringify(error); | ||||
|       if (stringified && stringified !== '{}') { | ||||
|         return stringified; | ||||
|       } | ||||
|     } catch (e) { | ||||
|       // Ignore JSON stringify errors | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 'Unknown error'; | ||||
| } | ||||
|  | ||||
| // Retry logic with exponential backoff | ||||
| async function withRetry(operation, options = {}) { | ||||
|   const { | ||||
|     maxRetries = 3, | ||||
|     baseDelay = 1000, | ||||
|     maxDelay = 10000, | ||||
|     backoffFactor = 2, | ||||
|     onRetry = null | ||||
|   } = options; | ||||
|  | ||||
|   let lastError; | ||||
|  | ||||
|   for (let attempt = 0; attempt <= maxRetries; attempt++) { | ||||
|     try { | ||||
|       return await operation(); | ||||
|     } catch (error) { | ||||
|       const classifiedError = classifyError(error); | ||||
|       lastError = classifiedError; | ||||
|  | ||||
|       // Don't retry if it's the last attempt or error is not retryable | ||||
|       if (attempt === maxRetries || !classifiedError.retryable) { | ||||
|         throw classifiedError; | ||||
|       } | ||||
|  | ||||
|       // Calculate delay with exponential backoff | ||||
|       const delay = Math.min(baseDelay * Math.pow(backoffFactor, attempt), maxDelay); | ||||
|  | ||||
|       // Call retry callback if provided | ||||
|       if (onRetry) { | ||||
|         onRetry(attempt + 1, delay, classifiedError); | ||||
|       } | ||||
|  | ||||
|       // Wait before retrying | ||||
|       await new Promise(resolve => setTimeout(resolve, delay)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   throw lastError; | ||||
| } | ||||
|  | ||||
| // Enhanced operation wrapper with loading states | ||||
| async function executeOperation(operation, options = {}) { | ||||
|   const { | ||||
|     loadingElement = null, | ||||
|     successMessage = null, | ||||
|     showRetryProgress = false, | ||||
|     onProgress = null | ||||
|   } = options; | ||||
|  | ||||
|   // Show loading state | ||||
|   if (loadingElement) { | ||||
|     setButtonLoading(loadingElement, true); | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     const result = await withRetry(operation, { | ||||
|       ...options, | ||||
|       onRetry: (attempt, delay, error) => { | ||||
|         if (showRetryProgress && onProgress) { | ||||
|           onProgress(`Retrying... (${attempt}/${options.maxRetries || 3})`); | ||||
|         } | ||||
|         if (options.onRetry) { | ||||
|           options.onRetry(attempt, delay, error); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     // Show success message if provided | ||||
|     if (successMessage) { | ||||
|       showToast(successMessage, 'success'); | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
|   } catch (error) { | ||||
|     // Show user-friendly error message | ||||
|     showToast(error.userMessage || error.message, 'error'); | ||||
|     throw error; | ||||
|   } finally { | ||||
|     // Hide loading state | ||||
|     if (loadingElement) { | ||||
|       setButtonLoading(loadingElement, false); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Export for use in other modules | ||||
| window.CryptoVaultError = CryptoVaultError; | ||||
| window.ERROR_CODES = ERROR_CODES; | ||||
| window.classifyError = classifyError; | ||||
| window.getErrorMessage = getErrorMessage; | ||||
| window.withRetry = withRetry; | ||||
| window.executeOperation = executeOperation; | ||||
| @@ -11,9 +11,12 @@ | ||||
|         <div class="logo-icon">🔐</div> | ||||
|         <h1>CryptoVault</h1> | ||||
|       </div> | ||||
|       <div class="status-indicator" id="statusIndicator"> | ||||
|         <div class="status-dot"></div> | ||||
|         <span id="statusText">Initializing...</span> | ||||
|       <div class="header-actions"> | ||||
|         <button id="themeToggle" class="btn-icon-only" title="Switch to dark mode"> | ||||
|           <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|             <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path> | ||||
|           </svg> | ||||
|         </button> | ||||
|       </div> | ||||
|     </header> | ||||
|  | ||||
| @@ -38,13 +41,26 @@ | ||||
|  | ||||
|     <!-- Main Vault Section --> | ||||
|     <section class="section hidden" id="vaultSection"> | ||||
|       <div class="vault-header"> | ||||
|         <h2>Your Keypairs</h2> | ||||
|         <button id="lockBtn" class="btn btn-ghost">🔒 Lock</button> | ||||
|       <!-- Status Section --> | ||||
|       <div class="vault-status" id="vaultStatus"> | ||||
|         <div class="status-indicator" id="statusIndicator"> | ||||
|           <div class="status-content"> | ||||
|             <div class="status-dot"></div> | ||||
|             <span id="statusText">Initializing...</span> | ||||
|           </div> | ||||
|           <button id="lockBtn" class="btn btn-ghost btn-small hidden"> | ||||
|             <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|               <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect> | ||||
|               <circle cx="12" cy="16" r="1"></circle> | ||||
|               <path d="M7 11V7a5 5 0 0 1 10 0v4"></path> | ||||
|             </svg> | ||||
|             Lock | ||||
|           </button> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <!-- Add Keypair Toggle Button --> | ||||
|       <div class="add-keypair-toggle"> | ||||
|       <div class="vault-header"> | ||||
|         <h2>Your Keypairs</h2> | ||||
|         <button id="toggleAddKeypairBtn" class="btn btn-primary"> | ||||
|           <span class="btn-icon">+</span> | ||||
|           Add Keypair | ||||
| @@ -99,25 +115,88 @@ | ||||
|             <label>Public Key:</label> | ||||
|             <div class="public-key-container"> | ||||
|               <code id="selectedPublicKey">-</code> | ||||
|               <button id="copyPublicKeyBtn" class="btn-copy" title="Copy to clipboard">📋</button> | ||||
|               <button id="copyPublicKeyBtn" class="btn-copy" title="Copy to clipboard"> | ||||
|                 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                   <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> | ||||
|                   <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> | ||||
|                 </svg> | ||||
|               </button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <!-- Sign Message --> | ||||
|       <!-- Crypto Operations --> | ||||
|       <div class="card"> | ||||
|         <h3>Sign Message</h3> | ||||
|         <div class="form-group"> | ||||
|           <label for="messageInput">Message (hex or text)</label> | ||||
|           <textarea id="messageInput" placeholder="Enter message to sign" rows="3"></textarea> | ||||
|         <h3>Crypto Operations</h3> | ||||
|  | ||||
|         <!-- Operation Tabs --> | ||||
|         <div class="operation-tabs"> | ||||
|           <button class="tab-btn active" data-tab="encrypt">Encrypt</button> | ||||
|           <button class="tab-btn" data-tab="decrypt">Decrypt</button> | ||||
|           <button class="tab-btn" data-tab="sign">Sign</button> | ||||
|           <button class="tab-btn" data-tab="verify">Verify</button> | ||||
|         </div> | ||||
|         <button id="signBtn" class="btn btn-primary" disabled>Sign Message</button> | ||||
|         <div id="signatureResult" class="signature-result hidden"> | ||||
|           <label>Signature:</label> | ||||
|           <div class="signature-container"> | ||||
|             <code id="signatureValue"></code> | ||||
|             <button id="copySignatureBtn" class="btn-copy" title="Copy to clipboard">📋</button> | ||||
|  | ||||
|         <!-- Encrypt Tab --> | ||||
|         <div class="tab-content active" id="encrypt-tab"> | ||||
|           <div class="form-group"> | ||||
|             <label for="encryptMessageInput">Message to Encrypt</label> | ||||
|             <textarea id="encryptMessageInput" placeholder="Enter message to encrypt..." rows="3"></textarea> | ||||
|           </div> | ||||
|           <button id="encryptBtn" class="btn btn-primary" disabled>Encrypt Message</button> | ||||
|  | ||||
|           <div class="encrypt-result hidden" id="encryptResult"> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <!-- Decrypt Tab --> | ||||
|         <div class="tab-content" id="decrypt-tab"> | ||||
|           <div class="form-group"> | ||||
|             <label for="encryptedMessageInput">Encrypted Message</label> | ||||
|             <textarea id="encryptedMessageInput" placeholder="Enter encrypted message..." rows="3"></textarea> | ||||
|           </div> | ||||
|           <button id="decryptBtn" class="btn btn-primary" disabled>Decrypt Message</button> | ||||
|  | ||||
|           <div class="decrypt-result hidden" id="decryptResult"> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <!-- Sign Tab --> | ||||
|         <div class="tab-content" id="sign-tab"> | ||||
|           <div class="form-group"> | ||||
|             <label for="messageInput">Message to Sign</label> | ||||
|             <textarea id="messageInput" placeholder="Enter your message here..." rows="3"></textarea> | ||||
|           </div> | ||||
|           <button id="signBtn" class="btn btn-primary" disabled>Sign Message</button> | ||||
|  | ||||
|           <div class="signature-result hidden" id="signatureResult"> | ||||
|             <label>Signature:</label> | ||||
|             <div class="signature-container"> | ||||
|               <code id="signatureValue">-</code> | ||||
|               <button id="copySignatureBtn" class="btn-copy" title="Copy to clipboard"> | ||||
|                 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                   <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> | ||||
|                   <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> | ||||
|                 </svg> | ||||
|               </button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <!-- Verify Tab --> | ||||
|         <div class="tab-content" id="verify-tab"> | ||||
|           <div class="form-group"> | ||||
|             <label for="verifyMessageInput">Original Message</label> | ||||
|             <textarea id="verifyMessageInput" placeholder="Enter the original message..." rows="3"></textarea> | ||||
|           </div> | ||||
|           <div class="form-group"> | ||||
|             <label for="signatureToVerifyInput">Signature</label> | ||||
|             <input type="text" id="signatureToVerifyInput" placeholder="Enter signature to verify..."> | ||||
|           </div> | ||||
|           <button id="verifyBtn" class="btn btn-primary" disabled>Verify Signature</button> | ||||
|  | ||||
|           <div class="verify-result hidden" id="verifyResult"> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| @@ -129,10 +208,11 @@ | ||||
|       <p>Processing...</p> | ||||
|     </div> | ||||
|  | ||||
|     <!-- Toast Notifications --> | ||||
|     <div id="toast" class="toast hidden"></div> | ||||
|  | ||||
|   </div> | ||||
|  | ||||
|   <!-- Enhanced JavaScript modules --> | ||||
|   <script src="js/errorHandler.js"></script> | ||||
|   <script src="popup.js"></script> | ||||
| </body> | ||||
| </html> | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -202,33 +202,6 @@ function debugString(val) { | ||||
|     // TODO we could test for more things here, like `Set`s and `Map`s. | ||||
|     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 | ||||
|  * @param {string} keyspace | ||||
| @@ -266,6 +239,11 @@ export function 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 | ||||
|  * @returns {any} | ||||
| @@ -356,20 +334,81 @@ export function sign(message) { | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Verify a signature with the current session's selected keypair | ||||
|  * @param {Uint8Array} message | ||||
|  * @param {string} signature | ||||
|  * @returns {Promise<any>} | ||||
|  */ | ||||
| export function verify(message, signature) { | ||||
|     const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc); | ||||
|     const len0 = WASM_VECTOR_LEN; | ||||
|     const ptr1 = passStringToWasm0(signature, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); | ||||
|     const len1 = WASM_VECTOR_LEN; | ||||
|     const ret = wasm.verify(ptr0, len0, ptr1, len1); | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Encrypt data using the current session's keyspace symmetric cipher | ||||
|  * @param {Uint8Array} data | ||||
|  * @returns {Promise<any>} | ||||
|  */ | ||||
| export function encrypt_data(data) { | ||||
|     const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc); | ||||
|     const len0 = WASM_VECTOR_LEN; | ||||
|     const ret = wasm.encrypt_data(ptr0, len0); | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Decrypt data using the current session's keyspace symmetric cipher | ||||
|  * @param {Uint8Array} encrypted | ||||
|  * @returns {Promise<any>} | ||||
|  */ | ||||
| export function decrypt_data(encrypted) { | ||||
|     const ptr0 = passArray8ToWasm0(encrypted, wasm.__wbindgen_malloc); | ||||
|     const len0 = WASM_VECTOR_LEN; | ||||
|     const ret = wasm.decrypt_data(ptr0, len0); | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Initialize the scripting environment (must be called before run_rhai) | ||||
|  */ | ||||
| export function init_rhai_env() { | ||||
|     wasm.init_rhai_env(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 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]); | ||||
| } | ||||
|  | ||||
| function __wbg_adapter_32(arg0, arg1, arg2) { | ||||
|     wasm.closure89_externref_shim(arg0, arg1, arg2); | ||||
|     wasm.closure121_externref_shim(arg0, arg1, arg2); | ||||
| } | ||||
|  | ||||
| function __wbg_adapter_35(arg0, arg1, arg2) { | ||||
|     wasm.closure133_externref_shim(arg0, arg1, arg2); | ||||
|     wasm.closure150_externref_shim(arg0, arg1, arg2); | ||||
| } | ||||
|  | ||||
| function __wbg_adapter_38(arg0, arg1, arg2) { | ||||
|     wasm.closure188_externref_shim(arg0, arg1, arg2); | ||||
|     wasm.closure227_externref_shim(arg0, arg1, arg2); | ||||
| } | ||||
|  | ||||
| function __wbg_adapter_135(arg0, arg1, arg2, arg3) { | ||||
|     wasm.closure1847_externref_shim(arg0, arg1, arg2, arg3); | ||||
| function __wbg_adapter_138(arg0, arg1, arg2, arg3) { | ||||
|     wasm.closure1879_externref_shim(arg0, arg1, arg2, arg3); | ||||
| } | ||||
|  | ||||
| const __wbindgen_enum_IdbTransactionMode = ["readonly", "readwrite", "versionchange", "readwriteflush", "cleanup"]; | ||||
| @@ -519,7 +558,7 @@ function __wbg_get_imports() { | ||||
|                 const a = state0.a; | ||||
|                 state0.a = 0; | ||||
|                 try { | ||||
|                     return __wbg_adapter_135(a, state0.b, arg0, arg1); | ||||
|                     return __wbg_adapter_138(a, state0.b, arg0, arg1); | ||||
|                 } finally { | ||||
|                     state0.a = a; | ||||
|                 } | ||||
| @@ -673,16 +712,16 @@ function __wbg_get_imports() { | ||||
|         const ret = false; | ||||
|         return ret; | ||||
|     }; | ||||
|     imports.wbg.__wbindgen_closure_wrapper288 = function(arg0, arg1, arg2) { | ||||
|         const ret = makeMutClosure(arg0, arg1, 90, __wbg_adapter_32); | ||||
|     imports.wbg.__wbindgen_closure_wrapper378 = function(arg0, arg1, arg2) { | ||||
|         const ret = makeMutClosure(arg0, arg1, 122, __wbg_adapter_32); | ||||
|         return ret; | ||||
|     }; | ||||
|     imports.wbg.__wbindgen_closure_wrapper518 = function(arg0, arg1, arg2) { | ||||
|         const ret = makeMutClosure(arg0, arg1, 134, __wbg_adapter_35); | ||||
|     imports.wbg.__wbindgen_closure_wrapper549 = function(arg0, arg1, arg2) { | ||||
|         const ret = makeMutClosure(arg0, arg1, 151, __wbg_adapter_35); | ||||
|         return ret; | ||||
|     }; | ||||
|     imports.wbg.__wbindgen_closure_wrapper776 = function(arg0, arg1, arg2) { | ||||
|         const ret = makeMutClosure(arg0, arg1, 189, __wbg_adapter_38); | ||||
|     imports.wbg.__wbindgen_closure_wrapper857 = function(arg0, arg1, arg2) { | ||||
|         const ret = makeMutClosure(arg0, arg1, 228, __wbg_adapter_38); | ||||
|         return ret; | ||||
|     }; | ||||
|     imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user