Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
260 lines
7.1 KiB
JavaScript
260 lines
7.1 KiB
JavaScript
/**
|
|
* Error Handler
|
|
* Handles errors gracefully without breaking the onboarding tour
|
|
*/
|
|
|
|
(function(window) {
|
|
'use strict';
|
|
|
|
class ErrorHandler {
|
|
constructor() {
|
|
this.errors = [];
|
|
this.maxErrors = 50; // Keep last 50 errors
|
|
}
|
|
|
|
/**
|
|
* Log an error without breaking the tour
|
|
* @param {string} context - Context where error occurred
|
|
* @param {Error|string} error - The error object or message
|
|
* @param {Object} metadata - Additional metadata
|
|
*/
|
|
logError(context, error, metadata = {}) {
|
|
const errorEntry = {
|
|
timestamp: new Date().toISOString(),
|
|
context,
|
|
message: error instanceof Error ? error.message : error,
|
|
stack: error instanceof Error ? error.stack : null,
|
|
metadata
|
|
};
|
|
|
|
// Add to errors array
|
|
this.errors.push(errorEntry);
|
|
|
|
// Keep only last maxErrors
|
|
if (this.errors.length > this.maxErrors) {
|
|
this.errors.shift();
|
|
}
|
|
|
|
// Log to console
|
|
console.error(`[Onboarding Error] ${context}:`, error, metadata);
|
|
|
|
// Optionally send to error tracking service
|
|
// this.sendToErrorTracking(errorEntry);
|
|
}
|
|
|
|
/**
|
|
* Attempt to recover from an error and continue tour
|
|
* @param {Error} error - The error object
|
|
* @param {number} currentStep - Current step index
|
|
* @returns {Object} Recovery action
|
|
*/
|
|
recoverFromError(error, currentStep) {
|
|
const errorType = this.classifyError(error);
|
|
|
|
switch (errorType) {
|
|
case 'ELEMENT_NOT_FOUND':
|
|
this.logError('Element Not Found', error, { currentStep });
|
|
return {
|
|
action: 'SKIP_STEP',
|
|
nextStep: currentStep + 1,
|
|
message: 'Target element not found, skipping to next step'
|
|
};
|
|
|
|
case 'STORAGE_UNAVAILABLE':
|
|
this.logError('Storage Unavailable', error);
|
|
return {
|
|
action: 'USE_MEMORY_STORAGE',
|
|
message: 'Local storage unavailable, using in-memory storage'
|
|
};
|
|
|
|
case 'DRIVER_NOT_LOADED':
|
|
this.logError('Driver.js Not Loaded', error);
|
|
return {
|
|
action: 'ABORT_TOUR',
|
|
message: 'Driver.js library not loaded, cannot start tour'
|
|
};
|
|
|
|
case 'INVALID_TOOLTIP':
|
|
this.logError('Invalid Tooltip Configuration', error, { currentStep });
|
|
return {
|
|
action: 'SKIP_STEP',
|
|
nextStep: currentStep + 1,
|
|
message: 'Invalid tooltip configuration, skipping'
|
|
};
|
|
|
|
case 'THEME_DETECTION_FAILED':
|
|
this.logError('Theme Detection Failed', error);
|
|
return {
|
|
action: 'USE_DEFAULT_THEME',
|
|
message: 'Using default dark theme'
|
|
};
|
|
|
|
default:
|
|
this.logError('Unknown Error', error, { currentStep });
|
|
return {
|
|
action: 'ABORT_TOUR',
|
|
message: 'Unexpected error occurred, aborting tour'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Classify error type
|
|
* @private
|
|
* @param {Error} error - The error object
|
|
* @returns {string} Error type
|
|
*/
|
|
classifyError(error) {
|
|
const message = error.message || error.toString();
|
|
|
|
if (message.includes('element') && message.includes('not found')) {
|
|
return 'ELEMENT_NOT_FOUND';
|
|
}
|
|
if (message.includes('storage') || message.includes('quota')) {
|
|
return 'STORAGE_UNAVAILABLE';
|
|
}
|
|
if (message.includes('driver') || message.includes('undefined')) {
|
|
return 'DRIVER_NOT_LOADED';
|
|
}
|
|
if (message.includes('invalid') || message.includes('validation')) {
|
|
return 'INVALID_TOOLTIP';
|
|
}
|
|
if (message.includes('theme')) {
|
|
return 'THEME_DETECTION_FAILED';
|
|
}
|
|
|
|
return 'UNKNOWN';
|
|
}
|
|
|
|
/**
|
|
* Get all logged errors
|
|
* @returns {Array} Array of error entries
|
|
*/
|
|
getErrors() {
|
|
return [...this.errors];
|
|
}
|
|
|
|
/**
|
|
* Clear all logged errors
|
|
*/
|
|
clearErrors() {
|
|
this.errors = [];
|
|
}
|
|
|
|
/**
|
|
* Get error statistics
|
|
* @returns {Object} Error statistics
|
|
*/
|
|
getStatistics() {
|
|
const stats = {
|
|
total: this.errors.length,
|
|
byContext: {},
|
|
byType: {},
|
|
recent: this.errors.slice(-10)
|
|
};
|
|
|
|
this.errors.forEach(error => {
|
|
// Count by context
|
|
stats.byContext[error.context] = (stats.byContext[error.context] || 0) + 1;
|
|
|
|
// Count by type
|
|
const type = this.classifyError({ message: error.message });
|
|
stats.byType[type] = (stats.byType[type] || 0) + 1;
|
|
});
|
|
|
|
return stats;
|
|
}
|
|
|
|
/**
|
|
* Handle graceful degradation when Driver.js fails to load
|
|
* @returns {boolean} Whether fallback was successful
|
|
*/
|
|
handleDriverLoadFailure() {
|
|
this.logError('Driver.js Load Failure', 'Driver.js library failed to load');
|
|
|
|
// Show fallback message
|
|
const fallbackMessage = document.createElement('div');
|
|
fallbackMessage.id = 'onboarding-fallback';
|
|
fallbackMessage.style.cssText = `
|
|
position: fixed;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
background: var(--card-base, #2a2a2a);
|
|
color: var(--fg, #ffffff);
|
|
padding: 15px 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
z-index: 9999;
|
|
max-width: 300px;
|
|
font-size: 14px;
|
|
`;
|
|
fallbackMessage.innerHTML = `
|
|
<strong>Welcome to DashCaddy!</strong><br>
|
|
<p style="margin: 10px 0 0 0; font-size: 12px;">
|
|
The interactive tour is unavailable, but you can explore the dashboard freely.
|
|
Check the documentation for help getting started.
|
|
</p>
|
|
`;
|
|
|
|
document.body.appendChild(fallbackMessage);
|
|
|
|
// Auto-remove after 10 seconds
|
|
setTimeout(() => {
|
|
if (fallbackMessage.parentNode) {
|
|
fallbackMessage.parentNode.removeChild(fallbackMessage);
|
|
}
|
|
}, 10000);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Handle storage unavailable scenario
|
|
* @returns {Object} In-memory storage fallback
|
|
*/
|
|
handleStorageUnavailable() {
|
|
this.logError('Storage Unavailable', 'Local storage is not available');
|
|
|
|
// Create in-memory storage
|
|
const memoryStorage = {
|
|
data: {},
|
|
getItem(key) {
|
|
return this.data[key] || null;
|
|
},
|
|
setItem(key, value) {
|
|
this.data[key] = value;
|
|
},
|
|
removeItem(key) {
|
|
delete this.data[key];
|
|
},
|
|
clear() {
|
|
this.data = {};
|
|
}
|
|
};
|
|
|
|
console.warn('[ErrorHandler] Using in-memory storage - progress will not persist');
|
|
return memoryStorage;
|
|
}
|
|
|
|
/**
|
|
* Send error to tracking service (placeholder)
|
|
* @private
|
|
* @param {Object} errorEntry - Error entry to send
|
|
*/
|
|
sendToErrorTracking(errorEntry) {
|
|
// Placeholder for error tracking integration
|
|
// Could integrate with Sentry, LogRocket, etc.
|
|
// Example:
|
|
// if (window.Sentry) {
|
|
// Sentry.captureException(new Error(errorEntry.message), {
|
|
// extra: errorEntry.metadata
|
|
// });
|
|
// }
|
|
}
|
|
}
|
|
|
|
window.ErrorHandler = ErrorHandler;
|
|
console.log('[ErrorHandler] Module loaded');
|
|
|
|
})(window);
|