Files
thrillwiki_django_no_react/static/js/error-handling.js

207 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Error handling and state management for version control system
*/
class VersionControlError extends Error {
constructor(message, code, details = {}) {
super(message);
this.name = 'VersionControlError';
this.code = code;
this.details = details;
this.timestamp = new Date();
}
}
// Error boundary for version control operations
class VersionControlErrorBoundary {
constructor(options = {}) {
this.onError = options.onError || this.defaultErrorHandler;
this.errors = new Map();
this.retryAttempts = new Map();
this.maxRetries = options.maxRetries || 3;
}
defaultErrorHandler(error) {
console.error(`[Version Control Error]: ${error.message}`, error);
this.showErrorNotification(error);
}
showErrorNotification(error) {
const notification = document.createElement('div');
notification.className = 'version-control-error notification';
notification.innerHTML = `
<div class="notification-content">
<span class="error-icon">⚠️</span>
<span class="error-message">${error.message}</span>
<button class="close-btn">×</button>
</div>
${error.details.retry ? '<button class="retry-btn">Retry</button>' : ''}
`;
document.body.appendChild(notification);
// Auto-hide after 5 seconds unless it's a critical error
if (!error.details.critical) {
setTimeout(() => {
notification.remove();
}, 5000);
}
// Handle retry
const retryBtn = notification.querySelector('.retry-btn');
if (retryBtn && error.details.retryCallback) {
retryBtn.addEventListener('click', () => {
notification.remove();
error.details.retryCallback();
});
}
// Handle close
const closeBtn = notification.querySelector('.close-btn');
closeBtn.addEventListener('click', () => notification.remove());
}
async wrapOperation(operationKey, operation) {
try {
// Check if operation is already in progress
if (this.errors.has(operationKey)) {
throw new VersionControlError(
'Operation already in progress',
'DUPLICATE_OPERATION'
);
}
// Show loading state
this.showLoading(operationKey);
const result = await operation();
// Clear any existing errors for this operation
this.errors.delete(operationKey);
this.retryAttempts.delete(operationKey);
return result;
} catch (error) {
const retryCount = this.retryAttempts.get(operationKey) || 0;
// Handle specific error types
if (error.name === 'VersionControlError') {
this.handleVersionControlError(error, operationKey, retryCount);
} else {
// Convert unknown errors to VersionControlError
const vcError = new VersionControlError(
'An unexpected error occurred',
'UNKNOWN_ERROR',
{ originalError: error }
);
this.handleVersionControlError(vcError, operationKey, retryCount);
}
throw error;
} finally {
this.hideLoading(operationKey);
}
}
handleVersionControlError(error, operationKey, retryCount) {
this.errors.set(operationKey, error);
// Determine if operation can be retried
const canRetry = retryCount < this.maxRetries;
error.details.retry = canRetry;
error.details.retryCallback = canRetry ?
() => this.retryOperation(operationKey) :
undefined;
this.onError(error);
}
async retryOperation(operationKey) {
const retryCount = (this.retryAttempts.get(operationKey) || 0) + 1;
this.retryAttempts.set(operationKey, retryCount);
// Exponential backoff for retries
const backoffDelay = Math.min(1000 * Math.pow(2, retryCount - 1), 10000);
await new Promise(resolve => setTimeout(resolve, backoffDelay));
// Get the original operation and retry
const operation = this.pendingOperations.get(operationKey);
if (operation) {
return this.wrapOperation(operationKey, operation);
}
}
showLoading(operationKey) {
const loadingElement = document.createElement('div');
loadingElement.className = `loading-indicator loading-${operationKey}`;
loadingElement.innerHTML = `
<div class="loading-spinner"></div>
<span class="loading-text">Processing...</span>
`;
document.body.appendChild(loadingElement);
}
hideLoading(operationKey) {
const loadingElement = document.querySelector(`.loading-${operationKey}`);
if (loadingElement) {
loadingElement.remove();
}
}
}
// Create singleton instance
const errorBoundary = new VersionControlErrorBoundary({
onError: (error) => {
// Log to monitoring system
if (window.monitoring) {
window.monitoring.logError('version_control', error);
}
}
});
// Export error handling utilities
export const versionControl = {
/**
* Wrap version control operations with error handling
*/
async performOperation(key, operation) {
return errorBoundary.wrapOperation(key, operation);
},
/**
* Create a new error instance
*/
createError(message, code, details) {
return new VersionControlError(message, code, details);
},
/**
* Show loading state manually
*/
showLoading(key) {
errorBoundary.showLoading(key);
},
/**
* Hide loading state manually
*/
hideLoading(key) {
errorBoundary.hideLoading(key);
},
/**
* Show error notification manually
*/
showError(error) {
errorBoundary.showErrorNotification(error);
}
};
// Add global error handler for uncaught version control errors
window.addEventListener('unhandledrejection', event => {
if (event.reason instanceof VersionControlError) {
event.preventDefault();
errorBoundary.defaultErrorHandler(event.reason);
}
});