suna/frontend/src/lib/error-handler.ts

195 lines
4.9 KiB
TypeScript

import { toast } from 'sonner';
import { BillingError } from './api';
export interface ApiError extends Error {
status?: number;
code?: string;
details?: any;
response?: Response;
}
export interface ErrorContext {
operation?: string;
resource?: string;
silent?: boolean;
}
const getStatusMessage = (status: number): string => {
switch (status) {
case 400:
return 'Invalid request. Please check your input and try again.';
case 401:
return 'Authentication required. Please sign in again.';
case 403:
return 'Access denied. You don\'t have permission to perform this action.';
case 404:
return 'The requested resource was not found.';
case 408:
return 'Request timeout. Please try again.';
case 409:
return 'Conflict detected. The resource may have been modified by another user.';
case 422:
return 'Invalid data provided. Please check your input.';
case 429:
return 'Too many requests. Please wait a moment and try again.';
case 500:
return 'Server error. Our team has been notified.';
case 502:
return 'Service temporarily unavailable. Please try again in a moment.';
case 503:
return 'Service maintenance in progress. Please try again later.';
case 504:
return 'Request timeout. The server took too long to respond.';
default:
return 'An unexpected error occurred. Please try again.';
}
};
const extractErrorMessage = (error: any): string => {
if (error instanceof BillingError) {
return error.detail?.message || error.message || 'Billing issue detected';
}
if (error instanceof Error) {
return error.message;
}
if (error?.response) {
const status = error.response.status;
return getStatusMessage(status);
}
if (error?.status) {
return getStatusMessage(error.status);
}
if (typeof error === 'string') {
return error;
}
if (error?.message) {
return error.message;
}
if (error?.error) {
return typeof error.error === 'string' ? error.error : error.error.message || 'Unknown error';
}
return 'An unexpected error occurred';
};
const shouldShowError = (error: any, context?: ErrorContext): boolean => {
if (context?.silent) {
return false;
}
if (error instanceof BillingError) {
return false;
}
if (error?.status === 404 && context?.resource) {
return false;
}
return true;
};
const formatErrorMessage = (message: string, context?: ErrorContext): string => {
if (!context?.operation && !context?.resource) {
return message;
}
const parts = [];
if (context.operation) {
parts.push(`Failed to ${context.operation}`);
}
if (context.resource) {
parts.push(context.resource);
}
const prefix = parts.join(' ');
if (message.toLowerCase().includes(context.operation?.toLowerCase() || '')) {
return message;
}
return `${prefix}: ${message}`;
};
export const handleApiError = (error: any, context?: ErrorContext): void => {
console.error('API Error:', error, context);
if (!shouldShowError(error, context)) {
return;
}
const rawMessage = extractErrorMessage(error);
const formattedMessage = formatErrorMessage(rawMessage, context);
if (error?.status >= 500) {
toast.error(formattedMessage, {
description: 'Our team has been notified and is working on a fix.',
duration: 6000,
});
} else if (error?.status === 401) {
toast.error(formattedMessage, {
description: 'Please refresh the page and sign in again.',
duration: 8000,
});
} else if (error?.status === 403) {
toast.error(formattedMessage, {
description: 'Contact support if you believe this is an error.',
duration: 6000,
});
} else if (error?.status === 429) {
toast.warning(formattedMessage, {
description: 'Please wait a moment before trying again.',
duration: 5000,
});
} else {
toast.error(formattedMessage, {
duration: 5000,
});
}
};
export const handleNetworkError = (error: any, context?: ErrorContext): void => {
const isNetworkError =
error?.message?.includes('fetch') ||
error?.message?.includes('network') ||
error?.message?.includes('connection') ||
error?.code === 'NETWORK_ERROR' ||
!navigator.onLine;
if (isNetworkError) {
toast.error('Connection error', {
description: 'Please check your internet connection and try again.',
duration: 6000,
});
} else {
handleApiError(error, context);
}
};
export const handleApiSuccess = (message: string, description?: string): void => {
toast.success(message, {
description,
duration: 3000,
});
};
export const handleApiWarning = (message: string, description?: string): void => {
toast.warning(message, {
description,
duration: 4000,
});
};
export const handleApiInfo = (message: string, description?: string): void => {
toast.info(message, {
description,
duration: 3000,
});
};