/ components / CustomWebView.tsx
CustomWebView.tsx
1 import React, { useRef, useState, useEffect } from 'react'; 2 import { Platform } from 'react-native'; 3 import { 4 WebView, 5 WebViewNavigation, 6 WebViewMessageEvent, 7 } from 'react-native-webview'; 8 9 interface CustomWebViewProps extends React.ComponentProps<typeof WebView> { 10 allowedHosts?: string[]; 11 currentUrl?: string; 12 } 13 14 const CustomWebView: React.FC<CustomWebViewProps> = ({ 15 allowedHosts = ['mangafire.to'], 16 currentUrl, 17 ...props 18 }) => { 19 const webViewRef = useRef<WebView>(null); 20 const [webViewKey, setWebViewKey] = useState(1); 21 const [lastLoadedUrl, setLastLoadedUrl] = useState<string | null>(null); 22 const [isInitialLoad, setIsInitialLoad] = useState(true); 23 24 useEffect(() => { 25 if (Platform.OS === 'ios') { 26 setTimeout(() => setWebViewKey((key) => key + 1), 50); 27 } 28 }, []); 29 30 const preventHorizontalScrollJS = ` 31 (function() { 32 document.body.style.overflowX = 'hidden'; 33 window.addEventListener('touchstart', function(e) { 34 this.start_x = e.changedTouches[0].clientX; 35 this.start_y = e.changedTouches[0].clientY; 36 }, { passive: false }); 37 38 window.addEventListener('touchmove', function(e) { 39 var end_x = e.changedTouches[0].clientX; 40 var end_y = e.changedTouches[0].clientY; 41 var delta_x = Math.abs(end_x - this.start_x); 42 var delta_y = Math.abs(end_y - this.start_y); 43 44 // Check if zoomed in 45 var zoomLevel = document.body.offsetWidth / window.innerWidth; 46 47 if (zoomLevel <= 1 && delta_x > delta_y) { 48 e.preventDefault(); 49 } 50 }, { passive: false }); 51 })(); 52 `; 53 54 const preventRedirectsJS = ` 55 (function() { 56 window.addEventListener('load', function() { 57 window.ReactNativeWebView.postMessage(JSON.stringify({ 58 type: 'PAGE_LOADED', 59 url: window.location.href 60 })); 61 }); 62 63 // Override location changes 64 const originalPushState = history.pushState; 65 const originalReplaceState = history.replaceState; 66 67 history.pushState = function() { 68 window.ReactNativeWebView.postMessage(JSON.stringify({ 69 type: 'HISTORY_PUSH', 70 url: arguments[2] 71 })); 72 return originalPushState.apply(this, arguments); 73 }; 74 75 history.replaceState = function() { 76 window.ReactNativeWebView.postMessage(JSON.stringify({ 77 type: 'HISTORY_REPLACE', 78 url: arguments[2] 79 })); 80 return originalReplaceState.apply(this, arguments); 81 }; 82 })(); 83 `; 84 85 const handleNavigationStateChange = (navState: WebViewNavigation) => { 86 const isAllowedHost = allowedHosts.some((host) => 87 navState.url.toLowerCase().includes(host.toLowerCase()) 88 ); 89 90 // If this is the initial load or it's an allowed navigation from the app, allow it 91 if (isInitialLoad || navState.url === currentUrl) { 92 setIsInitialLoad(false); 93 setLastLoadedUrl(navState.url); 94 props.onNavigationStateChange?.(navState); 95 return; 96 } 97 98 // If it's trying to load a URL we've already loaded, prevent the loop 99 if (navState.url === lastLoadedUrl) { 100 return; 101 } 102 103 // If it's not an allowed host, block it 104 if (!isAllowedHost && webViewRef.current) { 105 console.log('Blocking navigation to:', navState.url); 106 webViewRef.current.stopLoading(); 107 if (lastLoadedUrl) { 108 webViewRef.current.injectJavaScript(` 109 window.location.replace('${lastLoadedUrl}'); 110 true; 111 `); 112 } 113 return; 114 } 115 116 // If we get here, it's a new allowed navigation 117 setLastLoadedUrl(navState.url); 118 props.onNavigationStateChange?.(navState); 119 }; 120 121 const handleMessage = (event: WebViewMessageEvent) => { 122 try { 123 const data = JSON.parse(event.nativeEvent.data); 124 console.log('WebView message:', data); 125 126 switch (data.type) { 127 case 'PAGE_LOADED': 128 setLastLoadedUrl(data.url); 129 break; 130 } 131 132 props.onMessage?.(event); 133 } catch { 134 props.onMessage?.(event); 135 } 136 }; 137 138 return ( 139 <WebView 140 ref={webViewRef} 141 key={webViewKey} 142 {...props} 143 onNavigationStateChange={handleNavigationStateChange} 144 onMessage={handleMessage} 145 injectedJavaScript={` 146 ${preventHorizontalScrollJS} 147 ${preventRedirectsJS} 148 ${props.injectedJavaScript || ''} 149 true; 150 `} 151 {...(Platform.OS === 'android' ? { 152 userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' 153 } : props.userAgent ? { userAgent: props.userAgent } : {})} 154 sharedCookiesEnabled={false} 155 thirdPartyCookiesEnabled={false} 156 javaScriptCanOpenWindowsAutomatically={false} 157 allowsBackForwardNavigationGestures={false} 158 allowsLinkPreview={false} 159 incognito={true} 160 /> 161 ); 162 }; 163 164 export default CustomWebView;