// Interactive Geetest hand-off, following the official HG account SDK. The user completes the provider's widget; // only its proof is returned to the original login request. No phone, password or account token is sent here. const scripts = new Map(); const PROVIDERS = { v4: { src: 'https://static.geetest.com/v4/gt4.js', init: 'initGeetest4' }, v3: { src: 'https://static.geetest.com/static/tools/gt.js', init: 'initGeetest' }, }; async function loadProvider(version) { const provider = PROVIDERS[version]; if (typeof globalThis[provider.init] === 'function') return; if (!scripts.has(version)) { const pending = new Promise((resolve, reject) => { const script = document.createElement('script'); const fail = () => { clearTimeout(timer); script.remove(); reject(new Error('captcha_load')); }; const timer = setTimeout(fail, 20_000); script.src = provider.src; script.async = true; script.onerror = fail; script.onload = () => { clearTimeout(timer); if (typeof globalThis[provider.init] === 'function') resolve(); else fail(); }; document.head.append(script); }); scripts.set(version, pending); pending.catch(() => scripts.delete(version)); } await scripts.get(version); } /** * Called only by the user's captcha button. `scope` and `load` allow isolated widget contract tests without a * network call or a real challenge. Resolves null on user cancellation; never completes a real puzzle itself. */ export async function solveCaptcha(challenge, { scope = globalThis, load = loadProvider } = {}) { const version = challenge.geetestId ? 'v4' : challenge.gt && challenge.challenge ? 'v3' : null; if (!version) throw new Error('captcha_type'); await load(version); return new Promise((resolve, reject) => { let settled = false; const init = scope[PROVIDERS[version].init]; const options = version === 'v4' ? { captchaId: challenge.geetestId, riskType: challenge.riskType, product: 'bind', language: 'zho' } : { gt: challenge.gt, challenge: challenge.challenge, new_captcha: challenge.new_captcha, offline: false, product: 'bind', https: true, lang: 'zh-cn' }; init(options, (widget) => { const finish = (proof, error) => { if (settled) return; settled = true; widget.destroy?.(); if (error) reject(error); else resolve(proof); }; widget.onSuccess(() => { const proof = widget.getValidate(); if (!proof) { finish(null, new Error('captcha_validate')); return; } finish(version === 'v4' ? { ...proof, challenge: challenge.challenge } : proof); }); widget.onError((error) => { if (error?.error_code !== 'error_01') finish(null, new Error('captcha_validate')); }); widget.onClose(() => finish(null)); widget.onReady(() => { if (version === 'v4') widget.showCaptcha(); else widget.verify(); }); }); }); }