feat: 代理内网白名单(proxy_allowed_hosts CIDR)+ fix: Cloudflare 验证码首次加载卡死/登录注册失败后验证码状态重置
This commit is contained in:
@@ -31,10 +31,23 @@ export function showCaptcha(action) {
|
||||
});
|
||||
}
|
||||
|
||||
// 执行并清空排队中的渲染回调(供 onload 全局与竞态兜底共用)
|
||||
function flushCallbacks(type) {
|
||||
const key = type === 'recaptcha' ? 'recaptchaCallbacks' : 'turnstileCallbacks';
|
||||
const q = window[key] || [];
|
||||
window[key] = [];
|
||||
q.forEach((cb) => { try { cb(); } catch { /* 单条失败不影响其余 */ } });
|
||||
}
|
||||
|
||||
// 加载第三方验证码脚本(recaptcha / turnstile)
|
||||
// 注意:脚本通过 ?onload=onTurnstileLoad / onRecaptchaLoad 回调通知就绪,
|
||||
// 必须在注入脚本之前定义好全局 onload 函数(事件丢失 → 排队回调永不触发 → 永久卡「正在加载」)。
|
||||
function ensureThirdPartyScript(type) {
|
||||
if (type === 'recaptcha') {
|
||||
window.recaptchaCallbacks = window.recaptchaCallbacks || [];
|
||||
if (!window.onRecaptchaLoad) {
|
||||
window.onRecaptchaLoad = () => flushCallbacks('recaptcha');
|
||||
}
|
||||
if (typeof window.grecaptcha === 'undefined' && !document.querySelector('script[src*="recaptcha/api"]')) {
|
||||
const s = document.createElement('script');
|
||||
s.src = 'https://www.recaptcha.net/recaptcha/api.js?onload=onRecaptchaLoad&render=explicit';
|
||||
@@ -43,6 +56,9 @@ function ensureThirdPartyScript(type) {
|
||||
}
|
||||
} else {
|
||||
window.turnstileCallbacks = window.turnstileCallbacks || [];
|
||||
if (!window.onTurnstileLoad) {
|
||||
window.onTurnstileLoad = () => flushCallbacks('turnstile');
|
||||
}
|
||||
if (typeof window.turnstile === 'undefined' && !document.querySelector('script[src*="turnstile"]')) {
|
||||
const s = document.createElement('script');
|
||||
s.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad&render=explicit';
|
||||
@@ -52,6 +68,20 @@ function ensureThirdPartyScript(type) {
|
||||
}
|
||||
}
|
||||
|
||||
// 注册渲染回调:库就绪立即执行,未就绪排队等 onload 冲刷;加 300ms 竞态兜底
|
||||
// (脚本恰好在「检查就绪」与「入队」之间加载完成、onload 已错过时重放队列)。
|
||||
function queueRender(type, render) {
|
||||
const lib = type === 'recaptcha' ? 'grecaptcha' : 'turnstile';
|
||||
if (typeof window[lib] !== 'undefined') { render(); return; }
|
||||
const key = type === 'recaptcha' ? 'recaptchaCallbacks' : 'turnstileCallbacks';
|
||||
window[key] = window[key] || [];
|
||||
window[key].push(render);
|
||||
ensureThirdPartyScript(type);
|
||||
setTimeout(() => {
|
||||
if (typeof window[lib] !== 'undefined') flushCallbacks(type);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// SHA-256(Proof of Work 使用)
|
||||
async function sha256(str) {
|
||||
const buf = new TextEncoder().encode(str);
|
||||
@@ -185,6 +215,7 @@ function ThirdPartyCaptcha({ type, onFinish }) {
|
||||
if (!siteKey) { onFinish(null); return; }
|
||||
const container = widgetRef.current;
|
||||
const render = () => {
|
||||
if (doneRef.current) return; // 弹窗已取消/卸载,不再向已移除的容器渲染
|
||||
try {
|
||||
if (type === 'recaptcha') {
|
||||
const wid = window.grecaptcha.render(container, {
|
||||
@@ -215,21 +246,8 @@ function ThirdPartyCaptcha({ type, onFinish }) {
|
||||
setTimeout(() => onFinish(null), 2000);
|
||||
}
|
||||
};
|
||||
if (type === 'recaptcha') {
|
||||
if (typeof window.grecaptcha !== 'undefined') render();
|
||||
else {
|
||||
window.recaptchaCallbacks = window.recaptchaCallbacks || [];
|
||||
window.recaptchaCallbacks.push(render);
|
||||
ensureThirdPartyScript(type);
|
||||
}
|
||||
} else {
|
||||
if (typeof window.turnstile !== 'undefined') render();
|
||||
else {
|
||||
window.turnstileCallbacks = window.turnstileCallbacks || [];
|
||||
window.turnstileCallbacks.push(render);
|
||||
ensureThirdPartyScript(type);
|
||||
}
|
||||
}
|
||||
// 就绪立即渲染;未就绪排队等 onload 冲刷(含 300ms 竞态兜底)
|
||||
queueRender(type, render);
|
||||
// 卸载时不触发重复回调
|
||||
return () => { doneRef.current = true; };
|
||||
}, []);
|
||||
|
||||
Reference in New Issue
Block a user