diff --git a/crypto_vault_extension/background.js b/crypto_vault_extension/background.js index 9201ec9..191cd1a 100644 --- a/crypto_vault_extension/background.js +++ b/crypto_vault_extension/background.js @@ -2,6 +2,9 @@ let vault = null; let isInitialized = false; let currentSession = null; let keepAliveInterval = null; +let sessionTimeoutDuration = 15; // Default 15 seconds +let sessionTimeoutId = null; // Background timer +let popupPort = null; // Track popup connection // Utility function to convert Uint8Array to hex function toHex(uint8Array) { @@ -9,53 +12,78 @@ function toHex(uint8Array) { .map(b => b.toString(16).padStart(2, '0')) .join(''); } +// Background session timeout management +async function loadTimeoutSetting() { + const result = await chrome.storage.local.get(['sessionTimeout']); + sessionTimeoutDuration = result.sessionTimeout || 15; +} +function startSessionTimeout() { + clearSessionTimeout(); + if (currentSession && sessionTimeoutDuration > 0) { + sessionTimeoutId = setTimeout(async () => { + if (vault && currentSession) { + // Lock the session + vault.lock_session(); + await sessionManager.clear(); + // Notify popup if it's open + if (popupPort) { + popupPort.postMessage({ + type: 'sessionTimeout', + message: 'Session timed out due to inactivity' + }); + } + } + }, sessionTimeoutDuration * 1000); + } +} + +function clearSessionTimeout() { + if (sessionTimeoutId) { + clearTimeout(sessionTimeoutId); + sessionTimeoutId = null; + } +} + +function resetSessionTimeout() { + if (currentSession) { + startSessionTimeout(); + } +} // Session persistence functions async function saveSession(keyspace) { currentSession = { keyspace, timestamp: Date.now() }; // 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); - } + await chrome.storage.session.set({ cryptoVaultSession: currentSession }); + await chrome.storage.local.set({ cryptoVaultSessionBackup: currentSession }); } async function loadSession() { - try { - // Try session storage first - let result = await chrome.storage.session.get(['cryptoVaultSession']); - if (result.cryptoVaultSession) { - currentSession = result.cryptoVaultSession; - return currentSession; - } + // Try session storage first + let result = await chrome.storage.session.get(['cryptoVaultSession']); + if (result.cryptoVaultSession) { + currentSession = result.cryptoVaultSession; + 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) { - console.error('Failed to load session:', error); + // 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; } return null; } async function clearSession() { currentSession = null; - try { - await chrome.storage.session.remove(['cryptoVaultSession']); - await chrome.storage.local.remove(['cryptoVaultSessionBackup']); - } catch (error) { - console.error('Failed to clear session:', error); - } + await chrome.storage.session.remove(['cryptoVaultSession']); + await chrome.storage.local.remove(['cryptoVaultSessionBackup']); } // Keep service worker alive @@ -64,12 +92,8 @@ function startKeepAlive() { 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 - }); + chrome.storage.session.get(['keepAlive']).catch(() => {}); }, 20000); } @@ -80,54 +104,39 @@ function stopKeepAlive() { } } -// Enhanced session management with keep-alive -async function saveSessionWithKeepAlive(keyspace) { - await saveSession(keyspace); - startKeepAlive(); -} - -async function clearSessionWithKeepAlive() { - await clearSession(); - stopKeepAlive(); -} +// Consolidated session management +const sessionManager = { + async save(keyspace) { + await saveSession(keyspace); + startKeepAlive(); + await loadTimeoutSetting(); + startSessionTimeout(); + }, + async clear() { + await clearSession(); + stopKeepAlive(); + clearSessionTimeout(); + } +}; async function restoreSession() { const session = await loadSession(); if (session && vault) { - try { - // Check if the session is still valid by testing if vault is unlocked - const isUnlocked = vault.is_unlocked(); - if (isUnlocked) { - // Restart keep-alive for restored session - startKeepAlive(); - return session; - } else { - await clearSessionWithKeepAlive(); - } - } catch (error) { - console.error('Error checking session validity:', error); - await clearSessionWithKeepAlive(); + // Check if the session is still valid by testing if vault is unlocked + const isUnlocked = vault.is_unlocked(); + if (isUnlocked) { + // Restart keep-alive for restored session + startKeepAlive(); + return session; + } else { + await sessionManager.clear(); } } return null; } // Import WASM module functions -import init, { - create_keyspace, - init_session, - is_unlocked, - add_keypair, - list_keypairs, - select_keypair, - current_keypair_metadata, - current_keypair_public_key, - sign, - verify, - encrypt_data, - decrypt_data, - lock_session -} from './wasm/wasm_app.js'; +import init, * as wasmFunctions from './wasm/wasm_app.js'; // Initialize WASM module async function initVault() { @@ -138,23 +147,8 @@ async function initVault() { const wasmUrl = chrome.runtime.getURL('wasm/wasm_app_bg.wasm'); await init(wasmUrl); - // Create a vault object with all the imported functions - vault = { - create_keyspace, - init_session, - is_unlocked, - add_keypair, - list_keypairs, - select_keypair, - current_keypair_metadata, - current_keypair_public_key, - sign, - verify, - encrypt_data, - decrypt_data, - lock_session - }; - + // Use imported functions directly + vault = wasmFunctions; isInitialized = true; // Try to restore previous session @@ -167,20 +161,108 @@ 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 - }); +// Consolidated message handlers +const messageHandlers = { + createKeyspace: async (request) => { + await vault.create_keyspace(request.keyspace, request.password); + return { success: true }; + }, + + initSession: async (request) => { + await vault.init_session(request.keyspace, request.password); + await sessionManager.save(request.keyspace); + return { success: true }; + }, + + isUnlocked: () => ({ success: true, unlocked: vault.is_unlocked() }), + + addKeypair: async (request) => { + const result = await vault.add_keypair(request.keyType, request.metadata); + return { success: true, result }; + }, + + listKeypairs: async () => { + if (!vault.is_unlocked()) { + return { success: false, error: 'Session is not unlocked' }; + } + const keypairsRaw = await vault.list_keypairs(); + const keypairs = typeof keypairsRaw === 'string' ? JSON.parse(keypairsRaw) : keypairsRaw; + return { success: true, keypairs }; + }, + + selectKeypair: (request) => { + vault.select_keypair(request.keyId); + return { success: true }; + }, + + getCurrentKeypairMetadata: () => ({ success: true, metadata: vault.current_keypair_metadata() }), + + getCurrentKeypairPublicKey: () => ({ success: true, publicKey: toHex(vault.current_keypair_public_key()) }), + + sign: async (request) => { + const signature = await vault.sign(new Uint8Array(request.message)); + return { success: true, signature }; + }, + + encrypt: async (request) => { + if (!vault.is_unlocked()) { + return { success: false, error: 'Session is not unlocked' }; + } + const messageBytes = new TextEncoder().encode(request.message); + const encryptedData = await vault.encrypt_data(messageBytes); + const encryptedMessage = btoa(String.fromCharCode(...new Uint8Array(encryptedData))); + return { success: true, encryptedMessage }; + }, + + decrypt: async (request) => { + if (!vault.is_unlocked()) { + return { success: false, error: 'Session is not unlocked' }; + } + const encryptedBytes = new Uint8Array(atob(request.encryptedMessage).split('').map(c => c.charCodeAt(0))); + const decryptedData = await vault.decrypt_data(encryptedBytes); + const decryptedMessage = new TextDecoder().decode(new Uint8Array(decryptedData)); + return { success: true, decryptedMessage }; + }, + + verify: async (request) => { + const metadata = vault.current_keypair_metadata(); + if (!metadata) { + return { success: false, error: 'No keypair selected' }; + } + const isValid = await vault.verify(new Uint8Array(request.message), request.signature); + return { success: true, isValid }; + }, + + lockSession: async () => { + vault.lock_session(); + await sessionManager.clear(); + return { success: true }; + }, + + getStatus: async () => { + const status = vault ? vault.is_unlocked() : false; + const session = await loadSession(); + return { + success: true, + status, + session: session ? { keyspace: session.keyspace } : null + }; + }, + + // Timeout management handlers + resetTimeout: async () => { + resetSessionTimeout(); + return { success: true }; + }, + + updateTimeout: async (request) => { + sessionTimeoutDuration = request.timeout; + await chrome.storage.local.set({ sessionTimeout: request.timeout }); + resetSessionTimeout(); // Restart with new duration + return { success: true }; } -}); +}; // Handle messages from popup and content scripts chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { @@ -190,143 +272,13 @@ chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { await initVault(); } - switch (request.action) { - case 'createKeyspace': - await vault.create_keyspace(request.keyspace, request.password); - return { success: true }; - - case 'initSession': - await vault.init_session(request.keyspace, request.password); - await saveSessionWithKeepAlive(request.keyspace); - return { success: true }; - - case 'isUnlocked': - const unlocked = vault.is_unlocked(); - return { success: true, unlocked }; - - case 'addKeypair': - const result = await vault.add_keypair(request.keyType, request.metadata); - return { success: true, result }; - - case 'listKeypairs': - // Check if session is unlocked first - const isUnlocked = vault.is_unlocked(); - - if (!isUnlocked) { - return { success: false, error: 'Session is not unlocked' }; - } - - try { - const keypairsRaw = await vault.list_keypairs(); - - // Parse JSON string if needed - let keypairs; - if (typeof keypairsRaw === 'string') { - keypairs = JSON.parse(keypairsRaw); - } else { - keypairs = keypairsRaw; - } - - return { success: true, keypairs }; - } catch (listError) { - console.error('Background: Error calling list_keypairs:', listError); - throw listError; - } - - case 'selectKeypair': - vault.select_keypair(request.keyId); - return { success: true }; - - case 'getCurrentKeypairMetadata': - const metadata = vault.current_keypair_metadata(); - return { success: true, metadata }; - - case 'getCurrentKeypairPublicKey': - const publicKey = vault.current_keypair_public_key(); - const hexKey = toHex(publicKey); - return { success: true, publicKey: hexKey }; - - case 'sign': - 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 clearSessionWithKeepAlive(); - return { success: true }; - - case 'getStatus': - const status = vault ? vault.is_unlocked() : false; - const session = await loadSession(); - return { - success: true, - status, - session: session ? { keyspace: session.keyspace } : null - }; - - default: - throw new Error('Unknown action: ' + request.action); + const handler = messageHandlers[request.action]; + if (handler) { + return await handler(request); + } else { + throw new Error('Unknown action: ' + request.action); } } catch (error) { - console.error('Background script error:', error); return { success: false, error: error.message }; } }; @@ -342,4 +294,23 @@ chrome.runtime.onStartup.addListener(() => { chrome.runtime.onInstalled.addListener(() => { initVault(); +}); + +// Handle popup connection for keep-alive and timeout notifications +chrome.runtime.onConnect.addListener((port) => { + if (port.name === 'popup') { + // Track popup connection + popupPort = port; + + // If we have an active session, ensure keep-alive is running + if (currentSession) { + startKeepAlive(); + } + + port.onDisconnect.addListener(() => { + // Popup closed, clear reference and stop keep-alive + popupPort = null; + stopKeepAlive(); + }); + } }); \ No newline at end of file diff --git a/crypto_vault_extension/icons/icon128.png b/crypto_vault_extension/icons/icon128.png index e5869e4..3e8e2e1 100644 Binary files a/crypto_vault_extension/icons/icon128.png and b/crypto_vault_extension/icons/icon128.png differ diff --git a/crypto_vault_extension/icons/icon16.png b/crypto_vault_extension/icons/icon16.png index 5348679..0a943c9 100644 Binary files a/crypto_vault_extension/icons/icon16.png and b/crypto_vault_extension/icons/icon16.png differ diff --git a/crypto_vault_extension/icons/icon32.png b/crypto_vault_extension/icons/icon32.png new file mode 100644 index 0000000..fac3a65 Binary files /dev/null and b/crypto_vault_extension/icons/icon32.png differ diff --git a/crypto_vault_extension/icons/icon36.png b/crypto_vault_extension/icons/icon36.png new file mode 100644 index 0000000..e21d632 Binary files /dev/null and b/crypto_vault_extension/icons/icon36.png differ diff --git a/crypto_vault_extension/icons/icon48.png b/crypto_vault_extension/icons/icon48.png index 3d34ec4..046c982 100644 Binary files a/crypto_vault_extension/icons/icon48.png and b/crypto_vault_extension/icons/icon48.png differ diff --git a/crypto_vault_extension/js/errorHandler.js b/crypto_vault_extension/js/errorHandler.js index 585f076..662e8ec 100644 --- a/crypto_vault_extension/js/errorHandler.js +++ b/crypto_vault_extension/js/errorHandler.js @@ -1,5 +1,3 @@ -// Enhanced Error Handling System for CryptoVault Extension - class CryptoVaultError extends Error { constructor(message, code, retryable = false, userMessage = null) { super(message); @@ -11,35 +9,24 @@ class CryptoVaultError extends Error { } } -// 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.', @@ -62,7 +49,6 @@ const ERROR_MESSAGES = { [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, @@ -71,11 +57,9 @@ const RETRYABLE_ERRORS = new Set([ 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, @@ -85,7 +69,6 @@ function classifyError(error) { ); } - // Authentication errors if (errorMessage.includes('password') || errorMessage.includes('Invalid password')) { return new CryptoVaultError( errorMessage, @@ -104,7 +87,6 @@ function classifyError(error) { ); } - // Crypto errors if (errorMessage.includes('decryption error') || errorMessage.includes('aead::Error')) { return new CryptoVaultError( errorMessage, @@ -123,7 +105,6 @@ function classifyError(error) { ); } - // Validation errors if (errorMessage.includes('No keypair selected')) { return new CryptoVaultError( errorMessage, @@ -133,7 +114,6 @@ function classifyError(error) { ); } - // WASM errors if (errorMessage.includes('wasm') || errorMessage.includes('WASM')) { return new CryptoVaultError( errorMessage, @@ -143,7 +123,6 @@ function classifyError(error) { ); } - // Default to unknown error return new CryptoVaultError( errorMessage, ERROR_CODES.UNKNOWN_ERROR, @@ -152,7 +131,6 @@ function classifyError(error) { ); } -// Get error message from various error types function getErrorMessage(error) { if (!error) return 'Unknown error'; @@ -179,14 +157,13 @@ function getErrorMessage(error) { return stringified; } } catch (e) { - // Ignore JSON stringify errors + // Silently handle JSON stringify errors } } return 'Unknown error'; } -// Retry logic with exponential backoff async function withRetry(operation, options = {}) { const { maxRetries = 3, @@ -205,20 +182,16 @@ async function withRetry(operation, options = {}) { 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)); } } @@ -226,7 +199,6 @@ async function withRetry(operation, options = {}) { throw lastError; } -// Enhanced operation wrapper with loading states async function executeOperation(operation, options = {}) { const { loadingElement = null, @@ -235,7 +207,6 @@ async function executeOperation(operation, options = {}) { onProgress = null } = options; - // Show loading state if (loadingElement) { setButtonLoading(loadingElement, true); } @@ -253,25 +224,21 @@ async function executeOperation(operation, options = {}) { } }); - // 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; diff --git a/crypto_vault_extension/manifest.json b/crypto_vault_extension/manifest.json index bad7309..814bf08 100644 --- a/crypto_vault_extension/manifest.json +++ b/crypto_vault_extension/manifest.json @@ -9,17 +9,23 @@ "activeTab" ], + "icons": { + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + }, + "background": { "service_worker": "background.js", "type": "module" }, - - "action": { "default_popup": "popup.html", "default_icon": { "16": "icons/icon16.png", + "32": "icons/icon32.png", "48": "icons/icon48.png", "128": "icons/icon128.png" } diff --git a/crypto_vault_extension/popup.html b/crypto_vault_extension/popup.html index dee003b..551e4ee 100644 --- a/crypto_vault_extension/popup.html +++ b/crypto_vault_extension/popup.html @@ -12,6 +12,23 @@