From 31975aa9d3d4da8316861ed74139229a76645865 Mon Sep 17 00:00:00 2001 From: zaelgohary Date: Thu, 29 May 2025 08:57:25 +0300 Subject: [PATCH] Add session timeout, refactor session manager, reduce code duplication, update icons & styling --- crypto_vault_extension/background.js | 443 ++++++------- crypto_vault_extension/icons/icon128.png | Bin 1958 -> 7701 bytes crypto_vault_extension/icons/icon16.png | Bin 454 -> 676 bytes crypto_vault_extension/icons/icon32.png | Bin 0 -> 1537 bytes crypto_vault_extension/icons/icon36.png | Bin 0 -> 1636 bytes crypto_vault_extension/icons/icon48.png | Bin 1105 -> 2154 bytes crypto_vault_extension/js/errorHandler.js | 35 +- crypto_vault_extension/manifest.json | 10 +- crypto_vault_extension/popup.html | 22 +- crypto_vault_extension/popup.js | 763 +++++++++++----------- crypto_vault_extension/styles/popup.css | 311 +++++---- 11 files changed, 765 insertions(+), 819 deletions(-) create mode 100644 crypto_vault_extension/icons/icon32.png create mode 100644 crypto_vault_extension/icons/icon36.png 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 e5869e4bb9d87bd54ec6a76dcef3fc7a95a210b6..3e8e2e102d12f6192b8ce64578d2c3812586061d 100644 GIT binary patch literal 7701 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 bpOXRd5OR2a!NuD<9~ST=`nuJ-QnUUAymu?0 diff --git a/crypto_vault_extension/icons/icon16.png b/crypto_vault_extension/icons/icon16.png index 53486793ec88f6c7acce3e13ad5d4ec3c64e059b..0a943c9057e556e0fa3d919317ac27d034631779 100644 GIT binary patch delta 663 zcmV;I0%-ll1Ed9z8Gi-<001BJ|6u?C00DDSM?wIu&K&6g00Le~L_t(IPmR(|NK{c2 z0N``)eQ$i@n9q5QKa0pviK0y@C4;U+kXl61LIjsB+P0_}L#V)og!s2A4XlBA5<-*) zZ9`hLDEf8NU~1Mh<4n`cn`Yj;zk5PuMuWz@s|)9xk8|$11b=oNRaJeWD0VZ(P9vg7 z2;qphUX^+MvN17H&(J>U0Lh4=@x193ER#8KNU=T?3VA#p&#)v(R~Tbo2q6{#P;?4) z$`3W)pe<0&jd_aX5Fpg#Y;yJZfc2&Xgu~%8ilRI<4x^&!rAezOa&9XWeUd(hAH z=(3Jxw`o)!Re#*(qJwm@@wz!s0%m7tFZz7G>0~lF)z{Z|quANvaBEMK7#X8Z!DrZX zhEi;BG3>WEojDvZE|&mRRWH@n)=s5Tsmb2n-rGfBetNL0CHr_%aaK+MIt2nCaD+_W zVnfY?`uG;GsvhujsXpjPM|y}Ei&WE%$HZ-UQ4&M{pc~?;ooGkVK;z zboj^9tA9JdLsvj{J3)b#A7KmlBm;6t#C6u$KAQZz1Kf87WNGg=;0W+_$qNEP=3Aa# z@Q){7lz{lXiff8w4hRIj<+gwTu)bCeR@N1XYpk>5ZhWHzMDA2HGm)MXDJtavAb^_Y xS}r8qnYM}ev%l6@wrC%L(pTO`?mtuh0MiN)AQ1Sq63qYr002ovPDHLkV1gk=HCg}w literal 454 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZNnx_=LCuX@&;6qW}N@v){R72((wVB*-tA!D7q1 zh+lush<$oxl9keClJ6fgFKA4U9?N?k;Lk95c28 zIqW5#zOL*qSq1nll@Hx){s0u}_jGX#k+^K@@5$6+z_EDo)m`tyUhn(xU!U(r7N6&d zMg|7wKQ`~xA2jPX8}Kaoe@?4QxIv|F3U|<%2qT}^3CRb868^C+U8NOT=h*ApeZwr` z_RiNA7rsBBr<`&2k4}5zos2UcMm7gs{J0c7)xw=-q^Cu1-Vhu9?C7Px)yGcYrR9HvFmwRkfRUXH`=bZbRd+$u?Fw)>Eiy|f)ixCwuO3bpGtjU`AmygCx zBx{q6`-dhbDk^FQMIo?(aaDGcVptZchL{lJBR>9+eJHwNS4CQlN>N~1+G(fr?45hh zYcmxp^f4t&6YpO)x#xF&pY#2_&L#MXA2OT&J$v>fwAPmqLQ;$|bMks2M5L4&6ha*B z?CdOm^Z;UBGQP+%_(B8PQkn!|gzSltTK2ckBb;pC5TLue`zlIlhhC1P(~m#&^XbB`GTcJ11= zJmxsh#$5L|mSs`K7zmlVTx$&}C1iD|n9Oz^xsZQ!vB^{_5=Os-?xmV6u?%^Mlw=@? zjF$=_+kPg*v;aGI?wsd1&gMiSvBtJ-i*t@J45d=4E}=So0zwESgt#%sgj@Io3;SP0 zoQ|T(%|n%tJz3TCYZvV&G*^j?hmDi9SC*a%$Fu;uckjN%_x-K$czh}6 zoD_@2|4A9H6#`#dmZeVvL{UT>$5}z6{MN;#*A^{`_CqRyQq6ogKODSp!!`taZwG6? z%K5!-%P!ZHx)50Z2m`FSVtwsceV>^C9!e&Y>ulR@DHI9_tJRgiTeIdr(XpUnu+-QE2} zYisKxwr#t^xg5Uy>MNE`rzfPJ4073rLxfbpqN4|v6khS?sVwL;dezTg6QokAnNR0(=P||Ry zgwRBehSs4_kuE{Sz7^@}AOR*yc|xo&UiR9OHrYDg<9Auc#P``o#kAjpD#1{%2~P`f zMf3fE`G<;Ge0_BqV#Zm}_p;Y!+sLFm(P6Qf6X{%tf`DM~uniC63X}!{`@w5AJIP&Fu#83GOC8_2G`%~%Q(1z9f&u+&* zcEUPpB&)-fj-Px*9!W$&R9Hvtms@NURT#&=GiPRJc6avDZB1K1s7(Y!E*60TCXfmVq7uc2S{|VA zAVeQXhyt3>WKpE$ZV)lyR%@Xof{Aa6K=2kLu{4zy2nD&>3)}9kdzo39V?35p$_t;iMxWxgs*CLw9TW{icig(8q$;{0 zBcR&a+9E*^8gg=SX0t4dbX`yGUemM$rBoRLffnm+nB4^4^05M}uZ_IIx zbIW0mRY@kRGj9KW>Za(c82}wSZ$GW;xCe}v4kFciT>=Pous}l;teAj70erG>St004k_a6h zFmg(us}1hTe5c|Sc~X=tkt~dO44}K!0(L}_ov|}veSgNM!QH8*9<=P$qb88-6$PUR zqd{lhW241m2)bK&urmZG0_ceI2c~nOcaay2;U*mnROhbDcgcMUF^|nKC{RiO+-S=qaC)FaCXnhCc@ro*rdAXLcUqj_ zz$BS6NkA`AD_J$Qt|Pe7ntRVfGC>fKEj3YBS5=v}@7kLBEZb)S^_P8? zOsyLTuD9lbv)@DJ;&)}PtJmQhU*?6_$UwX8eWp&9eRitMJmIgm<$#^^T~9RwQJ-o# zT)MmKowO;tywkqC(5Y^A@cJ{^>Ogc zrTz)Xl*pjSAO^kO+hc1%vSlesbS#^308;+jWyvY&@w@=cqW5wq%-J8fGSb)o8BluU izAyd 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 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 @@

CryptoVault

+
+ + +
- `; - - // Add to document document.body.appendChild(toast); - - // Trigger entrance animation setTimeout(() => toast.classList.add('toast-show'), 10); - - // Auto-remove after 4 seconds setTimeout(() => { if (toast.parentElement) { toast.classList.add('toast-hide'); @@ -36,30 +30,6 @@ function showToast(message, type = 'info') { }, 4000); } -function getToastIcon(type) { - switch (type) { - case 'success': - return ` - - `; - case 'error': - return ` - - - - `; - case 'info': - default: - return ` - - - - `; - } -} - - - // Enhanced loading states for buttons function setButtonLoading(button, loading = true) { if (loading) { @@ -109,16 +79,17 @@ function showSection(sectionId) { } function setStatus(text, isConnected = false) { - document.getElementById('statusText').textContent = text; - const indicator = document.getElementById('statusIndicator'); - indicator.classList.toggle('connected', isConnected); - - // Show/hide lock button - only show when session is unlocked + const statusText = document.getElementById('statusText'); + const statusSection = document.getElementById('vaultStatus'); const lockBtn = document.getElementById('lockBtn'); - if (lockBtn) { - // Only show lock button when connected AND status indicates unlocked session - const isUnlocked = isConnected && text.toLowerCase().startsWith('connected to'); - lockBtn.classList.toggle('hidden', !isUnlocked); + + if (isConnected && text) { + // Show keyspace name and status section + statusText.textContent = text; + statusSection.classList.remove('hidden'); + if (lockBtn) { + lockBtn.classList.remove('hidden'); + } } } @@ -146,12 +117,20 @@ function stringToUint8Array(str) { // DOM Elements const elements = { + // Authentication elements keyspaceInput: document.getElementById('keyspaceInput'), passwordInput: document.getElementById('passwordInput'), createKeyspaceBtn: document.getElementById('createKeyspaceBtn'), loginBtn: document.getElementById('loginBtn'), + + // Header elements lockBtn: document.getElementById('lockBtn'), themeToggle: document.getElementById('themeToggle'), + settingsToggle: document.getElementById('settingsToggle'), + settingsDropdown: document.getElementById('settingsDropdown'), + timeoutInput: document.getElementById('timeoutInput'), + + // Keypair management elements toggleAddKeypairBtn: document.getElementById('toggleAddKeypairBtn'), addKeypairCard: document.getElementById('addKeypairCard'), keyTypeSelect: document.getElementById('keyTypeSelect'), @@ -160,34 +139,91 @@ const elements = { cancelAddKeypairBtn: document.getElementById('cancelAddKeypairBtn'), keypairsList: document.getElementById('keypairsList'), - // Sign tab + // Crypto operation elements - Sign tab messageInput: document.getElementById('messageInput'), signBtn: document.getElementById('signBtn'), signatureResult: document.getElementById('signatureResult'), copySignatureBtn: document.getElementById('copySignatureBtn'), - // Encrypt tab + // Crypto operation elements - Encrypt tab encryptMessageInput: document.getElementById('encryptMessageInput'), encryptBtn: document.getElementById('encryptBtn'), encryptResult: document.getElementById('encryptResult'), - // Decrypt tab + // Crypto operation elements - Decrypt tab encryptedMessageInput: document.getElementById('encryptedMessageInput'), decryptBtn: document.getElementById('decryptBtn'), decryptResult: document.getElementById('decryptResult'), - // Verify tab + // Crypto operation elements - Verify tab verifyMessageInput: document.getElementById('verifyMessageInput'), signatureToVerifyInput: document.getElementById('signatureToVerifyInput'), verifyBtn: document.getElementById('verifyBtn'), verifyResult: document.getElementById('verifyResult'), }; +// Global state variables let currentKeyspace = null; let selectedKeypairId = null; let backgroundPort = null; +let sessionTimeoutDuration = 15; // Default 15 seconds +// Session timeout management +function handleError(error, context, shouldShowToast = true) { + const errorMessage = error?.message || 'An unexpected error occurred'; + if (shouldShowToast) { + showToast(`${context}: ${errorMessage}`, 'error'); + } +} + +function validateInput(value, fieldName, options = {}) { + const { minLength = 1, maxLength = 1000, required = true } = options; + + if (required && (!value || !value.trim())) { + showToast(`${fieldName} is required`, 'error'); + return false; + } + + if (value && value.length < minLength) { + showToast(`${fieldName} must be at least ${minLength} characters`, 'error'); + return false; + } + + if (value && value.length > maxLength) { + showToast(`${fieldName} must be less than ${maxLength} characters`, 'error'); + return false; + } + + return true; +} +async function loadTimeoutSetting() { + const result = await chrome.storage.local.get(['sessionTimeout']); + sessionTimeoutDuration = result.sessionTimeout || 15; + if (elements.timeoutInput) { + elements.timeoutInput.value = sessionTimeoutDuration; + } +} + +async function checkSessionTimeout() { + const result = await chrome.storage.local.get(['sessionTimedOut']); + if (result.sessionTimedOut) { + // Clear the flag + await chrome.storage.local.remove(['sessionTimedOut']); + // Show timeout notification + showToast('Session timed out due to inactivity', 'info'); + } +} +async function saveTimeoutSetting(timeout) { + sessionTimeoutDuration = timeout; + await sendMessage('updateTimeout', { timeout }); +} + +async function resetSessionTimeout() { + if (currentKeyspace) { + await sendMessage('resetTimeout'); + } +} // Theme management function initializeTheme() { @@ -205,18 +241,46 @@ function toggleTheme() { updateThemeIcon(newTheme); } +// Settings dropdown management +function toggleSettingsDropdown() { + const dropdown = elements.settingsDropdown; + if (dropdown) { + dropdown.classList.toggle('hidden'); + } +} + +function closeSettingsDropdown() { + const dropdown = elements.settingsDropdown; + if (dropdown) { + dropdown.classList.add('hidden'); + } +} + function updateThemeIcon(theme) { const themeToggle = elements.themeToggle; if (!themeToggle) return; if (theme === 'dark') { - themeToggle.innerHTML = '☀️'; + // Bright sun SVG for dark theme + themeToggle.innerHTML = ` + + + + + + + + + + + + `; themeToggle.title = 'Switch to light mode'; } else { - // Dark crescent moon SVG for better visibility + // Dark crescent moon SVG for light theme themeToggle.innerHTML = ` - - + + `; themeToggle.title = 'Switch to dark mode'; @@ -225,14 +289,30 @@ function updateThemeIcon(theme) { // Establish connection to background script for keep-alive function connectToBackground() { - try { - backgroundPort = chrome.runtime.connect({ name: 'popup' }); - backgroundPort.onDisconnect.addListener(() => { - backgroundPort = null; - }); - } catch (error) { - // Silently handle connection errors - } + backgroundPort = chrome.runtime.connect({ name: 'popup' }); + + // Listen for messages from background script + backgroundPort.onMessage.addListener((message) => { + if (message.type === 'sessionTimeout') { + // Update UI state to reflect locked session + currentKeyspace = null; + selectedKeypairId = null; + setStatus('', false); + showSection('authSection'); + clearVaultState(); + + // Clear form inputs + if (elements.keyspaceInput) elements.keyspaceInput.value = ''; + if (elements.passwordInput) elements.passwordInput.value = ''; + + // Show timeout notification + showToast(message.message, 'info'); + } + }); + + backgroundPort.onDisconnect.addListener(() => { + backgroundPort = null; + }); } // Initialize @@ -240,78 +320,78 @@ document.addEventListener('DOMContentLoaded', async function() { // Initialize theme first initializeTheme(); + // Load timeout setting + await loadTimeoutSetting(); + // Ensure lock button starts hidden const lockBtn = document.getElementById('lockBtn'); if (lockBtn) { lockBtn.classList.add('hidden'); } - setStatus('Initializing...', false); - // Connect to background script for keep-alive connectToBackground(); - // Event listeners (with null checks) - if (elements.createKeyspaceBtn) { - elements.createKeyspaceBtn.addEventListener('click', createKeyspace); - } - if (elements.loginBtn) { - elements.loginBtn.addEventListener('click', login); - } - if (elements.lockBtn) { - elements.lockBtn.addEventListener('click', lockSession); - } - if (elements.themeToggle) { - elements.themeToggle.addEventListener('click', toggleTheme); - } + // Consolidated event listeners + const eventMap = { + createKeyspaceBtn: createKeyspace, + loginBtn: login, + lockBtn: lockSession, + themeToggle: toggleTheme, + settingsToggle: toggleSettingsDropdown, + toggleAddKeypairBtn: toggleAddKeypairForm, + addKeypairBtn: addKeypair, + cancelAddKeypairBtn: hideAddKeypairForm, + signBtn: signMessage, + encryptBtn: encryptMessage, + decryptBtn: decryptMessage, + verifyBtn: verifySignature + }; - if (elements.toggleAddKeypairBtn) { - elements.toggleAddKeypairBtn.addEventListener('click', toggleAddKeypairForm); - } - if (elements.addKeypairBtn) { - elements.addKeypairBtn.addEventListener('click', addKeypair); - } - if (elements.cancelAddKeypairBtn) { - elements.cancelAddKeypairBtn.addEventListener('click', hideAddKeypairForm); - } - - // Crypto operation buttons (with null checks) - if (elements.signBtn) { - elements.signBtn.addEventListener('click', signMessage); - } - if (elements.encryptBtn) { - elements.encryptBtn.addEventListener('click', encryptMessage); - } - if (elements.decryptBtn) { - elements.decryptBtn.addEventListener('click', decryptMessage); - } - if (elements.verifyBtn) { - elements.verifyBtn.addEventListener('click', verifySignature); - } + Object.entries(eventMap).forEach(([elementKey, handler]) => { + elements[elementKey]?.addEventListener('click', handler); + }); // Tab functionality initializeTabs(); - // Copy button event listeners (with null checks) - if (elements.copySignatureBtn) { - elements.copySignatureBtn.addEventListener('click', () => { - const signature = document.getElementById('signatureValue'); - if (signature) { - copyToClipboard(signature.textContent); - } - }); - } + // Additional event listeners + elements.copySignatureBtn?.addEventListener('click', () => { + copyToClipboard(document.getElementById('signatureValue')?.textContent); + }); - // Enable sign button when message is entered (with null checks) - if (elements.messageInput && elements.signBtn) { - elements.messageInput.addEventListener('input', () => { + elements.messageInput?.addEventListener('input', () => { + if (elements.signBtn) { elements.signBtn.disabled = !elements.messageInput.value.trim() || !selectedKeypairId; - }); - } + } + }); - // Basic keyboard shortcuts + // Timeout setting event listener + elements.timeoutInput?.addEventListener('change', async (e) => { + const timeout = parseInt(e.target.value); + if (timeout >= 3 && timeout <= 300) { + await saveTimeoutSetting(timeout); + } else { + e.target.value = sessionTimeoutDuration; // Reset to current value if invalid + } + }); + + // Activity detection - reset timeout on any interaction + document.addEventListener('click', (e) => { + resetSessionTimeout(); + + // Close settings dropdown if clicking outside + if (!elements.settingsToggle?.contains(e.target) && + !elements.settingsDropdown?.contains(e.target)) { + closeSettingsDropdown(); + } + }); + document.addEventListener('keydown', resetSessionTimeout); + document.addEventListener('input', resetSessionTimeout); + + // Keyboard shortcuts document.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && elements.addKeypairCard && !elements.addKeypairCard.classList.contains('hidden')) { + if (e.key === 'Escape' && !elements.addKeypairCard?.classList.contains('hidden')) { hideAddKeypairForm(); } if (e.key === 'Enter' && e.target === elements.keyNameInput && elements.keyNameInput.value.trim()) { @@ -331,16 +411,16 @@ async function checkExistingSession() { // Session is active currentKeyspace = response.session.keyspace; elements.keyspaceInput.value = currentKeyspace; - setStatus(`Connected to ${currentKeyspace}`, true); + setStatus(currentKeyspace, true); showSection('vaultSection'); await loadKeypairs(); } else { // No active session - setStatus('Ready', false); + setStatus('', false); showSection('authSection'); } } catch (error) { - setStatus('Ready', false); + setStatus('', false); showSection('authSection'); } } @@ -370,34 +450,43 @@ function hideAddKeypairForm() { // Tab functionality function initializeTabs() { - const tabButtons = document.querySelectorAll('.tab-btn'); - const tabContents = document.querySelectorAll('.tab-content'); + const tabContainer = document.querySelector('.operation-tabs'); - tabButtons.forEach(button => { - button.addEventListener('click', () => { - const targetTab = button.getAttribute('data-tab'); - - // Remove active class from all tabs and contents - tabButtons.forEach(btn => btn.classList.remove('active')); - tabContents.forEach(content => content.classList.remove('active')); - - // Add active class to clicked tab and corresponding content - button.classList.add('active'); - document.getElementById(`${targetTab}-tab`).classList.add('active'); - - // Clear results when switching tabs - clearTabResults(); - - // Update button states - updateButtonStates(); + if (tabContainer) { + // Use event delegation for better performance + tabContainer.addEventListener('click', (e) => { + if (e.target.classList.contains('tab-btn')) { + handleTabSwitch(e.target); + } }); - }); + } // Initialize input validation initializeInputValidation(); } +function handleTabSwitch(clickedTab) { + const targetTab = clickedTab.getAttribute('data-tab'); + const tabButtons = document.querySelectorAll('.tab-btn'); + const tabContents = document.querySelectorAll('.tab-content'); + // Remove active class from all tabs and contents + tabButtons.forEach(btn => btn.classList.remove('active')); + tabContents.forEach(content => content.classList.remove('active')); + + // Add active class to clicked tab and corresponding content + clickedTab.classList.add('active'); + const targetContent = document.getElementById(`${targetTab}-tab`); + if (targetContent) { + targetContent.classList.add('active'); + } + + // Clear results when switching tabs + clearTabResults(); + + // Update button states + updateButtonStates(); +} function clearTabResults() { // Hide all result sections (with null checks) @@ -488,8 +577,6 @@ function clearVaultState() { selectedKeypairId = null; updateButtonStates(); - - // Hide add keypair form if open hideAddKeypairForm(); @@ -499,24 +586,31 @@ function clearVaultState() { } } -async function createKeyspace() { +// Validation utilities +const validateAuth = () => { const keyspace = elements.keyspaceInput.value.trim(); const password = elements.passwordInput.value.trim(); - if (!keyspace || !password) { - showToast('Please enter keyspace name and password', 'error'); - return; + if (!validateInput(keyspace, 'Keyspace name', { minLength: 1, maxLength: 100 })) { + return null; } + if (!validateInput(password, 'Password', { minLength: 1, maxLength: 1000 })) { + return null; + } + return { keyspace, password }; +}; + +async function createKeyspace() { + const auth = validateAuth(); + if (!auth) return; try { await executeOperation( async () => { - const response = await sendMessage('createKeyspace', { keyspace, password }); - - if (response && response.success) { - // Clear any existing state before auto-login + const response = await sendMessage('createKeyspace', auth); + if (response?.success) { clearVaultState(); await login(); // Auto-login after creation return response; @@ -531,32 +625,25 @@ async function createKeyspace() { } ); } catch (error) { - console.error('Create keyspace error:', error); + handleError(error, 'Create keyspace'); } } async function login() { - const keyspace = elements.keyspaceInput.value.trim(); - const password = elements.passwordInput.value.trim(); - - if (!keyspace || !password) { - showToast('Please enter keyspace name and password', 'error'); - return; - } + const auth = validateAuth(); + if (!auth) return; try { await executeOperation( async () => { - const response = await sendMessage('initSession', { keyspace, password }); - - if (response && response.success) { - currentKeyspace = keyspace; - setStatus(`Connected to ${keyspace}`, true); + const response = await sendMessage('initSession', auth); + if (response?.success) { + currentKeyspace = auth.keyspace; + setStatus(auth.keyspace, true); showSection('vaultSection'); - - // Clear any previous vault state before loading new keyspace clearVaultState(); await loadKeypairs(); + return response; } else { throw new Error(getResponseError(response, 'login')); @@ -569,7 +656,7 @@ async function login() { } ); } catch (error) { - console.error('Login error:', error); + handleError(error, 'Create keyspace'); } } @@ -578,7 +665,7 @@ async function lockSession() { await sendMessage('lockSession'); currentKeyspace = null; selectedKeypairId = null; - setStatus('Locked', false); + setStatus('', false); showSection('authSection'); // Clear all form inputs @@ -601,47 +688,35 @@ async function addKeypair() { return; } - try { - await executeOperation( - async () => { - const metadata = JSON.stringify({ name: keyName }); - const response = await sendMessage('addKeypair', { keyType, metadata }); + await executeOperation( + async () => { + const metadata = JSON.stringify({ name: keyName }); + const response = await sendMessage('addKeypair', { keyType, metadata }); - if (response?.success) { - hideAddKeypairForm(); - await loadKeypairs(); - return response; - } else { - throw new Error(getResponseError(response, 'add keypair')); - } - }, - { - loadingElement: elements.addKeypairBtn, - successMessage: 'Keypair added successfully!' + if (response?.success) { + hideAddKeypairForm(); + await loadKeypairs(); + return response; + } else { + throw new Error(getResponseError(response, 'add keypair')); } - ); - } catch (error) { - // Error already handled by executeOperation - } + }, + { + loadingElement: elements.addKeypairBtn, + successMessage: 'Keypair added successfully!' + } + ); } async function loadKeypairs() { - try { - const response = await sendMessage('listKeypairs'); + const response = await sendMessage('listKeypairs'); - if (response && response.success) { - renderKeypairs(response.keypairs); - } else { - const errorMsg = getResponseError(response, 'load keypairs'); - const container = elements.keypairsList; - container.innerHTML = '
Failed to load keypairs. Try refreshing.
'; - showToast(errorMsg, 'error'); - } - } catch (error) { - const errorMsg = getErrorMessage(error, 'Failed to load keypairs'); - console.error('Error loading keypairs:', error); + if (response && response.success) { + renderKeypairs(response.keypairs); + } else { + const errorMsg = getResponseError(response, 'load keypairs'); const container = elements.keypairsList; - container.innerHTML = '
Error loading keypairs. Try refreshing.
'; + container.innerHTML = '
Failed to load keypairs. Try refreshing.
'; showToast(errorMsg, 'error'); } } @@ -724,7 +799,6 @@ async function selectKeypair(keyId) { } } catch (error) { const errorMsg = getErrorMessage(error, 'Failed to select keypair'); - console.error('Error selecting keypair:', error); // Revert visual state if there was an error updateKeypairSelection(null); showToast(errorMsg, 'error'); @@ -747,183 +821,114 @@ function updateKeypairSelection(selectedId) { } } -async function signMessage() { - const messageText = elements.messageInput.value.trim(); - if (!messageText || !selectedKeypairId) { - showToast('Please enter a message and select a keypair', 'error'); +// Shared templates +const copyIcon = ` + + +`; + +const createResultContainer = (label, value, btnId) => ` + +
+ ${value} + +
+`; + +// Unified crypto operation handler +async function performCryptoOperation(config) { + const { input, validation, action, resultElement, button, successMsg, resultProcessor } = config; + + if (!validation()) { + showToast(config.errorMsg, 'error'); return; } - try { - await executeOperation( - async () => { - const messageBytes = stringToUint8Array(messageText); - const response = await sendMessage('sign', { message: messageBytes }); + await executeOperation( + async () => { + const response = await sendMessage(action, input()); + if (response?.success) { + resultElement.classList.remove('hidden'); + resultElement.innerHTML = resultProcessor(response); - if (response?.success) { - elements.signatureResult.classList.remove('hidden'); - elements.signatureResult.innerHTML = ` - -
- ${response.signature} - -
- `; - - document.getElementById('copySignatureBtn').addEventListener('click', () => { - copyToClipboard(response.signature); - }); - - return response; - } else { - throw new Error(getResponseError(response, 'sign message')); + // Add copy button listener if result has copy button + const copyBtn = resultElement.querySelector('.btn-copy'); + if (copyBtn && config.copyValue) { + copyBtn.addEventListener('click', () => copyToClipboard(config.copyValue(response))); } - }, - { - loadingElement: elements.signBtn, - successMessage: 'Message signed successfully!' + + return response; + } else { + throw new Error(getResponseError(response, action)); } - ); - } catch (error) { - elements.signatureResult.classList.add('hidden'); - } + }, + { loadingElement: button, successMessage: successMsg } + ); } -async function encryptMessage() { - const messageText = elements.encryptMessageInput.value.trim(); - if (!messageText || !currentKeyspace) { - showToast('Please enter a message and ensure you are connected to a keyspace', 'error'); - return; - } +// Crypto operation functions using shared templates +const signMessage = () => performCryptoOperation({ + validation: () => elements.messageInput.value.trim() && selectedKeypairId, + errorMsg: 'Please enter a message and select a keypair', + action: 'sign', + input: () => ({ message: stringToUint8Array(elements.messageInput.value.trim()) }), + resultElement: elements.signatureResult, + button: elements.signBtn, + successMsg: 'Message signed successfully!', + copyValue: (response) => response.signature, + resultProcessor: (response) => createResultContainer('Signature', response.signature, 'copySignatureBtn') +}); - try { - await executeOperation( - async () => { - const response = await sendMessage('encrypt', { message: messageText }); +const encryptMessage = () => performCryptoOperation({ + validation: () => elements.encryptMessageInput.value.trim() && currentKeyspace, + errorMsg: 'Please enter a message and ensure you are connected to a keyspace', + action: 'encrypt', + input: () => ({ message: elements.encryptMessageInput.value.trim() }), + resultElement: elements.encryptResult, + button: elements.encryptBtn, + successMsg: 'Message encrypted successfully!', + copyValue: (response) => response.encryptedMessage, + resultProcessor: (response) => createResultContainer('Encrypted Message', response.encryptedMessage, 'copyEncryptedBtn') +}); - if (response?.success) { - elements.encryptResult.classList.remove('hidden'); - elements.encryptResult.innerHTML = ` - -
- ${response.encryptedMessage} - -
- `; +const decryptMessage = () => performCryptoOperation({ + validation: () => elements.encryptedMessageInput.value.trim() && currentKeyspace, + errorMsg: 'Please enter encrypted message and ensure you are connected to a keyspace', + action: 'decrypt', + input: () => ({ encryptedMessage: elements.encryptedMessageInput.value.trim() }), + resultElement: elements.decryptResult, + button: elements.decryptBtn, + successMsg: 'Message decrypted successfully!', + copyValue: (response) => response.decryptedMessage, + resultProcessor: (response) => createResultContainer('Decrypted Message', response.decryptedMessage, 'copyDecryptedBtn') +}); - document.getElementById('copyEncryptedBtn').addEventListener('click', () => { - copyToClipboard(response.encryptedMessage); - }); - - return response; - } else { - throw new Error(getResponseError(response, 'encrypt message')); - } - }, - { - loadingElement: elements.encryptBtn, - successMessage: 'Message encrypted successfully!' - } - ); - } catch (error) { - elements.encryptResult.classList.add('hidden'); - } -} - -async function decryptMessage() { - const encryptedText = elements.encryptedMessageInput.value.trim(); - if (!encryptedText || !currentKeyspace) { - showToast('Please enter encrypted message and ensure you are connected to a keyspace', 'error'); - return; - } - - try { - await executeOperation( - async () => { - const response = await sendMessage('decrypt', { encryptedMessage: encryptedText }); - - if (response?.success) { - elements.decryptResult.classList.remove('hidden'); - elements.decryptResult.innerHTML = ` - -
- ${response.decryptedMessage} - -
- `; - - document.getElementById('copyDecryptedBtn').addEventListener('click', () => { - copyToClipboard(response.decryptedMessage); - }); - - return response; - } else { - throw new Error(getResponseError(response, 'decrypt message')); - } - }, - { - loadingElement: elements.decryptBtn, - successMessage: 'Message decrypted successfully!' - } - ); - } catch (error) { - elements.decryptResult.classList.add('hidden'); - } -} - -async function verifySignature() { - const messageText = elements.verifyMessageInput.value.trim(); - const signature = elements.signatureToVerifyInput.value.trim(); - if (!messageText || !signature || !selectedKeypairId) { - showToast('Please enter message, signature, and select a keypair', 'error'); - return; - } - - try { - await executeOperation( - async () => { - const messageBytes = stringToUint8Array(messageText); - const response = await sendMessage('verify', { message: messageBytes, signature }); - - if (response?.success) { - const isValid = response.isValid; - const icon = isValid ? '✅' : '❌'; - const text = isValid ? 'Signature is valid' : 'Signature is invalid'; - - elements.verifyResult.classList.remove('hidden'); - elements.verifyResult.innerHTML = ` -
- ${icon} +const verifySignature = () => performCryptoOperation({ + validation: () => elements.verifyMessageInput.value.trim() && elements.signatureToVerifyInput.value.trim() && selectedKeypairId, + errorMsg: 'Please enter message, signature, and select a keypair', + action: 'verify', + input: () => ({ + message: stringToUint8Array(elements.verifyMessageInput.value.trim()), + signature: elements.signatureToVerifyInput.value.trim() + }), + resultElement: elements.verifyResult, + button: elements.verifyBtn, + successMsg: null, + resultProcessor: (response) => { + const isValid = response.isValid; + const icon = isValid + ? ` + + ` + : ` + + + + `; + const text = isValid ? 'Signature is valid' : 'Signature is invalid'; + return `
+ ${icon} ${text} -
- `; - - return response; - } else { - throw new Error(getResponseError(response, 'verify signature')); - } - }, - { - loadingElement: elements.verifyBtn, - successMessage: null // No success message for verification - } - ); - } catch (error) { - elements.verifyResult.classList.add('hidden'); +
`; } -} \ No newline at end of file +}); \ No newline at end of file diff --git a/crypto_vault_extension/styles/popup.css b/crypto_vault_extension/styles/popup.css index 64b4057..5581b1d 100644 --- a/crypto_vault_extension/styles/popup.css +++ b/crypto_vault_extension/styles/popup.css @@ -34,7 +34,7 @@ --accent-success: hsl(var(--accent-hue), 65%, 45%); --accent-error: hsl(0, 70%, 55%); --accent-warning: hsl(35, 85%, 55%); - --accent-info: hsl(var(--secondary-hue), 70%, 55%); + --accent-info: hsl(var(--primary-hue), 35%, 60%); /* Spacing system */ --spacing-xs: 4px; @@ -75,66 +75,63 @@ --accent-success: hsl(var(--accent-hue), 60%, 55%); --accent-error: hsl(0, 65%, 60%); --accent-warning: hsl(35, 80%, 60%); - --accent-info: hsl(var(--secondary-hue), 65%, 60%); + --accent-info: hsl(var(--primary-hue), 30%, 70%); } -/* Harmonious button styling system */ -.btn-primary { - background: var(--bg-button-primary); - color: var(--text-button-primary); +/* Consolidated button styling system */ +.btn-primary, .btn-secondary, .btn-ghost { border: none; - font-weight: 600; - box-shadow: var(--shadow-button); + font-weight: 500; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } -.btn-primary:hover { - background: hsl(var(--primary-hue), var(--primary-saturation), 50%); - box-shadow: var(--shadow-button-hover); - transform: translateY(-1px); -} - -.btn-primary:active { - transform: translateY(0); +.btn-primary { + background: var(--bg-button-primary); + color: var(--text-button-primary); + font-weight: 600; box-shadow: var(--shadow-button); } -/* Dark theme primary button adjustments */ -[data-theme="dark"] .btn-primary:hover { - background: hsl(var(--primary-hue), var(--primary-saturation), 65%); -} - .btn-secondary { background: var(--bg-button-secondary); color: var(--text-button-secondary); border: 1px solid var(--border-color); - font-weight: 500; - transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); -} - -.btn-secondary:hover { - background: var(--bg-input); - border-color: var(--border-focus); - color: var(--text-primary); - transform: translateY(-1px); -} - -.btn-secondary:active { - transform: translateY(0); } .btn-ghost { background: var(--bg-button-ghost); color: var(--border-focus); border: 1px solid transparent; - font-weight: 500; - transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +.btn-primary:hover, .btn-secondary:hover, .btn-ghost:hover { + transform: translateY(-1px); +} + +.btn-primary:active, .btn-secondary:active, .btn-ghost:active { + transform: translateY(0); +} + +.btn-primary:hover { + background: hsl(var(--primary-hue), var(--primary-saturation), 50%); + box-shadow: var(--shadow-button-hover); +} + +.btn-secondary:hover, .btn-ghost:hover { + background: var(--bg-input); + color: var(--text-primary); +} + +.btn-secondary:hover { + border-color: var(--border-focus); } .btn-ghost:hover { - background: var(--bg-input); border-color: var(--border-color); - color: var(--text-primary); +} + +[data-theme="dark"] .btn-primary:hover { + background: hsl(var(--primary-hue), var(--primary-saturation), 65%); } * { @@ -197,6 +194,73 @@ body { gap: var(--spacing-md); } +.settings-container { + position: relative; +} + +.settings-dropdown { + position: absolute; + top: 100%; + right: 0; + margin-top: var(--spacing-sm); + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: var(--spacing-lg); + box-shadow: var(--shadow-card); + backdrop-filter: blur(20px); + min-width: 200px; + z-index: 1000; +} + +.settings-dropdown.hidden { + display: none; +} + +.settings-item { + margin-bottom: var(--spacing-md); +} + +.settings-item:last-child { + margin-bottom: 0; +} + +.settings-item label { + display: block; + font-size: 14px; + font-weight: 500; + color: var(--text-secondary); + margin-bottom: var(--spacing-xs); +} + +.timeout-input-group { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.timeout-input-group input { + width: 60px; + padding: var(--spacing-xs) var(--spacing-sm); + font-size: 14px; + border: 1px solid var(--border-color); + border-radius: 8px; + background: var(--bg-input); + color: var(--text-primary); + text-align: center; +} + +.timeout-input-group input:focus { + outline: none; + border-color: var(--border-focus); + box-shadow: 0 0 0 2px hsla(var(--primary-hue), var(--primary-saturation), 55%, 0.15); +} + +.timeout-input-group span { + font-size: 14px; + color: var(--text-muted); +} + .btn-icon-only { background: var(--bg-button-ghost); border: none; @@ -224,39 +288,15 @@ body { gap: var(--spacing-md); } -.status-indicator .status-content { - display: flex; - align-items: center; - gap: var(--spacing-sm); -} -.status-dot { - width: 10px; - height: 10px; - border-radius: 50%; - background: var(--accent-warning); - box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.2); -} - -.status-indicator.connected .status-dot { - background: var(--accent-success); - box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2); -} #statusText { - font-size: 14px; - font-weight: 500; + font-size: 22px; + font-weight: 700; color: var(--text-primary); } -/* Vault status specific styling */ -.vault-status .status-indicator { - color: var(--text-primary); -} -.vault-status #statusText { - color: var(--text-primary); -} /* Enhanced lock button styling */ #lockBtn { @@ -515,7 +555,7 @@ input::placeholder, textarea::placeholder { .vault-header h2 { color: var(--text-primary); - font-size: 20px; + font-size: 18px; font-weight: 600; } @@ -749,31 +789,6 @@ input::placeholder, textarea::placeholder { color: var(--text-primary); } -/* Signature Result */ -.signature-result { - margin-top: 16px; - padding: 16px; - background: hsla(var(--accent-hue), 60%, 95%, 0.8); - border-radius: 12px; - border: 1px solid hsla(var(--accent-hue), 50%, 70%, 0.3); -} - -[data-theme="dark"] .signature-result { - background: hsla(var(--accent-hue), 40%, 15%, 0.6); - border-color: hsla(var(--accent-hue), 50%, 40%, 0.4); -} - - - -.signature-result label { - color: var(--accent-success); - font-weight: 500; - margin-bottom: 8px; - display: block; -} - - - /* Enhanced Toast Notifications */ .toast-notification { position: fixed; @@ -828,31 +843,7 @@ input::placeholder, textarea::placeholder { word-wrap: break-word; } -.toast-close { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 18px; - font-weight: bold; - line-height: 1; - opacity: 0.7; - padding: 0; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - transition: all 0.2s ease; - flex-shrink: 0; - margin-top: 2px; -} -.toast-close:hover { - opacity: 1; - background: rgba(255, 255, 255, 0.1); -} /* Success Toast */ .toast-success { @@ -880,9 +871,9 @@ input::placeholder, textarea::placeholder { /* Info Toast */ .toast-info { - background: linear-gradient(135deg, rgba(59, 130, 246, 0.95) 0%, rgba(37, 99, 235, 0.95) 100%); + background: var(--accent-info); color: white; - border-color: rgba(59, 130, 246, 0.3); + border-color: hsla(var(--primary-hue), 35%, 60%, 0.3); } .toast-info .toast-icon { @@ -981,10 +972,7 @@ input::placeholder, textarea::placeholder { color: var(--text-primary); } -[data-theme="dark"] .tab-btn:hover { - color: var(--border-focus); -} - +[data-theme="dark"] .tab-btn:hover, [data-theme="dark"] .tab-btn.active { color: var(--border-focus); } @@ -997,8 +985,8 @@ input::placeholder, textarea::placeholder { display: block; } -/* Result Styling */ -.encrypt-result, .decrypt-result, .verify-result { +/* Consolidated result styling */ +.encrypt-result, .decrypt-result, .verify-result, .signature-result { margin-top: 16px; padding: 16px; border-radius: 12px; @@ -1006,55 +994,55 @@ input::placeholder, textarea::placeholder { transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } -.encrypt-result { +.encrypt-result, .signature-result { background: hsla(var(--accent-hue), 60%, 95%, 0.8); border-color: hsla(var(--accent-hue), 50%, 70%, 0.3); box-shadow: 0 2px 12px hsla(var(--accent-hue), 50%, 50%, 0.1); } -[data-theme="dark"] .encrypt-result { - background: hsla(var(--accent-hue), 40%, 15%, 0.6); - border-color: hsla(var(--accent-hue), 50%, 40%, 0.4); -} - -.encrypt-result label { - color: var(--accent-success); - font-weight: 600; - margin-bottom: 12px; - display: block; - font-size: 14px; -} - .decrypt-result { background: hsla(var(--secondary-hue), 60%, 95%, 0.8); border-color: hsla(var(--secondary-hue), 50%, 70%, 0.3); box-shadow: 0 2px 12px hsla(var(--secondary-hue), 50%, 50%, 0.1); } -[data-theme="dark"] .decrypt-result { - background: hsla(var(--secondary-hue), 40%, 15%, 0.6); - border-color: hsla(var(--secondary-hue), 50%, 40%, 0.4); -} - -.decrypt-result label { - color: var(--accent-info); - font-weight: 600; - margin-bottom: 12px; - display: block; - font-size: 14px; -} - .verify-result { background: hsla(var(--primary-hue), 30%, 95%, 0.8); border-color: hsla(var(--primary-hue), 40%, 70%, 0.3); box-shadow: 0 2px 12px hsla(var(--primary-hue), 40%, 50%, 0.1); } +[data-theme="dark"] .encrypt-result, +[data-theme="dark"] .signature-result { + background: hsla(var(--accent-hue), 40%, 15%, 0.6); + border-color: hsla(var(--accent-hue), 50%, 40%, 0.4); +} + +[data-theme="dark"] .decrypt-result { + background: hsla(var(--secondary-hue), 40%, 15%, 0.6); + border-color: hsla(var(--secondary-hue), 50%, 40%, 0.4); +} + [data-theme="dark"] .verify-result { background: hsla(var(--primary-hue), 20%, 15%, 0.6); border-color: hsla(var(--primary-hue), 30%, 40%, 0.4); } +.encrypt-result label, .decrypt-result label, .signature-result label { + font-weight: 600; + margin-bottom: 12px; + display: block; + font-size: 14px; +} + +.encrypt-result label, .signature-result label { + color: var(--accent-success); +} + +.decrypt-result label { + color: var(--accent-info); +} + .verification-status { display: flex; align-items: center; @@ -1071,19 +1059,14 @@ input::placeholder, textarea::placeholder { color: var(--accent-error); } +.verification-icon { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} - - - - - - - - - - - - - - - +.verification-icon svg { + width: 20px; + height: 20px; +} \ No newline at end of file