281 lines
7.8 KiB
JavaScript
281 lines
7.8 KiB
JavaScript
// Enhanced Error Handling System for CryptoVault Extension
|
|
|
|
class CryptoVaultError extends Error {
|
|
constructor(message, code, retryable = false, userMessage = null) {
|
|
super(message);
|
|
this.name = 'CryptoVaultError';
|
|
this.code = code;
|
|
this.retryable = retryable;
|
|
this.userMessage = userMessage || message;
|
|
this.timestamp = Date.now();
|
|
}
|
|
}
|
|
|
|
// 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.',
|
|
[ERROR_CODES.SERVICE_UNAVAILABLE]: 'Service is temporarily unavailable. Please try again later.',
|
|
|
|
[ERROR_CODES.INVALID_PASSWORD]: 'Invalid password. Please check your password and try again.',
|
|
[ERROR_CODES.SESSION_EXPIRED]: 'Your session has expired. Please log in again.',
|
|
[ERROR_CODES.UNAUTHORIZED]: 'You are not authorized to perform this action.',
|
|
|
|
[ERROR_CODES.CRYPTO_ERROR]: 'Cryptographic operation failed. Please try again.',
|
|
[ERROR_CODES.INVALID_SIGNATURE]: 'Invalid signature. Please verify your input.',
|
|
[ERROR_CODES.ENCRYPTION_FAILED]: 'Encryption failed. Please try again.',
|
|
|
|
[ERROR_CODES.INVALID_INPUT]: 'Invalid input. Please check your data and try again.',
|
|
[ERROR_CODES.MISSING_KEYPAIR]: 'No keypair selected. Please select a keypair first.',
|
|
[ERROR_CODES.INVALID_FORMAT]: 'Invalid data format. Please check your input.',
|
|
|
|
[ERROR_CODES.WASM_ERROR]: 'System error occurred. Please refresh and try again.',
|
|
[ERROR_CODES.STORAGE_ERROR]: 'Storage error occurred. Please try again.',
|
|
[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,
|
|
ERROR_CODES.SERVICE_UNAVAILABLE,
|
|
ERROR_CODES.WASM_ERROR,
|
|
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,
|
|
ERROR_CODES.NETWORK_ERROR,
|
|
true,
|
|
ERROR_MESSAGES[ERROR_CODES.NETWORK_ERROR]
|
|
);
|
|
}
|
|
|
|
// Authentication errors
|
|
if (errorMessage.includes('password') || errorMessage.includes('Invalid password')) {
|
|
return new CryptoVaultError(
|
|
errorMessage,
|
|
ERROR_CODES.INVALID_PASSWORD,
|
|
false,
|
|
ERROR_MESSAGES[ERROR_CODES.INVALID_PASSWORD]
|
|
);
|
|
}
|
|
|
|
if (errorMessage.includes('session') || errorMessage.includes('not unlocked') || errorMessage.includes('expired')) {
|
|
return new CryptoVaultError(
|
|
errorMessage,
|
|
ERROR_CODES.SESSION_EXPIRED,
|
|
false,
|
|
ERROR_MESSAGES[ERROR_CODES.SESSION_EXPIRED]
|
|
);
|
|
}
|
|
|
|
// Crypto errors
|
|
if (errorMessage.includes('decryption error') || errorMessage.includes('aead::Error')) {
|
|
return new CryptoVaultError(
|
|
errorMessage,
|
|
ERROR_CODES.CRYPTO_ERROR,
|
|
false,
|
|
'Invalid password or corrupted data. Please check your password.'
|
|
);
|
|
}
|
|
|
|
if (errorMessage.includes('Crypto error') || errorMessage.includes('encryption')) {
|
|
return new CryptoVaultError(
|
|
errorMessage,
|
|
ERROR_CODES.CRYPTO_ERROR,
|
|
false,
|
|
ERROR_MESSAGES[ERROR_CODES.CRYPTO_ERROR]
|
|
);
|
|
}
|
|
|
|
// Validation errors
|
|
if (errorMessage.includes('No keypair selected')) {
|
|
return new CryptoVaultError(
|
|
errorMessage,
|
|
ERROR_CODES.MISSING_KEYPAIR,
|
|
false,
|
|
ERROR_MESSAGES[ERROR_CODES.MISSING_KEYPAIR]
|
|
);
|
|
}
|
|
|
|
// WASM errors
|
|
if (errorMessage.includes('wasm') || errorMessage.includes('WASM')) {
|
|
return new CryptoVaultError(
|
|
errorMessage,
|
|
ERROR_CODES.WASM_ERROR,
|
|
true,
|
|
ERROR_MESSAGES[ERROR_CODES.WASM_ERROR]
|
|
);
|
|
}
|
|
|
|
// Default to unknown error
|
|
return new CryptoVaultError(
|
|
errorMessage,
|
|
ERROR_CODES.UNKNOWN_ERROR,
|
|
false,
|
|
ERROR_MESSAGES[ERROR_CODES.UNKNOWN_ERROR]
|
|
);
|
|
}
|
|
|
|
// Get error message from various error types
|
|
function getErrorMessage(error) {
|
|
if (!error) return 'Unknown error';
|
|
|
|
if (typeof error === 'string') {
|
|
return error.trim();
|
|
}
|
|
|
|
if (error instanceof Error) {
|
|
return error.message;
|
|
}
|
|
|
|
if (error.error) {
|
|
return getErrorMessage(error.error);
|
|
}
|
|
|
|
if (error.message) {
|
|
return error.message;
|
|
}
|
|
|
|
if (typeof error === 'object') {
|
|
try {
|
|
const stringified = JSON.stringify(error);
|
|
if (stringified && stringified !== '{}') {
|
|
return stringified;
|
|
}
|
|
} catch (e) {
|
|
// Ignore JSON stringify errors
|
|
}
|
|
}
|
|
|
|
return 'Unknown error';
|
|
}
|
|
|
|
// Retry logic with exponential backoff
|
|
async function withRetry(operation, options = {}) {
|
|
const {
|
|
maxRetries = 3,
|
|
baseDelay = 1000,
|
|
maxDelay = 10000,
|
|
backoffFactor = 2,
|
|
onRetry = null
|
|
} = options;
|
|
|
|
let lastError;
|
|
|
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
try {
|
|
return await operation();
|
|
} catch (error) {
|
|
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));
|
|
}
|
|
}
|
|
|
|
throw lastError;
|
|
}
|
|
|
|
// Enhanced operation wrapper with loading states
|
|
async function executeOperation(operation, options = {}) {
|
|
const {
|
|
loadingElement = null,
|
|
successMessage = null,
|
|
showRetryProgress = false,
|
|
onProgress = null
|
|
} = options;
|
|
|
|
// Show loading state
|
|
if (loadingElement) {
|
|
setButtonLoading(loadingElement, true);
|
|
}
|
|
|
|
try {
|
|
const result = await withRetry(operation, {
|
|
...options,
|
|
onRetry: (attempt, delay, error) => {
|
|
if (showRetryProgress && onProgress) {
|
|
onProgress(`Retrying... (${attempt}/${options.maxRetries || 3})`);
|
|
}
|
|
if (options.onRetry) {
|
|
options.onRetry(attempt, delay, error);
|
|
}
|
|
}
|
|
});
|
|
|
|
// 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;
|
|
window.getErrorMessage = getErrorMessage;
|
|
window.withRetry = withRetry;
|
|
window.executeOperation = executeOperation;
|