Add email error suppression during active sync or isDirty flags (#1473)

This commit is contained in:
Leendert de Borst
2026-01-23 11:55:08 +01:00
parent 736c691970
commit 8a7cb75bc0
5 changed files with 66 additions and 2 deletions

View File

@@ -156,14 +156,30 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
}
return prevEmails;
});
// Clear any previous error on successful load
setError(null);
}
} catch {
// Try to parse as error response instead
const apiErrorResponse = response as ApiErrorResponse;
// Suppress errors while vault has unsynced changes (e.g., after item creation)
// The server may not know about newly created items/aliases yet
if (dbContext.shouldSuppressEmailErrors()) {
// Don't set error, keep loading state - will retry on next interval
return;
}
setError(t('emails.apiErrors.' + apiErrorResponse?.code));
return;
}
} catch {
// Suppress errors while vault has unsynced changes
if (dbContext.shouldSuppressEmailErrors()) {
return;
}
setError(t('common.errors.unknownError'));
return;
}

View File

@@ -44,6 +44,12 @@ type DbContextType = {
* Set the syncing state.
*/
setIsSyncing: (syncing: boolean) => void;
/**
* Check if email errors should be suppressed.
* Errors are suppressed when vault has local changes not yet synced,
* as the server may not know about newly created items/aliases yet.
*/
shouldSuppressEmailErrors: () => boolean;
/**
* Load a decrypted vault into memory (SQLite client).
*/
@@ -107,6 +113,15 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
*/
const [serverRevision, setServerRevision] = useState(0);
/**
* Check if email errors should be suppressed.
* Errors are suppressed when vault has local changes not yet synced,
* as the server may not know about newly created items/aliases yet.
*/
const shouldSuppressEmailErrors = useCallback(() => {
return isDirty || isSyncing;
}, [isDirty, isSyncing]);
/**
* Set the offline mode state and persist it to local storage.
* Updates both ref (sync) and state (triggers re-render).
@@ -277,6 +292,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
serverRevision,
setIsOffline,
setIsSyncing,
shouldSuppressEmailErrors,
loadDatabase,
loadStoredDatabase,
storeEncryptionKey,
@@ -285,7 +301,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
getVaultMetadata,
refreshSyncState,
hasPendingMigrations,
}), [sqliteClient, dbInitialized, dbAvailable, isOffline, getIsOffline, isDirty, isSyncing, serverRevision, setIsOffline, loadDatabase, loadStoredDatabase, storeEncryptionKey, storeEncryptionKeyDerivationParams, clearDatabase, getVaultMetadata, refreshSyncState, hasPendingMigrations]);
}), [sqliteClient, dbInitialized, dbAvailable, isOffline, getIsOffline, isDirty, isSyncing, serverRevision, setIsOffline, shouldSuppressEmailErrors, loadDatabase, loadStoredDatabase, storeEncryptionKey, storeEncryptionKeyDerivationParams, clearDatabase, getVaultMetadata, refreshSyncState, hasPendingMigrations]);
return (
<DbContext.Provider value={contextValue}>

View File

@@ -204,10 +204,23 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) : React.Rea
} catch {
// Try to parse as error response instead
const apiErrorResponse = response as ApiErrorResponse;
// Suppress errors while vault has unsynced changes (e.g., after item creation)
// The server may not know about newly created items/aliases yet
if (dbContext.shouldSuppressEmailErrors()) {
// Don't set error, keep loading state - will retry on next interval
return;
}
setError(t(`apiErrors.${apiErrorResponse?.code}`));
return;
}
} catch {
// Suppress errors while vault has unsynced changes
if (dbContext.shouldSuppressEmailErrors()) {
return;
}
setError(t('items.emailLoadError'));
}
}

View File

@@ -15,6 +15,12 @@ type DbContextType = {
isOffline: boolean;
setIsSyncing: (syncing: boolean) => void;
setIsOffline: (offline: boolean) => Promise<void>;
/**
* Check if email errors should be suppressed.
* Errors are suppressed when vault has local changes not yet synced,
* as the server may not know about newly created items/aliases yet.
*/
shouldSuppressEmailErrors: () => boolean;
refreshSyncState: () => Promise<void>;
storeEncryptionKey: (derivedKey: string) => Promise<void>;
storeEncryptionKeyDerivationParams: (keyDerivationParams: EncryptionKeyDerivationParams) => Promise<void>;
@@ -63,6 +69,15 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
*/
const [isOffline, setIsOfflineState] = useState(false);
/**
* Check if email errors should be suppressed.
* Errors are suppressed when vault has local changes not yet synced,
* as the server may not know about newly created items/aliases yet.
*/
const shouldSuppressEmailErrors = useCallback(() => {
return isDirty || isSyncing;
}, [isDirty, isSyncing]);
/**
* Unlock the vault in the native module which will decrypt the database using the stored encryption key
* and load it into memory.
@@ -246,6 +261,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
isOffline,
setIsSyncing,
setIsOffline,
shouldSuppressEmailErrors,
refreshSyncState,
hasPendingMigrations,
clearDatabase,
@@ -256,7 +272,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
storeEncryptionKeyDerivationParams,
checkStoredVault,
setDatabaseAvailable,
}), [sqliteClient, dbInitialized, dbAvailable, isDirty, isSyncing, isOffline, setIsSyncing, setIsOffline, refreshSyncState, hasPendingMigrations, clearDatabase, getVaultMetadata, testDatabaseConnection, unlockVault, storeEncryptionKey, storeEncryptionKeyDerivationParams, checkStoredVault, setDatabaseAvailable]);
}), [sqliteClient, dbInitialized, dbAvailable, isDirty, isSyncing, isOffline, setIsSyncing, setIsOffline, shouldSuppressEmailErrors, refreshSyncState, hasPendingMigrations, clearDatabase, getVaultMetadata, testDatabaseConnection, unlockVault, storeEncryptionKey, storeEncryptionKeyDerivationParams, checkStoredVault, setDatabaseAvailable]);
return (
<DbContext.Provider value={contextValue}>

View File

@@ -141,6 +141,9 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
return Unauthorized();
}
// Simulate a delay to test the email error suppression logic.
await Task.Delay(10000);
// Compare the logged-in username with the username in the provided vault model.
// If they do not match reject the request. This is important because it's
// possible that a user has logged in with a different username than the one