(function (root, factory) { // 验证码适配层同时支持浏览器运行和 Node 单元测试。 if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.CaptchaPages = factory(root); })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; var DEFAULT_SCENE_CODE = 'WEB_H5_LOGIN'; function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; } function query(selector, rootNode) { return (rootNode || root.document).querySelector(selector); } function showMessage(message) { if (root.MessageUtil && root.MessageUtil.show) { root.MessageUtil.show(message); return; } if (root.console && root.console.warn) root.console.warn(message); } function buildChallengeBody(options, api) { // 请求体字段严格对应 PC YAML 的 VerificationChallengeBody。 var settings = options || {}; return { tenantId: settings.tenantId || api.tenantId, clientId: settings.clientId || api.clientId, sceneCode: settings.sceneCode || DEFAULT_SCENE_CODE, subject: settings.subject || '' }; } function normalizeChallengeData(data) { // TAC 需要 data.type、data.id 和图片字段;接口文档返回 captchaType、challengeId、payload。 var source = data || {}; var payload = source.payload || {}; return Object.assign({}, payload, { type: source.captchaType || payload.type || 'SLIDER', id: payload.id || source.challengeId, challengeId: source.challengeId, providerCode: source.providerCode || 'tianai', captchaType: source.captchaType || payload.type || 'SLIDER' }); } function adaptChallengeResponse(response) { var payload = response || {}; if (Number(payload.code) !== 200 || !payload.data) return payload; return Object.assign({}, payload, { data: normalizeChallengeData(payload.data) }); } function buildVerifyBody(requestData, context, api) { // requestData 是 TAC 拖动完成后的轨迹数据;context 保存本次挑战的接口字段。 var settings = context || {}; var source = requestData || {}; return { tenantId: settings.tenantId || api.tenantId, clientId: settings.clientId || api.clientId, sceneCode: settings.sceneCode || DEFAULT_SCENE_CODE, subject: settings.subject || '', challengeId: settings.challengeId || source.challengeId || source.id, providerCode: settings.providerCode || 'tianai', captchaType: settings.captchaType || 'SLIDER', payload: { id: source.id, data: source.data || {} } }; } function normalizeVerifyResponse(response) { // TAC 只看 code 是否为 200;接口通过 passed 表示业务验证结果。 var payload = response || {}; if (Number(payload.code) === 200 && payload.data && payload.data.passed === false) { return Object.assign({}, payload, { code: 4001, msg: payload.data.message || payload.msg || '验证失败' }); } return payload; } function extractValidToken(response) { var payload = response || {}; var data = payload.data || {}; if (Number(payload.code) !== 200) return ''; if (data.passed === false) return ''; return data.validToken || payload.validToken || ''; } function getTokenField(form) { return query('input[name="validToken"]', form); } function getCaptchaBox(form) { // 优先使用页面级弹窗挂载点,避免 TAC 验证码挤占表单布局。 return query('.auth-captcha-modal[data-captcha-box]') || query('[data-captcha-box]', form) || query('[data-captcha-box]'); } function writeValidToken(form, token) { var field = getTokenField(form); if (field) field.value = token || ''; } function shouldRequireCaptcha(result) { // /captcha/require 返回 required=false 时允许直接继续业务请求。 if (!result) return true; return result.required !== false; } function createTac(form, options, api, resolve, reject) { var settings = options || {}; var box = getCaptchaBox(form); var context = buildChallengeBody(settings, api); var lastChallenge = {}; var captcha; var config; if (!root.TAC || !root.CaptchaConfig) { reject(new Error('滑动验证组件未加载')); return null; } if (!box) { reject(new Error('缺少滑动验证容器')); return null; } config = new root.CaptchaConfig({ bindEl: box, requestCaptchaDataUrl: api.buildApiUrl('/captcha/challenge'), validCaptchaUrl: api.buildApiUrl('/captcha/verify'), requestHeaders: { clientid: api.clientId }, validSuccess: function (response, captchaControl, captchaInstance) { var token = extractValidToken(response); if (!token) { reject(new Error('滑动验证未返回有效票据')); return; } writeValidToken(form, token); if (captchaInstance && captchaInstance.destroyWindow) captchaInstance.destroyWindow(); resolve(token); }, validFail: function (response, captchaControl, captchaInstance) { if (captchaInstance && captchaInstance.reloadCaptcha) captchaInstance.reloadCaptcha(); showMessage((response && (response.msg || response.message)) || '滑动验证失败,请重试'); }, btnCloseFun: function (event, captchaInstance) { if (captchaInstance) captchaInstance.destroyWindow(); reject(new Error('已取消滑动验证')); } }); config.addRequestChain({ preRequest: function (type, request) { if (type === 'requestCaptchaData') { request.data = buildChallengeBody(settings, api); } if (type === 'validCaptcha') { request.data = buildVerifyBody(request.data, Object.assign({}, context, lastChallenge), api); } return true; }, postRequest: function (type, request, response) { if (type === 'requestCaptchaData') { var adapted = adaptChallengeResponse(response); lastChallenge = { challengeId: adapted.data && adapted.data.challengeId, providerCode: adapted.data && adapted.data.providerCode, captchaType: adapted.data && adapted.data.captchaType }; Object.keys(response || {}).forEach(function (key) { delete response[key]; }); Object.assign(response, adapted); } if (type === 'validCaptcha') { Object.assign(response, normalizeVerifyResponse(response)); } return true; } }); captcha = new root.TAC(config, { bgUrl: 'public/tac/images/dun.jpeg', logoUrl: null }); captcha.init(); return captcha; } async function ensureToken(form, options) { // 认证页提交前调用:已有 validToken 直接通过,否则按验证中心策略弹出 TAC。 var api = getApi(); var field = getTokenField(form); var settings = options || {}; var requirement; if (!api) return ''; if (field && field.value) return field.value; if (!settings.sceneCode) return ''; try { requirement = await api.captchaRequirement(buildChallengeBody(settings, api)); if (!shouldRequireCaptcha(requirement)) return 'CAPTCHA_NOT_REQUIRED'; } catch (error) { // 查询策略失败时仍尝试直接拉起验证,避免认证接口缺少 validToken。 requirement = null; } return new Promise(function (resolve, reject) { createTac(form, settings, api, resolve, reject); }).catch(function (error) { showMessage(error.message || '请先完成滑动验证'); return ''; }); } return { buildChallengeBody: buildChallengeBody, adaptChallengeResponse: adaptChallengeResponse, buildVerifyBody: buildVerifyBody, getCaptchaBox: getCaptchaBox, extractValidToken: extractValidToken, normalizeVerifyResponse: normalizeVerifyResponse, shouldRequireCaptcha: shouldRequireCaptcha, ensureToken: ensureToken }; });