/** * AKSRT Captcha front-end runtime — cloud providers only (tencent / aliyun / cloudflare / geetest). * * PHP renders passive mounts via [data-aksrt-captcha-config] (JSON config attribute); * this script scans them, registers AKSRTCaptchaState.scenes[scene], loads the provider * SDK once per page, and writes verified tokens back into the surrounding form. * Bundled into assets/js/build/public.js by tools/build-public-assets.mjs. */ (function () { 'use strict'; var TOKEN_FIELDS = [ 'cf_turnstile_response', 'tencent_ticket', 'tencent_randstr', 'tencent_sid', 'geetest_lot_number', 'geetest_captcha_output', 'geetest_pass_token', 'geetest_gen_time', 'aliyun_captcha_verify_param' ]; var root = (window.AKSRTCaptchaState = window.AKSRTCaptchaState || { loaders: {}, scenes: {} }); root.rescan = scan; function scan() { var mounts = document.querySelectorAll('[data-aksrt-captcha-config]'); Array.prototype.forEach.call(mounts, function (el) { if (el.dataset.aksrtCaptchaBound) { return; } el.dataset.aksrtCaptchaBound = '1'; var config; try { config = JSON.parse(el.getAttribute('data-aksrt-captcha-config')); } catch (e) { return; } if (config && config.scene && config.uid && config.vid) { initMount(config); } }); } /** * One mount = one scene instance. Mirrors the scene semantics of the previous * per-render inline script: the last initialized mount wins scenes[scene]. */ function initMount(C) { var root = window.AKSRTCaptchaState; var box, hField, form = null; var verified = false; var pendingAction = null; var triggerCaptcha = function () { showTip('验证码加载中,请稍候', '#e67e22'); }; function loadScriptOnce(key, src, readyCheck) { if (typeof readyCheck === 'function' && readyCheck()) { return Promise.resolve(); } if (root.loaders[key]) { return root.loaders[key]; } root.loaders[key] = new Promise(function (resolve, reject) { var existing = document.querySelector('script[data-aksrt-sdk="' + key + '"]'); if (existing) { if (typeof readyCheck === 'function' && readyCheck()) { resolve(); return; } existing.addEventListener('load', function () { resolve(); }, { once: true }); existing.addEventListener('error', function () { reject(new Error(key + ' load failed')); }, { once: true }); return; } var script = document.createElement('script'); script.src = src; script.async = true; script.setAttribute('data-aksrt-sdk', key); script.onload = function () { resolve(); }; script.onerror = function () { reject(new Error(key + ' load failed')); }; document.head.appendChild(script); }); return root.loaders[key]; } function findForm() { form = box.closest('form'); if (!form && C.scene === 'comment') form = document.getElementById('commentform'); if (!form && (C.scene === 'login' || C.scene === 'register')) { form = box.closest('[data-auth-form]') || document.querySelector('[data-auth-form]'); } if (!form && C.scene === 'bind_phone') { form = document.getElementById('aksrt-bind-phone-form') || document.querySelector('[data-aksrt-bind-phone-form]') || document.querySelector('.auth-form'); } return form; } function bindSubmit() { if (!form) return; form.addEventListener('submit', function (e) { if (verified || hField.value === '1') return; e.preventDefault(); e.stopImmediatePropagation(); pendingAction = null; triggerCaptcha(); }, true); } function setField(name, value) { var container = form || box; var el = container.querySelector('input[name="' + name + '"]'); if (!el) { el = document.createElement('input'); el.type = 'hidden'; el.name = name; container.appendChild(el); } el.value = value; } function resetVerificationState() { verified = false; hField.value = '0'; var container = form || box; TOKEN_FIELDS.forEach(function (n) { var f = container.querySelector('input[name="' + n + '"]'); if (f) f.remove(); }); if (C.scene === 'download') { syncDownloadSiblings(false); } } function syncDownloadSiblings(passed) { var myContainer = box.closest('[data-aksrt-download-captcha]'); if (!myContainer) return; document.querySelectorAll('[data-aksrt-download-captcha]').forEach(function (target) { if (target === myContainer) return; var tgtH = target.querySelector('input[name="captcha_verified"]'); if (tgtH) tgtH.value = passed ? '1' : '0'; TOKEN_FIELDS.forEach(function (name) { var f = target.querySelector('input[name="' + name + '"]'); if (f) f.remove(); if (passed) { var srcEl = myContainer.querySelector('input[name="' + name + '"]'); if (srcEl && srcEl.value !== '') { var tgtEl = document.createElement('input'); tgtEl.type = 'hidden'; tgtEl.name = name; target.appendChild(tgtEl); tgtEl.value = srcEl.value; } } }); }); } function submitNativeForm() { if (!form) return; if (form.requestSubmit) { form.requestSubmit(); } else { HTMLFormElement.prototype.submit.call(form); } } function submitForm() { verified = true; hField.value = '1'; if (C.scene === 'download') syncDownloadSiblings(true); if (typeof pendingAction === 'function') { finishPending(true); return; } setTimeout(resetVerificationState, 3000); submitNativeForm(); } function showTip(msg, color) { var t = box.querySelector('.captcha-tip'); if (!t) { t = document.createElement('div'); t.className = 'captcha-tip'; t.style.cssText = 'font-size:12px;margin-top:4px;text-align:left;'; box.appendChild(t); } t.style.color = color || '#e74c3c'; t.textContent = msg; setTimeout(function () { if (t.parentNode) t.remove(); }, 4000); } function showLoading() { box.innerHTML = '
验证码加载中...
'; } function clearLoading() { box.innerHTML = ''; } function finishPending(ok) { var action = pendingAction; pendingAction = null; if (typeof action === 'function') { action(!!ok); } } function cancelPending(msg) { if (msg) showTip(msg, '#e67e22'); finishPending(false); } root.scenes[C.scene] = { verify: function (callback) { if (verified || hField.value === '1') { if (typeof callback === 'function') callback(true); return; } pendingAction = (typeof callback === 'function') ? function (ok) { callback(ok !== false); } : null; triggerCaptcha(); }, reset: resetVerificationState }; // ============ 腾讯云 Captcha 2.0 ============ function loadTencent() { showLoading(); loadScriptOnce('tencent', 'https://turing.captcha.qcloud.com/TJCaptcha.js', function () { return typeof TencentCaptcha !== 'undefined'; }).then(function () { clearLoading(); var capIns = null; triggerCaptcha = function () { if (!capIns) { capIns = new TencentCaptcha(C.tc.app_id, function (res) { if (res.ret === 2) { cancelPending('已取消验证'); return; } if (res.ret === 0 && res.ticket) { if (res.ticket.indexOf('trerror_') === 0) { showTip('验证码服务异常,请刷新页面重试', '#e67e22'); return; } setField('tencent_ticket', res.ticket); setField('tencent_randstr', res.randstr); setField('tencent_sid', res.sid || ''); submitForm(); return; } if (res.errorCode) { var errorMap = { 1001: '验证码组件加载失败', 1002: '验证码超时', 1006: '验证码配置拉取失败', 1008: '验证码页面加载异常', 1012: '验证码刷新次数过多', 1013: '验证码网络异常' }; showTip(errorMap[res.errorCode] || ('验证码异常(errorCode:' + res.errorCode + ')'), '#e74c3c'); } }, { userLanguage: 'zh-cn' }); } capIns.show(); }; }).catch(function () { clearLoading(); showTip('验证码组件加载失败,请刷新重试', '#e74c3c'); }); } // ============ Cloudflare Turnstile(弹窗) ============ function loadCloudflare() { var cfWidgetId = null, cfVerifying = false, cfModal = null; if (typeof turnstile === 'undefined') { window['onTurnstileLoad_' + C.uid] = function () {}; loadScriptOnce('cloudflare', 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad_' + C.uid + '&render=explicit', function () { return typeof turnstile !== 'undefined'; }); } function renderWidget() { if (cfWidgetId) { try { turnstile.remove(cfWidgetId); } catch (e) {} cfWidgetId = null; } if (typeof turnstile === 'undefined') { setTimeout(renderWidget, 200); return; } cfWidgetId = turnstile.render('#modalWidget_' + C.uid, { sitekey: C.cf.site_key, theme: C.cf.theme, size: C.cf.size, action: 'aksrt-verify', cdata: C.scene, callback: function (token) { if (!cfVerifying) return; cfVerifying = false; setField('cf_turnstile_response', token); closeModal(); submitForm(); }, 'error-callback': function () { showTip('验证码失败,请重试', '#e74c3c'); cfWidgetId = null; setTimeout(renderWidget, 500); }, 'expired-callback': function () { showTip('验证码已过期,请重新验证', '#e67e22'); cfWidgetId = null; setTimeout(renderWidget, 500); } }); } function createModal() { cfModal = document.createElement('div'); cfModal.className = 'aksrt-captcha-modal-overlay'; cfModal.innerHTML = '
' + '
' + '人机验证' + '
' + '

请完成验证后继续操作

' + '
'; document.body.appendChild(cfModal); var cancelFn = function () { cfVerifying = false; closeModal(); cancelPending('已取消验证'); }; cfModal.querySelector('.aksrt-captcha-modal-backdrop').addEventListener('click', cancelFn); cfModal.querySelector('.aksrt-captcha-modal-close').addEventListener('click', cancelFn); renderWidget(); } function closeModal() { if (cfWidgetId) { try { turnstile.remove(cfWidgetId); } catch (e) {} cfWidgetId = null; } if (cfModal && cfModal.parentNode) { cfModal.parentNode.removeChild(cfModal); } cfModal = null; } triggerCaptcha = function () { if (cfVerifying) { showTip('验证码进行中,请稍候', '#e67e22'); return; } cfVerifying = true; createModal(); }; } // ============ 极验 GeeTest v4 ============ function loadGeetest() { showLoading(); var captchaId = (C.gt && C.gt.captcha_id) ? String(C.gt.captcha_id).trim() : ''; var product = (C.gt && C.gt.product) ? String(C.gt.product) : 'bind'; if (!captchaId) { clearLoading(); showTip('极验 Captcha ID 未配置,请到后台「功能&权限 → 人机验证」填写并保存', '#e74c3c'); return; } function render() { clearLoading(); initGeetest4({ captchaId: captchaId, product: product, hideSuccess: true }, function (capIns) { window['__gt_' + C.uid] = capIns; function onSuccess() { var result = capIns.getValidate(); if (!result) return; setField('geetest_lot_number', result.lot_number); setField('geetest_captcha_output', result.captcha_output); setField('geetest_pass_token', result.pass_token); setField('geetest_gen_time', result.gen_time); submitForm(); } if (product === 'bind' || product === 'popup') { capIns.onReady(function () {}).onSuccess(onSuccess).onClose(function () { cancelPending('已取消验证'); }); } else { capIns.appendTo('#' + C.uid).onSuccess(onSuccess).onClose(function () { cancelPending('已取消验证'); }); } }); triggerCaptcha = function () { var instance = window['__gt_' + C.uid]; if (!instance) { showTip('验证码加载中,请稍候再试', '#e67e22'); return; } if (product === 'bind' || product === 'popup') { instance.showCaptcha(); return; } showTip('请先完成人机验证', '#e67e22'); }; } if (typeof initGeetest4 === 'undefined') { loadScriptOnce('geetest', 'https://static.geetest.com/v4/gt4.js', function () { return typeof initGeetest4 !== 'undefined'; }).then(render).catch(function () { clearLoading(); showTip('极验组件加载失败', '#e74c3c'); }); return; } render(); } // ============ 阿里云 CAPTCHA 2.0 V3 ============ function loadAliyun() { showLoading(); window.AliyunCaptchaConfig = { region: C.al.region || 'cn', prefix: C.al.prefix }; function render() { clearLoading(); var captchaIns = null; var btn = box.closest('form') ? box.closest('form').querySelector('button[type="submit"], input[type="submit"]') : null; window.initAliyunCaptcha({ SceneId: C.al.scene_id, mode: 'popup', element: '#' + C.uid, button: btn ? '#' + (btn.id || (function () { btn.id = 'aksrt-captcha-btn-' + C.scene; return btn.id; })()) : null, success: function (captchaVerifyParam) { setField('aliyun_captcha_verify_param', captchaVerifyParam); submitForm(); }, fail: function (result) { showTip('验证码失败: ' + (result || '请重试'), '#e74c3c'); captchaIns = null; }, getInstance: function (instance) { captchaIns = instance; }, slideStyle: { width: 360, height: 40 }, language: 'cn' }); triggerCaptcha = function () { if (captchaIns && captchaIns.show) { captchaIns.show(); return; } showTip('验证码组件加载中,请稍候再试', '#e67e22'); }; } if (typeof window.initAliyunCaptcha === 'undefined') { loadScriptOnce('aliyun', 'https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js', function () { return typeof window.initAliyunCaptcha !== 'undefined'; }).then(render).catch(function () { clearLoading(); showTip('验证码组件加载失败,请刷新重试', '#e74c3c'); }); return; } render(); } function loadProvider() { switch (C.provider) { case 'tencent': loadTencent(); break; case 'cloudflare': loadCloudflare(); break; case 'geetest': loadGeetest(); break; case 'aliyun': loadAliyun(); break; } } function init() { box = document.getElementById(C.uid); hField = document.getElementById(C.vid); if (!box || !hField) return; var tries = 0; (function retryFind() { findForm(); if (!form && ++tries < 10) { setTimeout(retryFind, 150); } else { if (form) bindSubmit(); setTimeout(loadProvider, 0); } })(); } init(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', scan); } else { setTimeout(scan, 50); } })();