feat: 代理内网白名单(proxy_allowed_hosts CIDR)+ fix: Cloudflare 验证码首次加载卡死/登录注册失败后验证码状态重置

This commit is contained in:
2026-08-09 03:48:19 +08:00
parent 0def382511
commit 6ce6438854
6 changed files with 116 additions and 18 deletions
+33 -15
View File
@@ -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-256Proof 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; };
}, []);