mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 09:31:12 -05:00
Approve database migration
This commit is contained in:
64
src/lib/environmentContext.ts
Normal file
64
src/lib/environmentContext.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Environment Context Capture
|
||||
* Captures browser/device information for error reports
|
||||
*/
|
||||
|
||||
export interface EnvironmentContext {
|
||||
viewport: { width: number; height: number };
|
||||
screen: { width: number; height: number };
|
||||
memory?: { usedJSHeapSize?: number; totalJSHeapSize?: number };
|
||||
connection?: string;
|
||||
timezone: string;
|
||||
language: string;
|
||||
platform: string;
|
||||
cookiesEnabled: boolean;
|
||||
localStorage: boolean;
|
||||
sessionStorage: boolean;
|
||||
}
|
||||
|
||||
export function captureEnvironmentContext(): EnvironmentContext {
|
||||
const context: EnvironmentContext = {
|
||||
viewport: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
},
|
||||
screen: {
|
||||
width: window.screen.width,
|
||||
height: window.screen.height,
|
||||
},
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
language: navigator.language,
|
||||
platform: navigator.platform,
|
||||
cookiesEnabled: navigator.cookieEnabled,
|
||||
localStorage: isStorageAvailable('localStorage'),
|
||||
sessionStorage: isStorageAvailable('sessionStorage'),
|
||||
};
|
||||
|
||||
// Memory info (Chrome only)
|
||||
if ('memory' in performance && (performance as any).memory) {
|
||||
const memory = (performance as any).memory;
|
||||
context.memory = {
|
||||
usedJSHeapSize: memory.usedJSHeapSize,
|
||||
totalJSHeapSize: memory.totalJSHeapSize,
|
||||
};
|
||||
}
|
||||
|
||||
// Connection info
|
||||
if ('connection' in navigator) {
|
||||
context.connection = (navigator as any).connection?.effectiveType;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function isStorageAvailable(type: 'localStorage' | 'sessionStorage'): boolean {
|
||||
try {
|
||||
const storage = window[type];
|
||||
const test = '__storage_test__';
|
||||
storage.setItem(test, test);
|
||||
storage.removeItem(test);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
80
src/lib/errorBreadcrumbs.ts
Normal file
80
src/lib/errorBreadcrumbs.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Error Breadcrumb Tracking
|
||||
* Captures user actions before errors occur for better debugging
|
||||
*/
|
||||
|
||||
export interface Breadcrumb {
|
||||
timestamp: string;
|
||||
category: 'navigation' | 'user_action' | 'api_call' | 'state_change';
|
||||
message: string;
|
||||
level: 'info' | 'warning' | 'error';
|
||||
data?: Record<string, any>;
|
||||
}
|
||||
|
||||
class BreadcrumbManager {
|
||||
private breadcrumbs: Breadcrumb[] = [];
|
||||
private readonly MAX_BREADCRUMBS = 10;
|
||||
|
||||
add(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void {
|
||||
const newBreadcrumb: Breadcrumb = {
|
||||
...breadcrumb,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
this.breadcrumbs.push(newBreadcrumb);
|
||||
|
||||
// Keep only last 10 breadcrumbs
|
||||
if (this.breadcrumbs.length > this.MAX_BREADCRUMBS) {
|
||||
this.breadcrumbs.shift();
|
||||
}
|
||||
}
|
||||
|
||||
getAll(): Breadcrumb[] {
|
||||
return [...this.breadcrumbs];
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.breadcrumbs = [];
|
||||
}
|
||||
}
|
||||
|
||||
export const breadcrumbManager = new BreadcrumbManager();
|
||||
|
||||
// Helper functions for common breadcrumb types
|
||||
export const breadcrumb = {
|
||||
navigation: (to: string, from?: string) => {
|
||||
breadcrumbManager.add({
|
||||
category: 'navigation',
|
||||
message: `Navigated to ${to}`,
|
||||
level: 'info',
|
||||
data: { to, from },
|
||||
});
|
||||
},
|
||||
|
||||
userAction: (action: string, component: string, data?: Record<string, any>) => {
|
||||
breadcrumbManager.add({
|
||||
category: 'user_action',
|
||||
message: `User ${action} in ${component}`,
|
||||
level: 'info',
|
||||
data,
|
||||
});
|
||||
},
|
||||
|
||||
apiCall: (endpoint: string, method: string, status?: number) => {
|
||||
breadcrumbManager.add({
|
||||
category: 'api_call',
|
||||
message: `API ${method} ${endpoint}`,
|
||||
level: status && status >= 400 ? 'error' : 'info',
|
||||
data: { endpoint, method, status },
|
||||
});
|
||||
},
|
||||
|
||||
stateChange: (description: string, data?: Record<string, any>) => {
|
||||
breadcrumbManager.add({
|
||||
category: 'state_change',
|
||||
message: description,
|
||||
level: 'info',
|
||||
data,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -21,7 +21,10 @@ export class AppError extends Error {
|
||||
export const handleError = (
|
||||
error: unknown,
|
||||
context: ErrorContext
|
||||
): void => {
|
||||
): string => { // Now returns error ID
|
||||
const errorId = context.metadata?.requestId as string | undefined;
|
||||
const shortErrorId = errorId ? errorId.slice(0, 8) : undefined;
|
||||
|
||||
const errorMessage = error instanceof AppError
|
||||
? error.userMessage || error.message
|
||||
: error instanceof Error
|
||||
@@ -32,14 +35,19 @@ export const handleError = (
|
||||
logger.error('Error occurred', {
|
||||
...context,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
errorId,
|
||||
});
|
||||
|
||||
// Show user-friendly toast
|
||||
// Show user-friendly toast with error ID
|
||||
toast.error(context.action, {
|
||||
description: errorMessage,
|
||||
duration: 5000
|
||||
description: shortErrorId
|
||||
? `${errorMessage}\n\nReference ID: ${shortErrorId}`
|
||||
: errorMessage,
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
return errorId || 'unknown';
|
||||
};
|
||||
|
||||
export const handleSuccess = (
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { requestContext, type RequestContext } from './requestContext';
|
||||
import { breadcrumbManager } from './errorBreadcrumbs';
|
||||
import { captureEnvironmentContext } from './environmentContext';
|
||||
|
||||
export interface RequestTrackingOptions {
|
||||
endpoint: string;
|
||||
@@ -63,8 +65,16 @@ export async function trackRequest<T>(
|
||||
} catch (error: unknown) {
|
||||
const duration = Date.now() - start;
|
||||
const errorInfo = error instanceof Error
|
||||
? { type: error.name, message: error.message }
|
||||
: { type: 'UnknownError', message: String(error) };
|
||||
? {
|
||||
type: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack ? error.stack.slice(0, 5000) : undefined // Limit to 5000 chars
|
||||
}
|
||||
: { type: 'UnknownError', message: String(error), stack: undefined };
|
||||
|
||||
// Capture breadcrumbs and environment
|
||||
const breadcrumbs = breadcrumbManager.getAll();
|
||||
const environment = captureEnvironmentContext();
|
||||
|
||||
// Log error to database (fire and forget)
|
||||
logRequestMetadata({
|
||||
@@ -76,6 +86,9 @@ export async function trackRequest<T>(
|
||||
duration,
|
||||
errorType: errorInfo.type,
|
||||
errorMessage: errorInfo.message,
|
||||
errorStack: errorInfo.stack,
|
||||
breadcrumbs,
|
||||
environmentContext: environment,
|
||||
userAgent: context.userAgent,
|
||||
clientVersion: context.clientVersion,
|
||||
parentRequestId: options.parentRequestId,
|
||||
@@ -100,6 +113,9 @@ interface RequestMetadata {
|
||||
duration: number;
|
||||
errorType?: string;
|
||||
errorMessage?: string;
|
||||
errorStack?: string;
|
||||
breadcrumbs?: any[];
|
||||
environmentContext?: any;
|
||||
userAgent?: string;
|
||||
clientVersion?: string;
|
||||
parentRequestId?: string;
|
||||
@@ -117,6 +133,9 @@ async function logRequestMetadata(metadata: RequestMetadata): Promise<void> {
|
||||
p_duration_ms: metadata.duration,
|
||||
p_error_type: metadata.errorType ?? undefined,
|
||||
p_error_message: metadata.errorMessage ?? undefined,
|
||||
p_error_stack: metadata.errorStack ?? undefined,
|
||||
p_breadcrumbs: metadata.breadcrumbs ? JSON.stringify(metadata.breadcrumbs) : '[]',
|
||||
p_environment_context: metadata.environmentContext ? JSON.stringify(metadata.environmentContext) : '{}',
|
||||
p_user_agent: metadata.userAgent ?? undefined,
|
||||
p_client_version: metadata.clientVersion ?? undefined,
|
||||
p_parent_request_id: metadata.parentRequestId ?? undefined,
|
||||
|
||||
Reference in New Issue
Block a user