/ 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;