diff --git a/src/hooks/useMetadata.ts b/src/hooks/useMetadata.ts index cdac029d..e94b9984 100644 --- a/src/hooks/useMetadata.ts +++ b/src/hooks/useMetadata.ts @@ -16,7 +16,7 @@ import { useSettings } from './useSettings'; // Constants for timeouts and retries const API_TIMEOUT = 10000; // 10 seconds -const MAX_RETRIES = 2; +const MAX_RETRIES = 1; // Reduced since stremioService already retries const RETRY_DELAY = 1000; // 1 second // Utility function to add timeout to promises @@ -393,7 +393,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat const loadMetadata = async () => { try { if (loadAttempts >= MAX_RETRIES) { - setError('Failed to load content after multiple attempts'); + setError(`Failed to load content after ${MAX_RETRIES + 1} attempts. Please check your connection and try again.`); setLoading(false); return; } @@ -662,11 +662,40 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat const isInLib = catalogService.getLibraryItems().some(item => item.id === id); setInLibrary(isInLib); } else { - if (__DEV__) logger.warn('[loadMetadata] addon metadata:not found or failed', { status: content.status, reason: (content as any)?.reason?.message }); - throw new Error('Content not found'); + // Extract the error from the rejected promise + const reason = (content as any)?.reason; + const reasonMessage = reason?.message || String(reason); + + if (__DEV__) { + console.log('[loadMetadata] addon metadata:not found or failed', { + status: content.status, + reason: reasonMessage, + fullReason: reason + }); + } + + // Check if this was a network/server error rather than content not found + if (reasonMessage && ( + reasonMessage.includes('500') || + reasonMessage.includes('502') || + reasonMessage.includes('503') || + reasonMessage.includes('Network Error') || + reasonMessage.includes('Request failed') + )) { + // This was a server/network error, preserve the original error message + throw reason instanceof Error ? reason : new Error(reasonMessage); + } else { + // This was likely a content not found error + throw new Error('Content not found'); + } } } catch (error) { - if (__DEV__) console.error('Failed to load metadata:', error); + if (__DEV__) { + console.error('Failed to load metadata:', error); + console.log('Error message being set:', error instanceof Error ? error.message : String(error)); + } + + // Preserve the original error details for better error parsing const errorMessage = error instanceof Error ? error.message : 'Failed to load content'; setError(errorMessage); @@ -1109,7 +1138,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat } catch (error) { if (__DEV__) console.error('❌ [loadStreams] Failed to load streams:', error); - setError('Failed to load streams'); + // Preserve the original error details for better error parsing + const errorMessage = error instanceof Error ? error.message : 'Failed to load streams'; + setError(errorMessage); setLoadingStreams(false); } }; @@ -1311,7 +1342,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat } catch (error) { if (__DEV__) console.error('❌ [loadEpisodeStreams] Failed to load episode streams:', error); - setError('Failed to load episode streams'); + // Preserve the original error details for better error parsing + const errorMessage = error instanceof Error ? error.message : 'Failed to load episode streams'; + setError(errorMessage); setLoadingEpisodeStreams(false); } }; diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index 4a71492c..4789d3d8 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -595,21 +595,111 @@ const MetadataScreen: React.FC = () => { opacity: transitionOpacity.value, }), []); - // Memoized error component for performance + // Improved error component with user-friendly messages and error codes const ErrorComponent = useMemo(() => { if (!metadataError) return null; - + + // Parse error to extract code and user-friendly message + const parseError = (error: string) => { + console.log('🔍 Parsing error in MetadataScreen:', error); + + // Check for HTTP status codes - handle multiple formats + // Match patterns like: "status code 500", "status": 500, "Request failed with status code 500" + const statusCodeMatch = error.match(/status code (\d+)/) || + error.match(/"status":\s*(\d+)/) || + error.match(/Request failed with status code (\d+)/) || + error.match(/\b(\d{3})\b/); // Match any 3-digit number (last resort) + + if (statusCodeMatch) { + const code = parseInt(statusCodeMatch[1]); + console.log('✅ Found status code:', code); + switch (code) { + case 404: + return { code: '404', message: 'Content not found', userMessage: 'This content doesn\'t exist or may have been removed.' }; + case 500: + return { code: '500', message: 'Server error', userMessage: 'The server is temporarily unavailable. Please try again later.' }; + case 502: + return { code: '502', message: 'Bad gateway', userMessage: 'The server is experiencing issues. Please try again later.' }; + case 503: + return { code: '503', message: 'Service unavailable', userMessage: 'The service is currently down for maintenance. Please try again later.' }; + case 429: + return { code: '429', message: 'Too many requests', userMessage: 'You\'re making too many requests. Please wait a moment and try again.' }; + case 408: + return { code: '408', message: 'Request timeout', userMessage: 'The request took too long. Please try again.' }; + default: + return { code: code.toString(), message: `Error ${code}`, userMessage: 'Something went wrong. Please try again.' }; + } + } + + // Check for network/Axios errors + if (error.includes('Network Error') || + error.includes('ERR_BAD_RESPONSE') || + error.includes('Request failed') || + error.includes('ERR_NETWORK')) { + return { code: 'NETWORK', message: 'Network error', userMessage: 'Please check your internet connection and try again.' }; + } + + // Check for timeout errors + if (error.includes('timeout') || + error.includes('timed out') || + error.includes('ECONNABORTED') || + error.includes('ETIMEDOUT')) { + return { code: 'TIMEOUT', message: 'Request timeout', userMessage: 'The request took too long. Please try again.' }; + } + + // Check for authentication errors + if (error.includes('401') || error.includes('Unauthorized') || error.includes('authentication')) { + return { code: '401', message: 'Authentication error', userMessage: 'Please check your account settings and try again.' }; + } + + // Check for permission errors + if (error.includes('403') || error.includes('Forbidden') || error.includes('permission')) { + return { code: '403', message: 'Access denied', userMessage: 'You don\'t have permission to access this content.' }; + } + + // Check for "not found" errors - but only if no status code was found + if (!statusCodeMatch && (error.includes('Content not found') || error.includes('not found'))) { + return { code: '404', message: 'Content not found', userMessage: 'This content doesn\'t exist or may have been removed.' }; + } + + // Check for retry/attempt errors + if (error.includes('attempts') || error.includes('Please check your connection')) { + return { code: 'CONNECTION', message: 'Connection error', userMessage: 'Please check your internet connection and try again.' }; + } + + // Check for streams-related errors + if (error.includes('streams') || error.includes('Failed to load streams')) { + return { code: 'STREAMS', message: 'Streams unavailable', userMessage: 'Streaming sources are currently unavailable. Please try again later.' }; + } + + // Default case + return { code: 'UNKNOWN', message: 'Unknown error', userMessage: 'An unexpected error occurred. Please try again.' }; + }; + + const errorInfo = parseError(metadataError); + return ( - - - - {metadataError || 'Content not found'} + + + Unable to Load Content + + Error Code: {errorInfo.code} + + + {errorInfo.userMessage} + + {__DEV__ && ( + + {metadataError} + + )} (request: () => Promise, retries = 3, delay = 1000): Promise { + private async retryRequest(request: () => Promise, retries = 1, delay = 1000): Promise { let lastError: any; for (let attempt = 0; attempt < retries + 1; attempt++) { try {