| | |
| | |
| | |
| |
|
| |
|
| | class ErrorHandler {
|
| | constructor() {
|
| | this.errors = [];
|
| | this.maxErrors = 100;
|
| | this.init();
|
| | }
|
| |
|
| | init() {
|
| |
|
| | window.addEventListener('error', (event) => {
|
| | this.handleError(event.error || event.message, 'Global Error');
|
| | event.preventDefault();
|
| | });
|
| |
|
| |
|
| | window.addEventListener('unhandledrejection', (event) => {
|
| | this.handleError(event.reason, 'Unhandled Promise');
|
| | event.preventDefault();
|
| | });
|
| |
|
| | console.log('✅ Error Handler initialized');
|
| | }
|
| |
|
| | |
| | |
| |
|
| | handleError(error, context = 'Unknown') {
|
| | const errorInfo = {
|
| | message: this.getErrorMessage(error),
|
| | context,
|
| | timestamp: Date.now(),
|
| | stack: error?.stack || null,
|
| | url: window.location.href
|
| | };
|
| |
|
| |
|
| | console.error(`[${context}]`, error);
|
| |
|
| |
|
| | this.errors.push(errorInfo);
|
| | if (this.errors.length > this.maxErrors) {
|
| | this.errors.shift();
|
| | }
|
| |
|
| |
|
| | this.showUserError(errorInfo);
|
| | }
|
| |
|
| | |
| | |
| |
|
| | getErrorMessage(error) {
|
| | if (typeof error === 'string') return error;
|
| | if (error?.message) return error.message;
|
| | if (error?.toString) return error.toString();
|
| | return 'An unknown error occurred';
|
| | }
|
| |
|
| | |
| | |
| |
|
| | showUserError(errorInfo) {
|
| | const message = this.getUserFriendlyMessage(errorInfo.message);
|
| |
|
| | if (window.uiManager) {
|
| | window.uiManager.showToast(message, 'error', 5000);
|
| | } else {
|
| |
|
| | console.error('Error:', message);
|
| | alert(message);
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | getUserFriendlyMessage(technicalMessage) {
|
| | const lowerMessage = technicalMessage.toLowerCase();
|
| |
|
| |
|
| | if (lowerMessage.includes('network') || lowerMessage.includes('fetch')) {
|
| | return '🌐 Network error. Please check your connection.';
|
| | }
|
| |
|
| |
|
| | if (lowerMessage.includes('timeout') || lowerMessage.includes('timed out')) {
|
| | return '⏱️ Request timed out. Please try again.';
|
| | }
|
| |
|
| |
|
| | if (lowerMessage.includes('404') || lowerMessage.includes('not found')) {
|
| | return '🔍 Resource not found. It may have been moved or deleted.';
|
| | }
|
| |
|
| |
|
| | if (lowerMessage.includes('401') || lowerMessage.includes('unauthorized')) {
|
| | return '🔒 Authentication required. Please log in.';
|
| | }
|
| |
|
| |
|
| | if (lowerMessage.includes('403') || lowerMessage.includes('forbidden')) {
|
| | return '🚫 Access denied. You don\'t have permission.';
|
| | }
|
| |
|
| |
|
| | if (lowerMessage.includes('500') || lowerMessage.includes('server error')) {
|
| | return '⚠️ Server error. We\'re working on it!';
|
| | }
|
| |
|
| |
|
| | if (lowerMessage.includes('database') || lowerMessage.includes('sql')) {
|
| | return '💾 Database error. Please try again later.';
|
| | }
|
| |
|
| |
|
| | if (lowerMessage.includes('api')) {
|
| | return '🔌 API error. Using fallback data.';
|
| | }
|
| |
|
| |
|
| | return `⚠️ ${technicalMessage}`;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | getErrors() {
|
| | return this.errors;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | clearErrors() {
|
| | this.errors = [];
|
| | }
|
| |
|
| | |
| | |
| |
|
| | exportErrors() {
|
| | const data = JSON.stringify(this.errors, null, 2);
|
| | const blob = new Blob([data], { type: 'application/json' });
|
| | const url = URL.createObjectURL(blob);
|
| |
|
| | const a = document.createElement('a');
|
| | a.href = url;
|
| | a.download = `errors-${Date.now()}.json`;
|
| | a.click();
|
| |
|
| | URL.revokeObjectURL(url);
|
| | }
|
| | }
|
| |
|
| |
|
| | class APIErrorHandler {
|
| | static async handleAPIError(response, fallbackData = null) {
|
| | let error = {
|
| | status: response?.status || 500,
|
| | statusText: response?.statusText || 'Unknown',
|
| | url: response?.url || 'unknown'
|
| | };
|
| |
|
| | try {
|
| | const data = await response.json();
|
| | error.message = data.message || data.error || 'API Error';
|
| | error.details = data.details || null;
|
| | } catch (e) {
|
| | error.message = `HTTP ${error.status}: ${error.statusText}`;
|
| | }
|
| |
|
| | console.error('API Error:', error);
|
| |
|
| |
|
| | if (window.errorHandler) {
|
| | window.errorHandler.handleError(error, 'API Error');
|
| | }
|
| |
|
| |
|
| | if (fallbackData) {
|
| | console.warn('Using fallback data due to API error');
|
| | return {
|
| | success: false,
|
| | error: error.message,
|
| | data: fallbackData,
|
| | fallback: true
|
| | };
|
| | }
|
| |
|
| | throw error;
|
| | }
|
| |
|
| | static async fetchWithFallback(url, options = {}, fallbackData = null) {
|
| | try {
|
| | const response = await fetch(url, {
|
| | ...options,
|
| | signal: options.signal || AbortSignal.timeout(options.timeout || 10000)
|
| | });
|
| |
|
| | if (!response.ok) {
|
| | return await this.handleAPIError(response, fallbackData);
|
| | }
|
| |
|
| | const data = await response.json();
|
| | return {
|
| | success: true,
|
| | data,
|
| | fallback: false
|
| | };
|
| | } catch (error) {
|
| | console.error('Fetch error:', error);
|
| |
|
| | if (window.errorHandler) {
|
| | window.errorHandler.handleError(error, 'Fetch Error');
|
| | }
|
| |
|
| | if (fallbackData) {
|
| | return {
|
| | success: false,
|
| | error: error.message,
|
| | data: fallbackData,
|
| | fallback: true
|
| | };
|
| | }
|
| |
|
| | throw error;
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | class FormValidator {
|
| | static validateRequired(value, fieldName) {
|
| | if (!value || (typeof value === 'string' && value.trim() === '')) {
|
| | return `${fieldName} is required`;
|
| | }
|
| | return null;
|
| | }
|
| |
|
| | static validateEmail(email) {
|
| | const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
| | if (!re.test(email)) {
|
| | return 'Invalid email address';
|
| | }
|
| | return null;
|
| | }
|
| |
|
| | static validateURL(url) {
|
| | try {
|
| | new URL(url);
|
| | return null;
|
| | } catch {
|
| | return 'Invalid URL';
|
| | }
|
| | }
|
| |
|
| | static validateNumber(value, min = null, max = null) {
|
| | const num = Number(value);
|
| | if (isNaN(num)) {
|
| | return 'Must be a number';
|
| | }
|
| | if (min !== null && num < min) {
|
| | return `Must be at least ${min}`;
|
| | }
|
| | if (max !== null && num > max) {
|
| | return `Must be at most ${max}`;
|
| | }
|
| | return null;
|
| | }
|
| |
|
| | static validateForm(formElement) {
|
| | const errors = {};
|
| | const inputs = formElement.querySelectorAll('[data-validate]');
|
| |
|
| | inputs.forEach(input => {
|
| | const rules = input.dataset.validate.split('|');
|
| | const fieldName = input.name || input.id;
|
| |
|
| | rules.forEach(rule => {
|
| | let error = null;
|
| |
|
| | if (rule === 'required') {
|
| | error = this.validateRequired(input.value, fieldName);
|
| | } else if (rule === 'email') {
|
| | error = this.validateEmail(input.value);
|
| | } else if (rule === 'url') {
|
| | error = this.validateURL(input.value);
|
| | } else if (rule.startsWith('number')) {
|
| | const params = rule.match(/number\((\d+),(\d+)\)/);
|
| | error = this.validateNumber(
|
| | input.value,
|
| | params ? parseInt(params[1]) : null,
|
| | params ? parseInt(params[2]) : null
|
| | );
|
| | }
|
| |
|
| | if (error) {
|
| | errors[fieldName] = error;
|
| | }
|
| | });
|
| | });
|
| |
|
| | return {
|
| | valid: Object.keys(errors).length === 0,
|
| | errors
|
| | };
|
| | }
|
| | }
|
| |
|
| |
|
| | class RetryHelper {
|
| | static async retry(fn, options = {}) {
|
| | const {
|
| | maxAttempts = 3,
|
| | delay = 1000,
|
| | backoff = 2,
|
| | onRetry = null
|
| | } = options;
|
| |
|
| | let lastError;
|
| |
|
| | for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
| | try {
|
| | return await fn();
|
| | } catch (error) {
|
| | lastError = error;
|
| |
|
| | if (attempt < maxAttempts) {
|
| | const waitTime = delay * Math.pow(backoff, attempt - 1);
|
| | console.warn(`Attempt ${attempt} failed, retrying in ${waitTime}ms...`);
|
| |
|
| | if (onRetry) {
|
| | onRetry(attempt, error);
|
| | }
|
| |
|
| | await new Promise(resolve => setTimeout(resolve, waitTime));
|
| | }
|
| | }
|
| | }
|
| |
|
| | throw lastError;
|
| | }
|
| | }
|
| |
|
| |
|
| | const errorHandler = new ErrorHandler();
|
| |
|
| |
|
| | if (typeof module !== 'undefined' && module.exports) {
|
| | module.exports = {
|
| | ErrorHandler,
|
| | APIErrorHandler,
|
| | FormValidator,
|
| | RetryHelper,
|
| | errorHandler
|
| | };
|
| | }
|
| |
|
| |
|
| | window.errorHandler = errorHandler;
|
| | window.APIErrorHandler = APIErrorHandler;
|
| | window.FormValidator = FormValidator;
|
| | window.RetryHelper = RetryHelper;
|
| |
|
| | console.log('✅ Error Handler loaded and ready');
|
| |
|