Connect to Lovable Cloud

Improve CORS handling and error logging to fix moderation edge cases:
- Add x-idempotency-key to allowed CORS headers and expose explicit POST methods
- Extend CORS headers to include Access-Control-Allow-Methods
- Update edge function tracing and client error handling to better detect and log CORS/network issues
- Enhance error handling utilities to surface CORS-related failures and provide clearer user messages
This commit is contained in:
gpt-engineer-app[bot]
2025-11-11 02:39:15 +00:00
parent c632e559d0
commit 7642ac435b
3 changed files with 53 additions and 3 deletions

View File

@@ -10,6 +10,7 @@ import { trackRequest } from './requestTracking';
import { getErrorMessage } from './errorHandler'; import { getErrorMessage } from './errorHandler';
import { withRetry, isRetryableError, type RetryOptions } from './retryHelpers'; import { withRetry, isRetryableError, type RetryOptions } from './retryHelpers';
import { breadcrumb } from './errorBreadcrumbs'; import { breadcrumb } from './errorBreadcrumbs';
import { logger } from './logger';
/** /**
* Invoke a Supabase edge function with request tracking * Invoke a Supabase edge function with request tracking
@@ -149,9 +150,31 @@ export async function invokeWithTracking<T = any>(
} }
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
// Detect CORS errors specifically
const isCorsError = errorMessage.toLowerCase().includes('cors') ||
errorMessage.toLowerCase().includes('cross-origin') ||
errorMessage.toLowerCase().includes('failed to send') ||
(error instanceof TypeError && errorMessage.toLowerCase().includes('failed to fetch'));
// Enhanced error logging
logger.error('[EdgeFunctionTracking] Edge function invocation failed', {
functionName,
error: errorMessage,
errorType: isCorsError ? 'CORS/Network' : (error as any)?.name || 'Unknown',
attempts: attemptCount,
isCorsError,
debugHint: isCorsError ? 'Browser blocked request - verify CORS headers allow X-Idempotency-Key or check network connectivity' : undefined,
status: (error as any)?.status,
});
return { return {
data: null, data: null,
error: { message: errorMessage, status: (error as any)?.status }, error: {
message: errorMessage,
status: (error as any)?.status,
isCorsError,
},
requestId: 'unknown', requestId: 'unknown',
duration: 0, duration: 0,
attempts: attemptCount, attempts: attemptCount,

View File

@@ -38,12 +38,24 @@ export function isSupabaseConnectionError(error: unknown): boolean {
// Database connection errors (08xxx codes) // Database connection errors (08xxx codes)
if (supabaseError.code?.startsWith('08')) return true; if (supabaseError.code?.startsWith('08')) return true;
// Check message for CORS and connectivity keywords
const message = supabaseError.message?.toLowerCase() || '';
if (message.includes('cors') ||
message.includes('cross-origin') ||
message.includes('failed to send')) {
return true;
}
} }
// Network fetch errors // Network fetch errors
if (error instanceof TypeError) { if (error instanceof TypeError) {
const message = error.message.toLowerCase(); const message = error.message.toLowerCase();
if (message.includes('fetch') || message.includes('network') || message.includes('failed to fetch')) { if (message.includes('fetch') ||
message.includes('network') ||
message.includes('failed to fetch') ||
message.includes('cors') ||
message.includes('cross-origin')) {
return true; return true;
} }
} }
@@ -61,7 +73,15 @@ export const handleError = (
// Check if this is a connection error and dispatch event // Check if this is a connection error and dispatch event
if (isSupabaseConnectionError(error)) { if (isSupabaseConnectionError(error)) {
window.dispatchEvent(new CustomEvent('api-connectivity-down')); const errorMsg = getErrorMessage(error).toLowerCase();
const isCors = errorMsg.includes('cors') || errorMsg.includes('cross-origin');
window.dispatchEvent(new CustomEvent('api-connectivity-down', {
detail: {
isCorsError: isCors,
error: errorMsg,
}
}));
} }
// Enhanced error message and stack extraction // Enhanced error message and stack extraction
@@ -132,6 +152,9 @@ export const handleError = (
} }
// Log to console/monitoring with enhanced debugging // Log to console/monitoring with enhanced debugging
const isCorsError = errorMessage.toLowerCase().includes('cors') ||
errorMessage.toLowerCase().includes('cross-origin') ||
errorMessage.toLowerCase().includes('failed to send');
logger.error('Error occurred', { logger.error('Error occurred', {
...context, ...context,
@@ -144,6 +167,8 @@ export const handleError = (
hasStack: !!stack, hasStack: !!stack,
isSyntheticStack: !!(error && typeof error === 'object' && !(error instanceof Error) && stack), isSyntheticStack: !!(error && typeof error === 'object' && !(error instanceof Error) && stack),
supabaseError: supabaseErrorDetails, supabaseError: supabaseErrorDetails,
isCorsError,
debugHint: isCorsError ? 'Browser blocked request - check CORS headers or network connectivity' : undefined,
}); });
// Additional debug logging when stack is missing // Additional debug logging when stack is missing

View File

@@ -9,6 +9,7 @@ const STANDARD_HEADERS = [
'x-client-info', 'x-client-info',
'apikey', 'apikey',
'content-type', 'content-type',
'x-idempotency-key',
]; ];
// Tracing headers for distributed tracing and request tracking // Tracing headers for distributed tracing and request tracking
@@ -36,6 +37,7 @@ export const corsHeaders = {
export const corsHeadersWithTracing = { export const corsHeadersWithTracing = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': ALL_HEADERS.join(', '), 'Access-Control-Allow-Headers': ALL_HEADERS.join(', '),
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
}; };
/** /**