Files
thrilltrack-explorer/src/lib/errorHandler.ts
gpt-engineer-app[bot] acfbf872d2 Fix Recent Activity errors
2025-11-05 03:53:58 +00:00

205 lines
5.7 KiB
TypeScript

import { toast } from 'sonner';
import { logger } from './logger';
import { supabase } from '@/integrations/supabase/client';
import { breadcrumbManager } from './errorBreadcrumbs';
import { captureEnvironmentContext } from './environmentContext';
export type ErrorContext = {
action: string;
userId?: string;
metadata?: Record<string, unknown>;
};
export class AppError extends Error {
constructor(
message: string,
public code: string,
public userMessage?: string
) {
super(message);
this.name = 'AppError';
}
}
export const handleError = (
error: unknown,
context: ErrorContext
): string => {
// Generate or use existing error ID
const errorId = (context.metadata?.requestId as string) || crypto.randomUUID();
const shortErrorId = errorId.slice(0, 8);
const errorMessage = error instanceof AppError
? error.userMessage || error.message
: error instanceof Error
? error.message
: 'An unexpected error occurred';
// Log to console/monitoring with enhanced debugging
const stack = error instanceof Error ? error.stack : undefined;
logger.error('Error occurred', {
...context,
error: error instanceof Error ? error.message : String(error),
stack,
errorId,
errorType: typeof error,
errorConstructor: error?.constructor?.name,
hasStack: !!stack,
});
// Additional debug logging when stack is missing
if (!stack) {
console.error('[handleError] Error without stack trace:', {
type: typeof error,
constructor: error?.constructor?.name,
error: error,
context,
errorId
});
}
// Log to database with breadcrumbs (non-blocking)
try {
const envContext = captureEnvironmentContext();
const breadcrumbs = breadcrumbManager.getAll();
// Fire-and-forget database logging
supabase.rpc('log_request_metadata', {
p_request_id: errorId,
p_user_id: context.userId || undefined,
p_endpoint: context.action,
p_method: 'ERROR',
p_status_code: 500,
p_error_type: error instanceof Error ? error.name : 'UnknownError',
p_error_message: errorMessage,
p_error_stack: error instanceof Error ? error.stack : undefined,
p_user_agent: navigator.userAgent,
p_breadcrumbs: JSON.stringify(breadcrumbs),
p_timezone: envContext.timezone,
p_referrer: document.referrer || undefined,
}).then(({ error: dbError }) => {
if (dbError) {
logger.error('Failed to log error to database', { dbError });
}
});
} catch (logError) {
logger.error('Failed to capture error context', { logError });
}
// Show user-friendly toast with error ID
toast.error(context.action, {
description: `${errorMessage}\n\nReference ID: ${shortErrorId}`,
duration: 5000,
});
return errorId;
};
export const handleSuccess = (
title: string,
description?: string
): void => {
toast.success(title, {
description,
duration: 3000
});
};
export const handleInfo = (
title: string,
description?: string
): void => {
toast.info(title, {
description,
duration: 4000
});
};
/**
* Handle non-critical errors (background failures) that should be logged
* to the database WITHOUT showing user toasts
* Use this for fire-and-forget operations where user shouldn't be interrupted
*/
export const handleNonCriticalError = (
error: unknown,
context: ErrorContext
): string => {
const errorId = crypto.randomUUID();
const shortErrorId = errorId.slice(0, 8);
const errorMessage = error instanceof AppError
? error.userMessage || error.message
: error instanceof Error
? error.message
: 'An unexpected error occurred';
// Log to console/monitoring (same as handleError)
logger.error('Non-critical error occurred', {
...context,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
errorId,
severity: 'low',
});
// Log to database with breadcrumbs (non-blocking, fire-and-forget)
try {
const envContext = captureEnvironmentContext();
const breadcrumbs = breadcrumbManager.getAll();
supabase.rpc('log_request_metadata', {
p_request_id: errorId,
p_user_id: context.userId || undefined,
p_endpoint: context.action,
p_method: 'NON_CRITICAL_ERROR',
p_status_code: 500,
p_error_type: error instanceof Error ? error.name : 'UnknownError',
p_error_message: errorMessage,
p_error_stack: error instanceof Error ? error.stack : undefined,
p_user_agent: navigator.userAgent,
p_breadcrumbs: JSON.stringify(breadcrumbs),
p_timezone: envContext.timezone,
p_referrer: document.referrer || undefined,
}).then(({ error: dbError }) => {
if (dbError) {
logger.error('Failed to log non-critical error to database', { dbError });
}
});
} catch (logError) {
logger.error('Failed to capture non-critical error context', { logError });
}
// NO TOAST - This is the key difference from handleError()
return errorId;
};
/**
* Type-safe error message extraction utility
* Use this instead of `error: any` in catch blocks
*/
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
if (typeof error === 'string') {
return error;
}
if (error && typeof error === 'object' && 'message' in error) {
return String(error.message);
}
return 'An unexpected error occurred';
}
/**
* Type guard to check if error has a code property
*/
export function hasErrorCode(error: unknown): error is { code: string } {
return (
error !== null &&
typeof error === 'object' &&
'code' in error &&
typeof (error as { code: unknown }).code === 'string'
);
}