334 lines
7.8 KiB
TypeScript
334 lines
7.8 KiB
TypeScript
import { logger } from './logging.js';
|
|
|
|
/**
|
|
* Error types for categorizing service worker errors
|
|
*/
|
|
export enum ServiceWorkerErrorType {
|
|
NETWORK = 'NETWORK',
|
|
CACHE = 'CACHE',
|
|
UPDATE = 'UPDATE',
|
|
CONNECTION = 'CONNECTION',
|
|
TIMEOUT = 'TIMEOUT',
|
|
UNKNOWN = 'UNKNOWN',
|
|
}
|
|
|
|
/**
|
|
* Error severity levels
|
|
*/
|
|
export enum ErrorSeverity {
|
|
DEBUG = 'debug',
|
|
INFO = 'info',
|
|
WARN = 'warn',
|
|
ERROR = 'error',
|
|
FATAL = 'fatal',
|
|
}
|
|
|
|
/**
|
|
* Interface for error context
|
|
*/
|
|
export interface IErrorContext {
|
|
url?: string;
|
|
method?: string;
|
|
statusCode?: number;
|
|
attempt?: number;
|
|
maxAttempts?: number;
|
|
duration?: number;
|
|
componentName?: string;
|
|
additionalInfo?: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Service Worker Error class with type categorization and context
|
|
*/
|
|
export class ServiceWorkerError extends Error {
|
|
public readonly type: ServiceWorkerErrorType;
|
|
public readonly severity: ErrorSeverity;
|
|
public readonly context: IErrorContext;
|
|
public readonly timestamp: number;
|
|
public readonly originalError?: Error;
|
|
|
|
constructor(
|
|
message: string,
|
|
type: ServiceWorkerErrorType = ServiceWorkerErrorType.UNKNOWN,
|
|
severity: ErrorSeverity = ErrorSeverity.ERROR,
|
|
context: IErrorContext = {},
|
|
originalError?: Error
|
|
) {
|
|
super(message);
|
|
this.name = 'ServiceWorkerError';
|
|
this.type = type;
|
|
this.severity = severity;
|
|
this.context = context;
|
|
this.timestamp = Date.now();
|
|
this.originalError = originalError;
|
|
|
|
// Maintain proper stack trace in V8 environments
|
|
if (Error.captureStackTrace) {
|
|
Error.captureStackTrace(this, ServiceWorkerError);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a formatted log message
|
|
*/
|
|
public toLogMessage(): string {
|
|
const parts = [
|
|
`[${this.type}]`,
|
|
this.message,
|
|
];
|
|
|
|
if (this.context.url) {
|
|
parts.push(`URL: ${this.context.url}`);
|
|
}
|
|
if (this.context.method) {
|
|
parts.push(`Method: ${this.context.method}`);
|
|
}
|
|
if (this.context.statusCode !== undefined) {
|
|
parts.push(`Status: ${this.context.statusCode}`);
|
|
}
|
|
if (this.context.attempt !== undefined && this.context.maxAttempts !== undefined) {
|
|
parts.push(`Attempt: ${this.context.attempt}/${this.context.maxAttempts}`);
|
|
}
|
|
if (this.context.duration !== undefined) {
|
|
parts.push(`Duration: ${this.context.duration}ms`);
|
|
}
|
|
|
|
return parts.join(' | ');
|
|
}
|
|
|
|
/**
|
|
* Converts to a plain object for serialization
|
|
*/
|
|
public toJSON(): Record<string, unknown> {
|
|
return {
|
|
name: this.name,
|
|
message: this.message,
|
|
type: this.type,
|
|
severity: this.severity,
|
|
context: this.context,
|
|
timestamp: this.timestamp,
|
|
stack: this.stack,
|
|
originalError: this.originalError?.message,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Error handler for consistent error handling across service worker components
|
|
*/
|
|
export class ErrorHandler {
|
|
private static instance: ErrorHandler;
|
|
private errorHistory: ServiceWorkerError[] = [];
|
|
private readonly maxHistorySize = 100;
|
|
|
|
private constructor() {}
|
|
|
|
/**
|
|
* Gets the singleton instance
|
|
*/
|
|
public static getInstance(): ErrorHandler {
|
|
if (!ErrorHandler.instance) {
|
|
ErrorHandler.instance = new ErrorHandler();
|
|
}
|
|
return ErrorHandler.instance;
|
|
}
|
|
|
|
/**
|
|
* Handles an error with consistent logging and tracking
|
|
*/
|
|
public handle(
|
|
error: Error | ServiceWorkerError | string,
|
|
type: ServiceWorkerErrorType = ServiceWorkerErrorType.UNKNOWN,
|
|
severity: ErrorSeverity = ErrorSeverity.ERROR,
|
|
context: IErrorContext = {}
|
|
): ServiceWorkerError {
|
|
let swError: ServiceWorkerError;
|
|
|
|
if (error instanceof ServiceWorkerError) {
|
|
swError = error;
|
|
} else if (error instanceof Error) {
|
|
swError = new ServiceWorkerError(error.message, type, severity, context, error);
|
|
} else {
|
|
swError = new ServiceWorkerError(String(error), type, severity, context);
|
|
}
|
|
|
|
// Log the error
|
|
this.logError(swError);
|
|
|
|
// Track the error
|
|
this.trackError(swError);
|
|
|
|
return swError;
|
|
}
|
|
|
|
/**
|
|
* Creates and handles a network error
|
|
*/
|
|
public handleNetworkError(
|
|
message: string,
|
|
url: string,
|
|
originalError?: Error,
|
|
context: Partial<IErrorContext> = {}
|
|
): ServiceWorkerError {
|
|
return this.handle(
|
|
originalError || message,
|
|
ServiceWorkerErrorType.NETWORK,
|
|
ErrorSeverity.WARN,
|
|
{ url, ...context }
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates and handles a cache error
|
|
*/
|
|
public handleCacheError(
|
|
message: string,
|
|
url?: string,
|
|
originalError?: Error,
|
|
context: Partial<IErrorContext> = {}
|
|
): ServiceWorkerError {
|
|
return this.handle(
|
|
originalError || message,
|
|
ServiceWorkerErrorType.CACHE,
|
|
ErrorSeverity.ERROR,
|
|
{ url, ...context }
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates and handles an update error
|
|
*/
|
|
public handleUpdateError(
|
|
message: string,
|
|
originalError?: Error,
|
|
context: Partial<IErrorContext> = {}
|
|
): ServiceWorkerError {
|
|
return this.handle(
|
|
originalError || message,
|
|
ServiceWorkerErrorType.UPDATE,
|
|
ErrorSeverity.ERROR,
|
|
context
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates and handles a connection error
|
|
*/
|
|
public handleConnectionError(
|
|
message: string,
|
|
originalError?: Error,
|
|
context: Partial<IErrorContext> = {}
|
|
): ServiceWorkerError {
|
|
return this.handle(
|
|
originalError || message,
|
|
ServiceWorkerErrorType.CONNECTION,
|
|
ErrorSeverity.WARN,
|
|
context
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates and handles a timeout error
|
|
*/
|
|
public handleTimeoutError(
|
|
message: string,
|
|
url?: string,
|
|
duration?: number,
|
|
context: Partial<IErrorContext> = {}
|
|
): ServiceWorkerError {
|
|
return this.handle(
|
|
message,
|
|
ServiceWorkerErrorType.TIMEOUT,
|
|
ErrorSeverity.WARN,
|
|
{ url, duration, ...context }
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets the error history
|
|
*/
|
|
public getErrorHistory(): ServiceWorkerError[] {
|
|
return [...this.errorHistory];
|
|
}
|
|
|
|
/**
|
|
* Gets errors by type
|
|
*/
|
|
public getErrorsByType(type: ServiceWorkerErrorType): ServiceWorkerError[] {
|
|
return this.errorHistory.filter((e) => e.type === type);
|
|
}
|
|
|
|
/**
|
|
* Gets errors within a time range
|
|
*/
|
|
public getRecentErrors(withinMs: number): ServiceWorkerError[] {
|
|
const cutoff = Date.now() - withinMs;
|
|
return this.errorHistory.filter((e) => e.timestamp >= cutoff);
|
|
}
|
|
|
|
/**
|
|
* Clears the error history
|
|
*/
|
|
public clearHistory(): void {
|
|
this.errorHistory = [];
|
|
}
|
|
|
|
/**
|
|
* Gets error statistics
|
|
*/
|
|
public getStats(): Record<ServiceWorkerErrorType, number> {
|
|
const stats: Record<ServiceWorkerErrorType, number> = {
|
|
[ServiceWorkerErrorType.NETWORK]: 0,
|
|
[ServiceWorkerErrorType.CACHE]: 0,
|
|
[ServiceWorkerErrorType.UPDATE]: 0,
|
|
[ServiceWorkerErrorType.CONNECTION]: 0,
|
|
[ServiceWorkerErrorType.TIMEOUT]: 0,
|
|
[ServiceWorkerErrorType.UNKNOWN]: 0,
|
|
};
|
|
|
|
for (const error of this.errorHistory) {
|
|
stats[error.type]++;
|
|
}
|
|
|
|
return stats;
|
|
}
|
|
|
|
/**
|
|
* Logs an error with the appropriate severity
|
|
*/
|
|
private logError(error: ServiceWorkerError): void {
|
|
const logMessage = error.toLogMessage();
|
|
|
|
switch (error.severity) {
|
|
case ErrorSeverity.DEBUG:
|
|
logger.log('note', logMessage);
|
|
break;
|
|
case ErrorSeverity.INFO:
|
|
logger.log('info', logMessage);
|
|
break;
|
|
case ErrorSeverity.WARN:
|
|
logger.log('warn', logMessage);
|
|
break;
|
|
case ErrorSeverity.ERROR:
|
|
case ErrorSeverity.FATAL:
|
|
logger.log('error', logMessage);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tracks an error in the history
|
|
*/
|
|
private trackError(error: ServiceWorkerError): void {
|
|
this.errorHistory.push(error);
|
|
|
|
// Trim history if needed
|
|
if (this.errorHistory.length > this.maxHistorySize) {
|
|
this.errorHistory = this.errorHistory.slice(-this.maxHistorySize);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export singleton getter for convenience
|
|
export const getErrorHandler = (): ErrorHandler => ErrorHandler.getInstance();
|