You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native跨平台应用Firebase手机号认证失败求助

React Native Firebase手机号认证错误修复方案

错误原因

你遇到的auth/operation-not-supported-in-this-environment错误,是因为使用了Web版Firebase Auth的RecaptchaVerifier——React Native环境没有Web端的location对象和Web Storage,完全不兼容这个类。你已经导入了expo-firebase-recaptcha的FirebaseRecaptchaVerifierModal,但没正确用它替代Web版验证器。

修复步骤

1. 移除Web版RecaptchaVerifier代码

删掉newRecaptchaVerifier函数,以及所有基于firebase.auth.RecaptchaVerifier的逻辑。

2. 添加FirebaseRecaptchaVerifierModal到组件

在返回的JSX中加入这个modal,它会在React Native环境提供符合要求的人机验证能力。

3. 修改手机号验证逻辑

用modal提供的验证器调用signInWithPhoneNumber,同时保存verificationId用于后续验证码校验。

4. 完善验证码验证组件

实现Signup_phone_otp组件的输入与验证逻辑,结合PhoneAuthProvider.credential和signInWithCredential完成认证。

修改后的完整代码

import React, { useState, useEffect, useRef } from "react";
import {
  View,
  StyleSheet,
  SafeAreaView,
  Text,
  TouchableOpacity,
  FlatList,
  TextInput,
  Alert
} from "react-native";
import { LinearGradient } from "expo-linear-gradient";
import {
  sharedStyles,
  windowWidth,
  windowHeight,
} from "../../assets/shared/styles.js";
import Signup_email from "./signup_email.js";
import Signup_email_verify from "./signup_email_verify.js";
import Signup_phone from "./signup_phone.js";
import { firebaseConfig } from "../../config/config.js";
import firebase from "firebase/compat/app";
import firestore from "@react-native-firebase/firestore";
import {
  createUserWithEmailAndPassword,
  sendEmailVerification,
  getAuth,
  PhoneAuthProvider,
  signInWithPhoneNumber,
  signInWithCredential
} from "firebase/auth";
import { FirebaseRecaptchaVerifierModal } from "expo-firebase-recaptcha";

const PaginationDot = ({ index, activeIndex }) => {
  return (
    <View style={[styles.dot, index === activeIndex && styles.activeDot]} />
  );
};

// 实现Signup_phone_otp组件(原代码为空,补充逻辑)
const Signup_phone_otp = ({ verificationId, onVerifyCode }) => {
  const [code, setCode] = useState('');

  const handleVerify = () => {
    if (!code.trim()) {
      Alert.alert('请输入6位验证码');
      return;
    }
    onVerifyCode(code);
  };

  return (
    <View style={{marginTop: windowHeight * 0.1}}>
      <TextInput
        style={{
          borderBottomWidth: 1,
          borderColor: '#ccc',
          fontSize: 20,
          padding: 10,
          marginBottom: 20,
          width: windowWidth * 0.8
        }}
        placeholder="输入验证码"
        keyboardType="number-pad"
        maxLength={6}
        value={code}
        onChangeText={setCode}
      />
      <TouchableOpacity onPress={handleVerify}>
        <LinearGradient
          colors={["#5899E2", "#12B3CA"]}
          style={[sharedStyles.button, { width: windowWidth * 0.8 }]}
          start={{ x: 0, y: 0.5 }}
          end={{ x: 1, y: 0.5 }}
        >
          <Text style={sharedStyles.buttonText}>验证验证码</Text>
        </LinearGradient>
      </TouchableOpacity>
    </View>
  );
};

export default function Signup_base() {
  const [activeSlide, setActiveSlide] = useState(0);
  const [isEmailValid, setIsEmailValid] = useState(false);
  const [userEmail, setUserEmail] = useState("");
  const [authUser, setAuthUser] = useState(null);
  const [userPhone, setUserPhone] = useState(null);
  const [verificationId, setVerificationId] = useState(null);
  const auth = getAuth();
  const recaptchaVerifierRef = useRef(null);

  const handleEmailEntered = (email) => {
    setUserEmail(email);
  };

  const handlePhoneEntered = (phone) => {
    setUserPhone(phone);
  };

  const handleEmailValidated = (isValid) => {
    setIsEmailValid(isValid);
  };

  const data = Array.from({ length: 5 }, (_, index) => ({
    key: `Slide ${index + 1}`,
  }));

  useEffect(() => {
    const checkEmailVerification = async () => {
      const user = auth.currentUser;

      if (user) {
        await user.reload();

        if (user.emailVerified && activeSlide === 1) {
          setAuthUser(user);
          setActiveSlide((prev) => (prev + 1) % data.length);
        }
      }
    };

    const intervalId = setInterval(checkEmailVerification, 1000);
    return () => clearInterval(intervalId);
  }, [activeSlide]);

  const getSlideComponent = (index) => {
    switch (index) {
      case 0:
        return (
          <Signup_email
            onEmailEntered={handleEmailEntered}
            onEmailValidated={handleEmailValidated}
          />
        );
      case 1:
        return <Signup_email_verify email={userEmail} />;
      case 2:
        return <Signup_phone onPhoneEntered={handlePhoneEntered} />;
      case 3:
        return (
          <Signup_phone_otp 
            verificationId={verificationId} 
            onVerifyCode={handleVerifyCode} 
          />
        );
      case 4:
        return <Signup_password />;
      default:
        return <></>;
    }
  };

  const handleSignup_email = async () => {
    if (!userEmail.trim() || !isEmailValid) {
      Alert.alert(!userEmail.trim() ? "请输入有效邮箱" : "邮箱格式无效");
      return;
    }

    try {
      const tempPassword = Math.random().toString(36).slice(-8);
      const { user } = await createUserWithEmailAndPassword(
        auth,
        userEmail,
        tempPassword
      );

      await sendEmailVerification(user);
      setAuthUser(user);
      setActiveSlide((prev) => (prev + 1) % data.length);
    } catch (error) {
      if (error.code === "auth/email-already-in-use") {
        Alert.alert("该邮箱已注册,请更换邮箱");
      } else {
        console.error("创建用户失败:", error.message);
        Alert.alert("创建用户失败", error.message);
      }
    }
  };

  const handleSignup_email_verification = async () => {
    const user = auth.currentUser;
    if (user) {
      await user.reload();
      await sendEmailVerification(user);
      Alert.alert("验证邮件已重新发送");
    }
  };

  const handleSignup_phone = async () => {
    if (!userPhone.trim()) {
      Alert.alert("请输入有效手机号");
      return;
    }

    try {
      const confirmation = await signInWithPhoneNumber(
        auth,
        userPhone,
        recaptchaVerifierRef.current
      );
      setVerificationId(confirmation.verificationId);
      setActiveSlide((prev) => (prev + 1) % data.length);
    } catch (error) {
      console.error("发送验证码失败:", error.message);
      Alert.alert("发送验证码失败", error.message);
    }
  };

  const handleVerifyCode = async (code) => {
    if (!verificationId || !code.trim()) return;

    try {
      const credential = PhoneAuthProvider.credential(verificationId, code);
      await signInWithCredential(auth, credential);
      Alert.alert("手机号验证成功");
      setActiveSlide((prev) => (prev + 1) % data.length);
    } catch (error) {
      console.error("验证码验证失败:", error.message);
      Alert.alert("验证码错误或已过期");
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      {/* 加入Recaptcha验证模态框 */}
      <FirebaseRecaptchaVerifierModal
        ref={recaptchaVerifierRef}
        firebaseConfig={firebaseConfig}
        title="完成人机验证"
      />

      <View>
        <View style={styles.paginationContainer}>
          <FlatList
            data={data}
            horizontal
            showsHorizontalScrollIndicator={false}
            pagingEnabled
            keyExtractor={(item, index) => index.toString()}
            renderItem={({ index }) => (
              <PaginationDot index={index} activeIndex={activeSlide} />
            )}
          />
        </View>
      </View>

      {getSlideComponent(activeSlide)}

      <TouchableOpacity
        onPress={() => {
          if (activeSlide === 0) handleSignup_email();
          else if (activeSlide === 1) handleSignup_email_verification();
          else if (activeSlide === 2) handleSignup_phone();
        }}
        disabled={activeSlide === 3}
      >
        <LinearGradient
          colors={["#5899E2", "#12B3CA"]}
          style={[
            sharedStyles.button, 
            { 
              width: windowWidth * 0.8,
              opacity: activeSlide === 3 ? 0.5 : 1
            }
          ]}
          start={{ x: 0, y: 0.5 }}
          end={{ x: 1, y: 0.5 }}
        >
          {activeSlide !== 1 && <Text style={sharedStyles.buttonText}>Next</Text>}
          {activeSlide === 1 && <Text style={sharedStyles.buttonText}>重新发送验证邮件</Text>}
        </LinearGradient>
      </TouchableOpacity>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "flex-start",
    flexDirection: "column",
    alignItems: "center",
  },
  paginationContainer: {
    flexDirection: "row",
    justifyContent: "center",
    alignItems: "center",
    marginTop: windowHeight * 0.02,
  },
  dot: {
    width: 15,
    height: 15,
    borderRadius: 50,
    backgroundColor: "#BDBDBD",
    marginHorizontal: windowWidth * 0.03,
    alignSelf: "center",
  },
  activeDot: {
    backgroundColor: "#2196F3",
    width: 20,
    height: 20,
    alignSelf: "center",
  },
});

关键修改说明

  • 移除Web版RecaptchaVerifier,改用FirebaseRecaptchaVerifierModal适配React Native环境
  • 补充Signup_phone_otp组件的验证码输入与验证逻辑
  • 调整handleSignup_phone函数,使用正确的验证器调用signInWithPhoneNumber并保存verificationId
  • 添加handleVerifyCode函数完成验证码校验
  • 禁用验证码步骤的底部Next按钮,验证逻辑移至组件内部处理

内容的提问来源于stack exchange,提问作者JC Lim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 22:33:10