admin管理员组

文章数量:1389825

WebView JavaScript execution started VM11913:31 WebView JavaScript execution started VM11915:31 WebView JavaScript execution started ...

I've tried removing the injected JavaScript code that logs these messages, yet the issue persists. Here are some key points regarding my setup:

  1. State Updates & Navigation:

    • I update the WebView's source based on a state variable (e.g., currentUrl).

    • In the onNavigationStateChange event, I compare the current URL with the new URL.

    • However, even when the URLs are the same (or only differ in minor ways like a trailing slash), the WebView reloads repeatedly.

  2. Injected JavaScript:

    • The injected JavaScript logs "WebView JavaScript execution started" and sets up event listeners for messages.

    • Since removing it doesn’t stop the reload, it seems that the code is being re-injected every time the WebView reloads, indicating that the reload is triggered elsewhere.

  3. Other Considerations:

    • I'm using NetInfo to monitor network state, and some useEffect hooks that might be causing unnecessary re-renders.

    • I also attempted to normalize the URL before updating the state, but the problem remains.

What I've tried so far:

  • Removing the injected JavaScript (the reload still occurs).

  • Using console.trace() in onLoadStart and onNavigationStateChange to inspect the call stack.

  • Debugging with React Native Debugger, but I still can’t pinpoint the exact trigger.

import React, { useEffect, useRef, useState } from 'react';
import { check, request, PERMISSIONS, RESULTS, openSettings } from 'react-native-permissions';
import PermissionScreen from "./PermissionScreen";
import NoInternetScreen from "./NoInternetScreen.tsx";
import LocationPermissionModal from "./LocationPermissionModal.tsx";
import AsyncStorage from '@react-native-async-storage/async-storage';
import NetInfo from "@react-native-community/netinfo";

import {
  ActivityIndicator,
  StyleSheet,
  BackHandler,
  ToastAndroid,
  StatusBar,
  PermissionsAndroid,
  Platform,
  Alert,
  View,
  Text
} from 'react-native';
import { WebView } from 'react-native-webview';
import Geolocation from '@react-native-community/geolocation';

const App: React.FC = () => {
  const [isPermissionGranted, setIsPermissionGranted] = useState(false);
  const [isWebViewLoaded, setIsWebViewLoaded] = useState(false);
  const [isPageLoaded, setIsPageLoaded] = useState(false);
  const [isFirstLaunch, setIsFirstLaunch] = useState(true);
  const webviewRef = useRef<WebView>(null);
  // Replace sensitive URL information with example
  const [currentUrl, setCurrentUrl] = useState('/');
  const [checkUrl, setCheckUrl] = useState('/');
  const [isLocationDenied, setIsLocationDenied] = useState(false);
  const [barStyle, setBarStyle] = useState<'dark-content' | 'light-content'>('dark-content');

  // Handler for navigation state change
  const handleNavigationStateChange = (navState: any) => {
    if (!navState) return;
    const url = navState;
    if (url.includes('mypage') || url.includes('dark-theme')) {
      console.log("Detected dark background -> changing status bar text to light");
      setBarStyle('light-content');
    } else {
      console.log("Detected light background -> changing status bar text to dark");
      setBarStyle('dark-content');
    }
  };

  const userAgent = (() => {
    if (Platform.OS === "ios") {
      const iosVersion = parseFloat(Platform.Version.toString());
      return iosVersion < 16
        ? "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1"
        : "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1";
    }
    return "MyAppWebView";
  })();

  let isLocationSent = false; // To ensure location data is sent only once

  const sendLocationToWebView = async () => {
    console.log("Requesting location data.");
    const granted = await checkLocationPermission();
    if (granted !== true) {
      console.warn("Location permission denied!");
      setIsLocationDenied(true);
      const jsCode = `
        if (window.receiveLocationFromReactNative) {
          window.receiveLocationFromReactNative(${JSON.stringify({
            type: "ERROR",
            reason: "LOCATION_DENIED"
          })});
        }
      `;
      webviewRef.current?.injectJavaScript(jsCode);
      return;
    }

    Geolocation.getCurrentPosition(
      (position) => {
        const { latitude, longitude } = position.coords;
        const locationData = {
          type: "LOCATION_DATA",
          latitude,
          longitude
        };

        console.log("Location data generated in React Native:", JSON.stringify(locationData));
        const jsCode = `
          if (window.receiveLocationFromReactNative) {
            window.receiveLocationFromReactNative(${JSON.stringify(locationData)});
          }
        `;
        webviewRef.current?.injectJavaScript(jsCode);
        setIsLocationDenied(false);
        isLocationSent = true;
      },
      (error) => {
        console.warn("Error obtaining location data:", error);
        if (Platform.OS === "ios") {
          Alert.alert(
            "Location Service Needed",
            "Please enable location services in your iPhone settings to get your location.",
            [{ text: "Go to Settings", onPress: () => openSettings() }, { text: "Cancel", style: "cancel" }]
          );
        }

        setIsLocationDenied(true);
        isLocationSent = false;
        const jsCode = `
          if (window.receiveLocationFromReactNative) {
            window.receiveLocationFromReactNative(${JSON.stringify({
              type: "ERROR",
              reason: "LOCATION_DENIED"
            })});
          }
        `;
        webviewRef.current?.injectJavaScript(jsCode);
      },
      { enableHighAccuracy: true, timeout: 5000 }
    );
  };

  const [isInternetConnected, setIsInternetConnected] = useState<boolean | null>(true);

  useEffect(() => {
    console.log('1');
    const checkInternet = async () => {
      try {
        // Send a HEAD request to a public server (e.g., Google) to check for network connectivity
        const response = await fetch(";, { method: "HEAD" });
        if (response.ok) {
          console.log("Internet connection is normal");
          setIsInternetConnected(true);
        } else {
          console.log("Internet connection is down (non-normal response)");
          setIsInternetConnected(false);
        }
      } catch (error) {
        console.log("Internet connection is down (request failed)");
        setIsInternetConnected(false);
      }
    };

    // Listen for network status changes using NetInfo
    const unsubscribe = NetInfo.addEventListener((state) => {
      console.log("Detected network state change:", state.isConnected);
      if (state.isConnected === false) {
        setIsInternetConnected(false);
      } else {
        checkInternet();
      }
    });

    return () => unsubscribe();
  }, []);

  const checkNotificationPermission = async () => {
    await checkNotifications()
      .then((status) => {
        setDeviceNotiCheck(status);
      })
      .catch((error) => console.log('checkNotifications', error));
  };

  useEffect(() => {
    checkNotificationPermission();
  }, []);

  const hasRequestedPermission = useRef(false);

  const checkLocationPermission = async () => {
    if (Platform.OS === "ios") {
      console.log("Detected iOS environment!");
      const status = await check(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE);
      console.log("iOS location permission status:", status);

      if (status === RESULTS.GRANTED) {
        console.log("Location permission is already granted");
        return true;
      }

      if (status === RESULTS.UNAVAILABLE || status === RESULTS.BLOCKED) {
        console.warn("Location permission is unavailable or blocked -> treating as LOCATION_DENIED");
        return "LOCATION_DENIED";
      }

      if (status === RESULTS.DENIED) {
        if (hasRequestedPermission.current) {
          console.warn("Permission already requested, stopping further requests.");
          const jsCode = `
            if (window.receiveLocationFromReactNative) {
              window.receiveLocationFromReactNative(${JSON.stringify({
                type: "ERROR",
                reason: "LOCATION_ALREADY_REQUESTED"
              })});
            }
          `;
          webviewRef.current?.injectJavaScript(jsCode);
          return "LOCATION_ALREADY_REQUESTED";
        }

        console.log("Location permission denied. Requesting again (only once)!");
        hasRequestedPermission.current = true;
        const newStatus = await request(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE);
        console.log("New iOS location permission status:", newStatus);
        return newStatus === RESULTS.GRANTED ? true : "LOCATION_DENIED";
      }
    }

    if (Platform.OS === "android") {
      const result = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
      console.log("Android location permission status:", result);

      if (result) return true;
      if (hasRequestedPermission.current) {
        console.warn("Permission already requested, stopping further requests.");
        const jsCode = `
          if (window.receiveLocationFromReactNative) {
            window.receiveLocationFromReactNative(${JSON.stringify({
              type: "ERROR",
              reason: "LOCATION_ALREADY_REQUESTED"
            })});
          }
        `;
        webviewRef.current?.injectJavaScript(jsCode);
        return "LOCATION_ALREADY_REQUESTED";
      }

      hasRequestedPermission.current = true;
      const requestResult = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
      console.log("New Android location permission status:", requestResult);
      return requestResult === PermissionsAndroid.RESULTS.GRANTED ? true : "LOCATION_DENIED";
    }

    console.warn("Running on an unknown platform!");
    return false;
  };

  const handleMessage = async (event: any) => {
    const rawMessage = event.nativeEvent.data;
    console.log("Received message from WebView: -------------------", rawMessage);

    let message;

    try {
      message = JSON.parse(rawMessage);
    } catch (error) {
      message = rawMessage; // If JSON parsing fails, use the raw message
    }

    if (message.type === "LOCATIONERROR" && message.reason === "LOCATION_DENIED") {
      console.warn("Location permission denied -> prompting user to go to settings");
      return;
    }

    if (message === "REQ:LOCATION") {
      console.log("Received location request from WebView");
      const granted = await checkLocationPermission();

      if (granted) {
        console.log("Location permission granted -> sending location data");
        sendLocationToWebView();
      } else {
        console.warn("Location permission denied -> displaying settings modal");
        const jsCode = `
          if (window.receiveLocationFromReactNative) {
            window.receiveLocationFromReactNative(${JSON.stringify({
              type: "ERROR",
              reason: "LOCATION_DENIED"
            })});
          }
        `;
        webviewRef.current?.injectJavaScript(jsCode);
        setIsLocationDenied(true);
      }
    } else if (typeof message === "string" && message.startsWith(";)) {
      console.log("Received Kakao auth URL. Opening in WebView");
      setCurrentUrl(message);
    } else if (typeof message === "string" && message.startsWith(";)) {
      console.log("Received Naver auth URL. Opening in WebView");
      setCurrentUrl(message);
    } else if (typeof message === "string" && message.startsWith(";)) {
      console.log("Received Google auth URL. Opening in WebView");
      setCurrentUrl(message);
    } else if (typeof message === "string" && message.startsWith(";)) {
      console.log("Received Apple auth URL. Opening in WebView");
      setCurrentUrl(message);
    } else if (message === 'REQ:LOGIN') {
      console.log("Received login request from WebView");
      initiateKakaoLogin();
    } else if (typeof message === "object" && message.type === "REQ:PASS_AUTH") {
      console.log("Received REQ:PASS_AUTH message:", message);
      await initiatePassAuth(message.token);
    } else if (typeof message === "object" && message.type === "APPLE_LOGIN" && message.status === "FAILED") {
      console.error("Detected Apple login failure:", message);
      Alert.alert("Login Error", "Apple login failed. Please try again.");
      setTimeout(() => {
        webviewRef.current?.injectJavaScript(`window.location.href = '/'`);
      }, 2000);
    } else {
      console.log("Unknown message:", message);
    }
  };

  const initiatePassAuth = async (token: string) => {
    try {
      const apiUrl = ";;
      const response = await fetch(apiUrl, {
        method: "POST",
        headers: {
          Authorization: token,
          "Content-Type": "application/json",
        },
      });

      if (response.ok) {
        const data = await response.json();
        console.log("PASS API response successful:", data);
        const { enc_data, integrity_value, token_version_id } = data.context;
        const message = {
          type: "PASS_AUTH_SUCCESS",
          data: { enc_data, integrity_value, token_version_id },
        };

        if (webviewRef.current) {
          webviewRef.current.postMessage(JSON.stringify(message));
          console.log("Sent PASS data to WebView:", message);
        } else {
          console.error("WebView is not ready.");
        }
      } else {
        console.error("PASS API response failed:", response.status);
      }
    } catch (error) {
      console.error("Error during PASS API call:", error);
    }
  };

  const initiateKakaoLogin = () => {
    const kakaoAuthUrl = ``;
    fetch(kakaoAuthUrl, { method: 'GET' })
      .then(response => response.json())
      .then(data => {
        console.log("Login successful:", data);
        const jsCode = `
          window.handleLoginSuccess && window.handleLoginSuccess(${JSON.stringify(data)});
        `;
        webviewRef.current?.injectJavaScript(jsCode);
      })
      .catch(error => {
        console.error("Error during login request:", error);
        Alert.alert("Login Error", "There was a problem with the login request.");
      });
  };

  const [backPressCount, setBackPressCount] = useState(0);
  const [canGoBack, setCanGoBack] = useState(false);

  useEffect(() => {
    const backAction = () => {
      if (backPressCount === 0) {
        webviewRef.current.goBack();
        setBackPressCount(1);
        ToastAndroid.show("Press back again to exit.", ToastAndroid.SHORT);
        setTimeout(() => setBackPressCount(0), 2000);
        return true;
      }
      if (backPressCount === 1) {
        BackHandler.exitApp();
        return true;
      }
      return false;
    };

    const backHandler = BackHandler.addEventListener("hardwareBackPress", backAction);
    return () => backHandler.remove();
  }, [backPressCount, canGoBack]);

  const handleOAuthRedirect = async (url: string) => {
    console.log("Detected OAuth Redirect:", url);
    if (url.startsWith(";)) {
      console.log("SNS login completion URL detected!");
      const params = new URL(url).searchParams;
      const encodedData = params.get("data");
      if (encodedData) {
        const decodedData = decodeURIComponent(encodedData);
        console.log("OAuth login success data:", decodedData);
        const jsCode = `
          if (window.handleOAuthSuccess) {
            window.handleOAuthSuccess(${decodedData});
          }
        `;
        webviewRef.current?.injectJavaScript(jsCode);
      }
    }
  };

  const [shouldUseSafeArea, setShouldUseSafeArea] = useState(false);

  useEffect(() => {
    console.log("Detected current URL change:", currentUrl);
    const safeUrls = [
      ";,
      ";,
      ";,
      "/",
      ";,
      ";,
      ";
    ];

    const isSafe = safeUrls.some(pattern => checkUrl.includes(pattern));
    console.log("Applying Safe Area:", isSafe);
    setShouldUseSafeArea(isSafe);
  }, [checkUrl]);

  useEffect(() => {
    checkFirstLaunch();
  }, []);

  const checkFirstLaunch = async () => {
    const firstLaunch = await AsyncStorage.getItem("first_launch");
    if (firstLaunch === "done") {
      setIsFirstLaunch(false);
    } else {
      setIsFirstLaunch(true);
    }
  };

  const handlePermissionsGranted = async () => {
    console.log("isFirstLaunch", isFirstLaunch);
    await AsyncStorage.setItem("first_launch", "done");
    setIsFirstLaunch(false);
  };

  const clearWebViewCache = () => {
    if (webviewRef.current) {
      webviewRef.current.clearCache(true);
      console.log("WebView cache cleared!");
    }
  };

  const prevNavState = useRef<string | null>(null);

  return (
    <View style={[styles.container, shouldUseSafeArea && styles.safeAreaContainer]}>
      <StatusBar
        hidden={false}
        translucent={true}
        backgroundColor="transparent"
        barStyle={barStyle}
      />
      {(!isWebViewLoaded || !isPageLoaded) && (
        <View style={styles.splashScreen}>
          <ActivityIndicator size="large" color="#0000ff" />
        </View>
      )}
      {isInternetConnected === true ? (
        isFirstLaunch === true ? (
          <PermissionScreen onPermissionsGranted={handlePermissionsGranted} />
        ) : (
          <>
            {isLocationDenied === true && (
              <LocationPermissionModal
                visible={isLocationDenied}
                onClose={() => setIsLocationDenied(false)}
              />
            )}
            <WebView
              onLoadStart={() => {
                console.log("

本文标签: reactjsInfinite Reloading of React Native WebView – How to Trace the TriggerStack Overflow