This commit is contained in:
tapframe 2025-10-08 17:37:46 +05:30
parent ea3fe35790
commit 69d9885e30
3 changed files with 165 additions and 14 deletions

View file

@ -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);
}
};

View file

@ -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 (
<SafeAreaView
<SafeAreaView
style={[styles.container, { backgroundColor: dynamicBackgroundColor }]}
edges={['bottom']}
>
<StatusBar translucent backgroundColor="transparent" barStyle="light-content" />
<View style={styles.errorContainer}>
<MaterialIcons name="error-outline" size={64} color={currentTheme.colors.textMuted} />
<Text style={[styles.errorText, { color: currentTheme.colors.highEmphasis }]}>
{metadataError || 'Content not found'}
<MaterialIcons name="error-outline" size={64} color={currentTheme.colors.error || '#FF6B6B'} />
<Text style={[styles.errorTitle, { color: currentTheme.colors.highEmphasis }]}>
Unable to Load Content
</Text>
<Text style={[styles.errorCode, { color: currentTheme.colors.textMuted }]}>
Error Code: {errorInfo.code}
</Text>
<Text style={[styles.errorMessage, { color: currentTheme.colors.highEmphasis }]}>
{errorInfo.userMessage}
</Text>
{__DEV__ && (
<Text style={[styles.errorDetails, { color: currentTheme.colors.textMuted }]}>
{metadataError}
</Text>
)}
<TouchableOpacity
style={[styles.retryButton, { backgroundColor: currentTheme.colors.primary }]}
onPress={loadMetadata}
@ -802,6 +892,34 @@ const styles = StyleSheet.create({
alignItems: 'center',
padding: 32,
},
errorTitle: {
fontSize: 20,
fontWeight: '700',
textAlign: 'center',
marginTop: 16,
marginBottom: 8,
},
errorCode: {
fontSize: 14,
textAlign: 'center',
marginBottom: 8,
fontFamily: 'monospace',
},
errorMessage: {
fontSize: 16,
textAlign: 'center',
marginBottom: 16,
lineHeight: 22,
},
errorDetails: {
fontSize: 12,
textAlign: 'center',
marginTop: 16,
padding: 8,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
borderRadius: 8,
fontFamily: 'monospace',
},
errorText: {
fontSize: 18,
textAlign: 'center',

View file

@ -425,7 +425,7 @@ class StremioService {
}
}
private async retryRequest<T>(request: () => Promise<T>, retries = 3, delay = 1000): Promise<T> {
private async retryRequest<T>(request: () => Promise<T>, retries = 1, delay = 1000): Promise<T> {
let lastError: any;
for (let attempt = 0; attempt < retries + 1; attempt++) {
try {