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
相关产品推荐
相关产品推荐

