typescript - Firebase 9 隐形 reCAPTCHA 元素抛出“reCAPTCHA 客户端元素已被移除”错误
我有一个用 TypeScript 编写的 Vue 3 单页面应用,并使用 Firebase 9 版本进行身份验证。
已登录账户的用户可以使用一次性短信验证码验证电话号码,然后该电话号码将与其用户账户关联。
这些是我的可组合函数:
创建一个必需的 reCaptcha 元素并将其添加到 window:
const setInvisibleRecaptchaOnWindow = async (recaptchaId: string) => {
window.recaptchaVerifier = new RecaptchaVerifier(
recaptchaId,
{ size: 'invisible' },
auth
);
window.recaptchaWidgetId = await window.recaptchaVerifier.render();
};
重置 window 上的 reCaptcha:
const resetReCaptchaOnWindow = () => {
if (
typeof grecaptcha !== 'undefined' &&
typeof window.recaptchaWidgetId !== 'undefined'
) {
grecaptcha.reset(window.recaptchaWidgetId);
}
};
发送短信验证码:
const sendSMSOtp = async (phoneE164Standard: string, applicationVerifier: ApplicationVerifier) => {
const provider = new PhoneAuthProvider(auth);
const verificationId = await provider.verifyPhoneNumber(
phoneE164Standard,
applicationVerifier
);
return verificationId;
};
验证短信代码并将电话号码绑定到用户账户:
const verifySMSOtpAndLinkPhone = async (verificationId: string, verificationCode: string ) => {
const phoneCredential = PhoneAuthProvider.credential(
verificationId,
verificationCode
);
if (user.value && user.value.phoneNumber) {
// Remove existing number before adding a new one
await unlink(user.value, 'phone');
await linkWithCredential(user.value, phoneCredential);
} else if (user.value) {
// Add number new number if none previously existed
await linkWithCredential(user.value, phoneCredential);
}
};
在 Vue 单文件组件中使用组合函数:
我的页面有各种用于手机和验证码等的输入框,以及一个下面的 div 用来存放 reCaptcha 元素。这里是一个简化版本:
<form @submit="handlePhoneSubmit">
<input v-model="phone" type="text" placeholder="Phone Number">
<input type="submit" value="Submit">
</form>
<form @submit="handleVerificationCodeSubmit">
<input v-model="SMSOtp" type="text" placeholder="SMS Code">
<input type="submit" value="Submit">
</form>
<div id="recaptcha-container"></div>
我正在使用这个来触发 Vue SFC 页面内的组合函数。
let verificationId: string | undefined;
const handlePhoneSubmit = async () => {
resetReCaptchaOnWindow();
if (!window.recaptchaVerifier) {
await setInvisibleRecaptchaOnWindow('recaptcha-container');
}
if (window.recaptchaVerifier) {
verificationId = await sendSMSOtp(
phoneE164Standard.value,
window.recaptchaVerifier
);
}
};
const handleVerificationCodeSubmit = async () => {
await verifySMSOtpAndLinkPhone(verificationId, SMSOtp.value);
};
如果用户停留在同一页面,一切运行良好。他们可以根据需要多次更改和验证自己的电话号码。
但是如果他们导航到 SPA 的不同“页面”然后再回来尝试再次更改他们的号码,我就会收到这个错误:
reCAPTCHA client element has been removed: 0
这是一个单页应用,所以导航到其他页面不会刷新页面。它只会改变 DOM 元素。
我假设这个错误是因为 reCaptcha 是在 recaptcha-container div 上创建的,而当导航离开时,该 div 被移除了。因此返回时已经没有 reCaptcha 元素了。
我试图通过在每次调用验证函数之前使用 resetReCaptchaOnWindow() 来解决这个问题。这段代码是从这个 Firebase 快速入门示例中获取并调整的。但它没有任何作用。错误仍然会出现。
我该如何解决这个问题?
答案#1
我通过将 <div id="recaptcha-container"></div> 移动到 App.vue 来解决这个问题,这样在切换页面时它就不会被删除:
<template>
<router-view />
<div id="recaptcha-container"></div>
</template>
这里是我最终使用的可组合函数。不需要 resetReCaptchaOnWindow。
请注意,以下可组合函数都被包装在 Vue Concurrency 的 useTask 生成器中。如果你不想,也不需要这样做,但 Vue Concurrency 是一个非常酷的库,它可以在许多情况下(不仅仅是这一种)大大减少大量样板代码。
export const useSendSMSOtpTask = () => {
return useTask(function* (
signal,
phoneE164Standard: string,
recaptchaConatinerId: string
) {
if (!window.recaptchaVerifier) {
window.recaptchaVerifier = new RecaptchaVerifier(
recaptchaConatinerId,
{ size: 'invisible' },
auth
);
}
const provider = new PhoneAuthProvider(auth);
const verificationId: string = yield provider.verifyPhoneNumber(
phoneE164Standard,
window.recaptchaVerifier
);
return verificationId;
});
};
export const useVerifySMSOtpAndLinkPhoneTask = () => {
return useTask(function* (
signal,
verificationId: string,
verificationCode: string
) {
const phoneCredential = PhoneAuthProvider.credential(
verificationId,
verificationCode
);
if (auth.currentUser && auth.currentUser.phoneNumber) {
// Remove existing phone then add new phone
yield unlink(auth.currentUser, 'phone');
yield linkWithCredential(auth.currentUser, phoneCredential);
} else if (auth.currentUser) {
// Add new phone
yield linkWithCredential(auth.currentUser, phoneCredential);
}
return auth.currentUser?.phoneNumber;
});
};
