@@ -36,6 +36,21 @@ export { isPersonalKey, isContactKey } from '@/types';
3636
3737const CURRENT_VAULT_VERSION = 2 ;
3838
39+ /**
40+ * Check available storage space
41+ * Returns approximate bytes available (chrome.storage.local has ~5MB-10MB limit)
42+ */
43+ export async function checkStorageQuota ( ) : Promise < { bytesUsed : number ; quotaBytes : number ; percentUsed : number } > {
44+ const bytesUsed = await chrome . storage . local . getBytesInUse ( ) ;
45+ // Chrome local storage quota is typically 5MB for extensions, 10MB with unlimitedStorage permission
46+ const quotaBytes = 5 * 1024 * 1024 ; // 5MB default
47+ const percentUsed = Math . round ( ( bytesUsed / quotaBytes ) * 100 ) ;
48+
49+ console . log ( `📊 Storage: ${ bytesUsed } bytes used (${ percentUsed } % of ~${ quotaBytes } bytes)` ) ;
50+
51+ return { bytesUsed, quotaBytes, percentUsed } ;
52+ }
53+
3954// ============================================================================
4055// Vault Lifecycle
4156// ============================================================================
@@ -59,7 +74,34 @@ export async function createVault(masterPassword: string): Promise<void> {
5974 data : encrypted ,
6075 } ;
6176
62- await chrome . storage . local . set ( { [ STORAGE_KEYS . VAULT ] : encryptedVault } ) ;
77+ try {
78+ await chrome . storage . local . set ( { [ STORAGE_KEYS . VAULT ] : encryptedVault } ) ;
79+
80+ // Verify the write succeeded
81+ const verifyResult = await chrome . storage . local . get ( STORAGE_KEYS . VAULT ) ;
82+ if ( ! verifyResult [ STORAGE_KEYS . VAULT ] ) {
83+ throw new Error ( 'Vault creation failed: verification failed' ) ;
84+ }
85+
86+ // Verify we can decrypt
87+ const testDecrypt = await decryptVault (
88+ encryptedVault . data ,
89+ encryptedVault . iv ,
90+ encryptedVault . salt ,
91+ masterPassword
92+ ) ;
93+
94+ if ( ! testDecrypt ) {
95+ throw new Error ( 'Vault creation failed: decryption verification failed' ) ;
96+ }
97+
98+ console . log ( '✅ Vault created successfully' ) ;
99+ } catch ( error ) {
100+ console . error ( '❌ Vault creation failed:' , error ) ;
101+ // Clean up any partial write
102+ await chrome . storage . local . remove ( STORAGE_KEYS . VAULT ) ;
103+ throw error ;
104+ }
63105}
64106
65107/**
@@ -117,22 +159,97 @@ async function migrateVaultV1ToV2(_oldVault: VaultData): Promise<VaultData> {
117159
118160/**
119161 * Save vault data (re-encrypt with password)
162+ *
163+ * SAFETY: Uses write-ahead pattern with verification:
164+ * 1. Backup current vault before overwriting
165+ * 2. Write new vault
166+ * 3. Verify the write succeeded by re-reading
167+ * 4. If verification fails, attempt to restore backup
120168 */
121169export async function saveVault (
122170 vaultData : VaultData ,
123171 masterPassword : string
124172) : Promise < void > {
125- const vaultJson = JSON . stringify ( vaultData ) ;
126- const { salt, iv, encrypted } = await encryptVault ( vaultJson , masterPassword ) ;
173+ const BACKUP_KEY = 'vault_backup' ;
127174
128- const encryptedVault : EncryptedVault = {
129- version : CURRENT_VAULT_VERSION ,
130- salt,
131- iv,
132- data : encrypted ,
133- } ;
134-
135- await chrome . storage . local . set ( { [ STORAGE_KEYS . VAULT ] : encryptedVault } ) ;
175+ try {
176+ // Step 1: Backup current vault before overwriting
177+ const currentResult = await chrome . storage . local . get ( STORAGE_KEYS . VAULT ) ;
178+ const currentVault = currentResult [ STORAGE_KEYS . VAULT ] ;
179+ if ( currentVault ) {
180+ await chrome . storage . local . set ( { [ BACKUP_KEY ] : currentVault } ) ;
181+ }
182+
183+ // Step 2: Encrypt and save new vault
184+ const vaultJson = JSON . stringify ( vaultData ) ;
185+ const { salt, iv, encrypted } = await encryptVault ( vaultJson , masterPassword ) ;
186+
187+ const encryptedVault : EncryptedVault = {
188+ version : CURRENT_VAULT_VERSION ,
189+ salt,
190+ iv,
191+ data : encrypted ,
192+ } ;
193+
194+ await chrome . storage . local . set ( { [ STORAGE_KEYS . VAULT ] : encryptedVault } ) ;
195+
196+ // Step 3: Verify the write succeeded by attempting to decrypt
197+ const verifyResult = await chrome . storage . local . get ( STORAGE_KEYS . VAULT ) ;
198+ const savedVault = verifyResult [ STORAGE_KEYS . VAULT ] as EncryptedVault | undefined ;
199+
200+ if ( ! savedVault ) {
201+ throw new Error ( 'Vault save verification failed: vault not found after save' ) ;
202+ }
203+
204+ // Verify we can decrypt with the password
205+ const testDecrypt = await decryptVault (
206+ savedVault . data ,
207+ savedVault . iv ,
208+ savedVault . salt ,
209+ masterPassword
210+ ) ;
211+
212+ if ( ! testDecrypt ) {
213+ throw new Error ( 'Vault save verification failed: decryption test failed' ) ;
214+ }
215+
216+ // Parse and verify data integrity
217+ const parsedVault = JSON . parse ( testDecrypt ) as VaultData ;
218+ if ( ! parsedVault . keys || ! parsedVault . groups ) {
219+ throw new Error ( 'Vault save verification failed: data structure invalid' ) ;
220+ }
221+
222+ // Verify counts match
223+ if ( parsedVault . keys . length !== vaultData . keys . length ||
224+ parsedVault . groups . length !== vaultData . groups . length ) {
225+ throw new Error ( 'Vault save verification failed: data count mismatch' ) ;
226+ }
227+
228+ // Step 4: Clear backup on success
229+ await chrome . storage . local . remove ( BACKUP_KEY ) ;
230+
231+ console . log ( '✅ Vault saved successfully:' , {
232+ keys : vaultData . keys . length ,
233+ groups : vaultData . groups . length
234+ } ) ;
235+
236+ } catch ( error ) {
237+ console . error ( '❌ Vault save failed:' , error ) ;
238+
239+ // Attempt to restore backup
240+ try {
241+ const backupResult = await chrome . storage . local . get ( BACKUP_KEY ) ;
242+ const backup = backupResult [ BACKUP_KEY ] ;
243+ if ( backup ) {
244+ await chrome . storage . local . set ( { [ STORAGE_KEYS . VAULT ] : backup } ) ;
245+ console . log ( '🔄 Restored vault from backup after save failure' ) ;
246+ }
247+ } catch ( restoreError ) {
248+ console . error ( '❌ Failed to restore backup:' , restoreError ) ;
249+ }
250+
251+ throw error ; // Re-throw so caller knows save failed
252+ }
136253}
137254
138255// ============================================================================
0 commit comments