cloudflare.tsx
1 import React, { useState } from 'react'; 2 import { 3 View, 4 Text, 5 TouchableOpacity, 6 StyleSheet, 7 ActivityIndicator, 8 Platform, 9 useColorScheme, 10 } from 'react-native'; 11 import { useTheme } from '@/constants/ThemeContext'; 12 import { Colors, ColorScheme } from '@/constants/Colors'; 13 import { useRouter } from 'expo-router'; 14 import { MANGA_API_URL } from '@/constants/Config'; 15 import CustomWebView from '@/components/CustomWebView'; 16 import { Ionicons } from '@expo/vector-icons'; 17 import { WebViewNavigation } from 'react-native-webview'; 18 19 import { useCloudflareDetection } from '@/hooks/useCloudflareDetection'; 20 21 export default function CloudflarePage() { 22 const router = useRouter(); 23 const { theme } = useTheme(); 24 const systemColorScheme = useColorScheme() as ColorScheme; 25 const colorScheme = 26 theme === 'system' ? systemColorScheme : (theme as ColorScheme); 27 const colors = Colors[colorScheme]; 28 const styles = getStyles(colors); 29 30 const [isLoading, setIsLoading] = useState(true); 31 const [error, setError] = useState<string | null>(null); 32 const [verificationComplete, setVerificationComplete] = useState(false); 33 34 const handleLoadEnd = () => { 35 setIsLoading(false); 36 }; 37 38 const handleError = () => { 39 setError('Failed to load verification page. Please try again.'); 40 setIsLoading(false); 41 }; 42 43 const { handleVerificationComplete } = useCloudflareDetection(); 44 45 const handleNavigationStateChange = (navState: WebViewNavigation) => { 46 // Check if we're no longer on a Cloudflare page 47 if ( 48 !navState.url.includes('cf-browser-verification') && 49 !navState.url.includes('cf_captcha_kind') 50 ) { 51 setVerificationComplete(true); 52 handleVerificationComplete(); // This will route back to the previous page 53 } 54 }; 55 56 const handleBackPress = () => { 57 router.back(); 58 }; 59 60 const handleRetry = () => { 61 setError(null); 62 setIsLoading(true); 63 setVerificationComplete(false); 64 }; 65 66 // Inject JavaScript to modify the page appearance and remove unnecessary elements 67 const injectedJavaScript = ` 68 document.body.style.backgroundColor = '${colors.background}'; 69 document.body.style.color = '${colors.text}'; 70 const elements = document.querySelectorAll('header, footer, nav, aside'); 71 elements.forEach(element => element.style.display = 'none'); 72 true; 73 `; 74 75 return ( 76 <View style={styles.container}> 77 {isLoading && ( 78 <View style={styles.loadingContainer}> 79 <ActivityIndicator size="large" color={colors.primary} /> 80 </View> 81 )} 82 83 {error ? ( 84 <View style={styles.errorContainer}> 85 <Text style={styles.errorText}>{error}</Text> 86 <TouchableOpacity style={styles.retryButton} onPress={handleRetry}> 87 <Text style={styles.retryButtonText}>Retry</Text> 88 </TouchableOpacity> 89 </View> 90 ) : ( 91 <> 92 <CustomWebView 93 source={{ uri: MANGA_API_URL }} 94 style={styles.webView} 95 onLoadEnd={handleLoadEnd} 96 onError={handleError} 97 injectedJavaScript={injectedJavaScript} 98 allowedHosts={['mangafire.to']} 99 javaScriptEnabled={true} 100 domStorageEnabled={true} 101 onNavigationStateChange={handleNavigationStateChange} 102 decelerationRate={Platform.OS === 'ios' ? 'normal' : 0.98} 103 /> 104 105 <TouchableOpacity style={styles.backButton} onPress={handleBackPress}> 106 <Ionicons name="chevron-back" size={24} color={colors.text} /> 107 </TouchableOpacity> 108 109 {verificationComplete && ( 110 <View style={styles.completeBanner}> 111 <Text style={styles.completeText}>Verification Complete!</Text> 112 <TouchableOpacity 113 style={styles.continueButton} 114 onPress={handleBackPress} 115 > 116 <Text style={styles.continueButtonText}>Continue</Text> 117 </TouchableOpacity> 118 </View> 119 )} 120 </> 121 )} 122 </View> 123 ); 124 } 125 126 const getStyles = (colors: typeof Colors.light) => 127 StyleSheet.create({ 128 container: { 129 flex: 1, 130 backgroundColor: colors.background, 131 }, 132 webView: { 133 flex: 1, 134 }, 135 loadingContainer: { 136 position: 'absolute', 137 left: 0, 138 right: 0, 139 top: 0, 140 bottom: 0, 141 alignItems: 'center', 142 justifyContent: 'center', 143 backgroundColor: colors.background, 144 }, 145 errorContainer: { 146 flex: 1, 147 justifyContent: 'center', 148 alignItems: 'center', 149 padding: 20, 150 }, 151 errorText: { 152 fontSize: 18, 153 textAlign: 'center', 154 color: colors.error, 155 marginBottom: 20, 156 }, 157 retryButton: { 158 backgroundColor: colors.primary, 159 paddingHorizontal: 32, 160 paddingVertical: 12, 161 borderRadius: 8, 162 }, 163 retryButtonText: { 164 color: colors.card, 165 fontSize: 16, 166 fontWeight: 'bold', 167 }, 168 backButton: { 169 position: 'absolute', 170 top: 50, 171 left: 10, 172 zIndex: 1000, 173 backgroundColor: 'rgba(0, 0, 0, 0.3)', 174 borderRadius: 20, 175 padding: 8, 176 }, 177 completeBanner: { 178 position: 'absolute', 179 bottom: 0, 180 left: 0, 181 right: 0, 182 backgroundColor: colors.card, 183 padding: 20, 184 alignItems: 'center', 185 borderTopLeftRadius: 15, 186 borderTopRightRadius: 15, 187 shadowColor: '#000', 188 shadowOffset: { 189 width: 0, 190 height: -2, 191 }, 192 shadowOpacity: 0.25, 193 shadowRadius: 3.84, 194 elevation: 5, 195 }, 196 completeText: { 197 color: colors.primary, 198 fontSize: 18, 199 fontWeight: 'bold', 200 marginBottom: 10, 201 }, 202 continueButton: { 203 backgroundColor: colors.primary, 204 paddingHorizontal: 32, 205 paddingVertical: 12, 206 borderRadius: 8, 207 minWidth: 200, 208 }, 209 continueButtonText: { 210 color: colors.card, 211 fontSize: 16, 212 fontWeight: 'bold', 213 textAlign: 'center', 214 }, 215 });