diff --git a/apps/browser-extension/src/entrypoints/background/VaultMessageHandler.ts b/apps/browser-extension/src/entrypoints/background/VaultMessageHandler.ts index bfe2fdf34..47dedd04c 100644 --- a/apps/browser-extension/src/entrypoints/background/VaultMessageHandler.ts +++ b/apps/browser-extension/src/entrypoints/background/VaultMessageHandler.ts @@ -18,6 +18,21 @@ import { WebApiService } from '@/utils/WebApiService'; import { t } from '@/i18n/StandaloneI18n'; +/** + * Cache for the SqliteClient to avoid repeated decryption and initialization. + * The cached instance is the single source of truth for the in-memory vault. + * + * Cache Strategy: + * - Local mutations (createCredential, etc.): Work directly on cachedSqliteClient, no cache clearing + * - New vault from remote (login, sync): Clear cache by setting both to null + * - Logout/clear vault: Clear cache by setting both to null + * + * The cache is cleared by setting cachedSqliteClient and cachedVaultBlob to null directly + * in the functions that receive new vault data from external sources. + */ +let cachedSqliteClient: SqliteClient | null = null; +let cachedVaultBlob: string | null = null; + /** * Check if the user is logged in and if the vault is locked, and also check for pending migrations. */ @@ -58,8 +73,6 @@ export async function handleCheckAuthStatus() : Promise<{ isLoggedIn: boolean, i hasPendingMigrations }; } catch (error) { - console.error('Error checking pending migrations:', error); - // If it's a version incompatibility error, we need to handle it specially if (error instanceof VaultVersionIncompatibleError) { // Return the error so the UI can handle it appropriately (logout user) @@ -92,6 +105,10 @@ export async function handleStoreVault( // Store new encrypted vault in session storage. await storage.setItem('session:encryptedVault', vaultRequest.vaultBlob); + // Clear cached client since we received a new vault blob from external source + cachedSqliteClient = null; + cachedVaultBlob = null; + /* * For all other values, check if they have a value and store them in session storage if they do. * Some updates, e.g. when mutating local database, these values will not be set. @@ -155,7 +172,7 @@ export async function handleStoreEncryptionKeyDerivationParams( */ export async function handleSyncVault( ) : Promise { - const webApi = new WebApiService(() => {}); + const webApi = new WebApiService(); const statusResponse = await webApi.getStatus(); const statusError = webApi.validateStatusResponse(statusResponse); if (statusError !== null) { @@ -175,6 +192,10 @@ export async function handleSyncVault( { key: 'session:hiddenPrivateEmailDomains', value: vaultResponse.vault.hiddenPrivateEmailDomainList }, { key: 'session:vaultRevisionNumber', value: vaultResponse.vault.currentRevisionNumber } ]); + + // Clear cached client since we received a new vault blob from server + cachedSqliteClient = null; + cachedVaultBlob = null; } return { success: true }; @@ -240,6 +261,10 @@ export function handleClearVault( 'session:vaultRevisionNumber' ]); + // Clear cached client since vault was cleared + cachedSqliteClient = null; + cachedVaultBlob = null; + return { success: true }; } @@ -284,7 +309,6 @@ export async function handleGetFilteredCredentials( const sqliteClient = await createVaultSqliteClient(); const allCredentials = sqliteClient.getAllCredentials(); - // Import filtering logic const { filterCredentials, AutofillMatchingMode } = await import('@/utils/credentialMatcher/CredentialMatcher'); // Parse matching mode from string @@ -500,13 +524,11 @@ export async function handleUploadVault( message: any ) : Promise { try { - // Store the new vault blob in session storage. + // Persist the current updated vault blob in session storage. await storage.setItem('session:encryptedVault', message.vaultBlob); - // Create new sqlite client which will use the new vault blob. - const sqliteClient = await createVaultSqliteClient(); - // Upload the new vault to the server. + const sqliteClient = await createVaultSqliteClient(); const response = await uploadNewVaultToServer(sqliteClient); return { success: true, status: response.status, newRevisionNumber: response.newRevisionNumber }; } catch (error) { @@ -581,10 +603,17 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise {}); + const webApi = new WebApiService(); const response = await webApi.post('Vault', newVault); // Check if response is successful (.status === 0) @@ -620,6 +649,7 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise { const encryptedVault = await storage.getItem('session:encryptedVault') as string; @@ -628,15 +658,24 @@ async function createVaultSqliteClient() : Promise { throw new Error(await t('common.errors.unknownError')); } - // Decrypt the vault. + // Check if we have a valid cached client + if (cachedSqliteClient && cachedVaultBlob === encryptedVault) { + return cachedSqliteClient; + } + + // Decrypt the vault const decryptedVault = await EncryptionUtility.symmetricDecrypt( encryptedVault, encryptionKey ); - // Initialize the SQLite client with the decrypted vault. + // Initialize the SQLite client with the decrypted vault const sqliteClient = new SqliteClient(); await sqliteClient.initializeFromBase64(decryptedVault); + // Cache the client and vault blob + cachedSqliteClient = sqliteClient; + cachedVaultBlob = encryptedVault; + return sqliteClient; }