Files
wordpress-theme-aksrt/assets/js/routes/user-center-auth.js
T

371 lines
15 KiB
JavaScript

(function (window, document) {
'use strict';
function parseConfig(root) {
try { return JSON.parse(root.getAttribute('data-identity-config') || '{}'); } catch (_) { return {}; }
}
function uuid() {
if (window.crypto && crypto.randomUUID) return crypto.randomUUID();
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0;
return (c === 'x' ? r : (r & 3 | 8)).toString(16);
});
}
function request(path, config, options) {
options = Object.assign({}, options || {});
options.credentials = 'same-origin';
options.headers = Object.assign({ 'X-WP-Nonce': config.nonce || '' }, options.headers || {});
return window.AksrtRequest.request(path, options).then(function (response) {
return response.data !== undefined ? response.data : response.json;
});
}
function errorMessage(error, fallback) {
if (error && error.json) {
return error.json.message || (error.json.data && error.json.data.message) || fallback;
}
return error && error.message ? error.message : fallback;
}
function loadScript(src) {
return new Promise(function (resolve, reject) {
var existing = document.querySelector('script[src="' + src + '"]');
if (existing) {
if (existing.dataset.loaded === '1') return resolve();
existing.addEventListener('load', resolve, { once: true });
existing.addEventListener('error', reject, { once: true });
return;
}
var script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = function () { script.dataset.loaded = '1'; resolve(); };
script.onerror = reject;
document.head.appendChild(script);
});
}
function aliyunMetaInfo(config) {
if (config.provider !== 'aliyun') return Promise.resolve('');
return loadScript('https://o.alicdn.com/yd-cloudauth/cloudauth-cdn/jsvm_all.js').then(function () {
if (typeof window.getMetaInfo !== 'function') throw new Error('阿里云 MetaInfo 组件未加载成功');
var meta = window.getMetaInfo();
return typeof meta === 'string' ? meta : JSON.stringify(meta || {});
});
}
function init(root) {
if (!root || root.dataset.identityBound === '1') return;
root.dataset.identityBound = '1';
var config = parseConfig(root);
var template = root.querySelector('[data-auth-modal-template]');
var runtime = window.AksrtModalRuntime;
if (!template) return;
var activeShell = null;
var currentUuid = config.currentUuid || '';
var currentStage = config.currentStage || '';
var pollTimer = null;
var pollBackoff = 0;
var currentController = null;
var refreshController = null;
var refreshTimeout = null;
var refreshInFlight = false;
var openSequence = 0;
function isOpen(shell) {
return !!shell && shell.classList.contains('is-open') && document.contains(shell);
}
function stopPolling() {
if (pollTimer) window.clearTimeout(pollTimer);
pollTimer = null;
}
function abortCurrent() {
if (currentController) {
try { currentController.abort(); } catch (_) {}
}
currentController = null;
}
function abortRefresh() {
if (refreshTimeout) window.clearTimeout(refreshTimeout);
refreshTimeout = null;
if (refreshController) {
try { refreshController.abort(); } catch (_) {}
}
refreshController = null;
refreshInFlight = false;
}
function setMessage(shell, message) {
var node = shell && shell.querySelector('[data-auth-status-message]');
if (node) node.textContent = message || '';
}
function updateSummary(data) {
if (!data || data.status !== 'verified') return;
var trigger = root.querySelector('[data-auth-modal-open]');
if (trigger) {
trigger.disabled = true;
trigger.textContent = '已认证';
}
runtime.invalidate(['identity']);
document.dispatchEvent(new CustomEvent('aksrt:identity:changed', { detail: { status: data.status } }));
}
function schedulePoll(delay) {
stopPolling();
if (!currentUuid || !isOpen(activeShell) || document.visibilityState !== 'visible') return;
var base = Math.max(5, Number(delay || config.pollInterval || 5));
var seconds = Math.min(60, base * Math.pow(2, Math.min(4, pollBackoff)));
pollTimer = window.setTimeout(function () {
pollTimer = null;
refresh(false);
}, seconds * 1000);
}
function render(data, shell) {
if (!shell || shell !== activeShell || !isOpen(shell)) return;
data = data || {};
var previousStage = currentStage;
currentUuid = data.uuid || currentUuid;
currentStage = data.stage || currentStage;
if (data.stage && data.stage !== previousStage) runtime.invalidate(['identity']);
var lock = ['created', 'provider_pending', 'provider_processing', 'manual_pending'].indexOf(currentStage) !== -1;
var retryable = data.next_action === 'manual_or_retry';
var statusBox = shell.querySelector('[data-auth-status]');
var createForm = shell.querySelector('[data-auth-create]');
var stageNode = shell.querySelector('[data-auth-stage]');
var messageNode = shell.querySelector('[data-auth-status-message]');
var continueLink = shell.querySelector('[data-auth-continue]');
var manualButton = shell.querySelector('[data-auth-manual-open]');
if (statusBox) statusBox.hidden = !data.uuid && !retryable;
if (createForm) createForm.hidden = lock || data.status === 'verified';
if (stageNode) stageNode.textContent = data.stage || data.status || '准备认证';
if (messageNode) messageNode.textContent = data.message || '认证结果以站点服务端主动查询为准。';
if (continueLink) {
var canContinue = data.next_action === 'poll' && data.redirect_url;
continueLink.hidden = !canContinue;
if (canContinue) continueLink.href = data.redirect_url;
}
if (manualButton) manualButton.hidden = !retryable;
if (data.status === 'verified' || data.next_action !== 'poll') {
pollBackoff = 0;
stopPolling();
} else if (isOpen(shell) && document.visibilityState === 'visible') {
schedulePoll(data.poll_interval || config.pollInterval);
}
updateSummary(data);
}
function refresh(explicit) {
if (!currentUuid || !isOpen(activeShell) || document.visibilityState !== 'visible' || refreshInFlight) {
return Promise.resolve(null);
}
stopPolling();
abortRefresh();
refreshInFlight = true;
refreshController = window.AbortController ? new window.AbortController() : null;
var timedOut = false;
var shell = activeShell;
var timeoutMs = Math.max(5000, Number(config.refreshTimeout || 15000));
refreshTimeout = window.setTimeout(function () {
timedOut = true;
if (refreshController) {
try { refreshController.abort(); } catch (_) {}
}
}, timeoutMs);
return request(config.restBase + '/' + encodeURIComponent(currentUuid) + '/refresh', config, {
method: 'POST',
signal: refreshController ? refreshController.signal : undefined,
}).then(function (data) {
if (shell !== activeShell || !isOpen(shell)) return data;
pollBackoff = explicit ? 0 : Math.min(4, pollBackoff + 1);
render(data, shell);
return data;
}).catch(function (error) {
var aborted = error && (error.name === 'AbortError' || error.code === 'ABORT_ERR');
if (shell !== activeShell || !isOpen(shell) || (aborted && !timedOut)) return null;
setMessage(shell, timedOut ? '云端刷新超时,请稍后重试。' : errorMessage(error, '刷新失败,请稍后重试。'));
pollBackoff = Math.min(4, pollBackoff + 1);
schedulePoll(config.pollInterval || 5);
return null;
}).finally(function () {
if (refreshTimeout) window.clearTimeout(refreshTimeout);
refreshTimeout = null;
refreshController = null;
refreshInFlight = false;
});
}
function loadCurrent(shell, sequence) {
abortCurrent();
currentController = window.AbortController ? new window.AbortController() : null;
var controller = currentController;
return request(config.restBase + '/current', config, {
method: 'GET',
signal: controller ? controller.signal : undefined,
}).then(function (snapshot) {
if (sequence !== openSequence || shell !== activeShell || !isOpen(shell)) return;
var data = snapshot && (snapshot.current || snapshot.active || snapshot.latest);
if (!data) {
currentUuid = '';
currentStage = '';
var statusBox = shell.querySelector('[data-auth-status]');
var createForm = shell.querySelector('[data-auth-create]');
if (statusBox) statusBox.hidden = true;
if (createForm) createForm.hidden = false;
stopPolling();
return;
}
render(data, shell);
}).catch(function (error) {
var aborted = error && (error.name === 'AbortError' || error.code === 'ABORT_ERR');
if (aborted || sequence !== openSequence || shell !== activeShell || !isOpen(shell)) return;
setMessage(shell, errorMessage(error, '状态加载失败,请稍后重试。'));
}).finally(function () {
if (currentController === controller) currentController = null;
});
}
function bindForm(shell) {
var createForm = shell.querySelector('[data-auth-create]');
var manualForm = shell.querySelector('[data-auth-manual]');
var statusBox = shell.querySelector('[data-auth-status]');
var refreshButton = shell.querySelector('[data-auth-refresh]');
var manualButton = shell.querySelector('[data-auth-manual-open]');
var typeSelect = createForm && createForm.querySelector('[name="type"]');
var enterprise = createForm && createForm.querySelector('[data-enterprise-fields]');
var personLabel = createForm && createForm.querySelector('[data-auth-person-label]');
var idLabel = createForm && createForm.querySelector('[data-auth-id-label]');
function syncEnterprise() {
var enabled = typeSelect && typeSelect.value === 'enterprise';
if (enterprise) enterprise.hidden = !enabled;
if (enterprise) enterprise.querySelectorAll('input').forEach(function (input) { input.required = enabled; });
if (personLabel) personLabel.textContent = enabled ? '法定代表人姓名' : '真实姓名';
if (idLabel) idLabel.textContent = enabled ? '法定代表人身份证号' : '身份证号码';
}
if (typeSelect) {
typeSelect.addEventListener('change', syncEnterprise);
syncEnterprise();
}
if (refreshButton) refreshButton.addEventListener('click', function () { refresh(true); });
if (manualButton) manualButton.addEventListener('click', function () { if (manualForm) manualForm.hidden = false; });
if (createForm) createForm.addEventListener('submit', function (event) {
event.preventDefault();
var button = createForm.querySelector('[type="submit"]');
var message = createForm.querySelector('[data-auth-message]');
var form = new FormData(createForm);
var payload = {};
form.forEach(function (value, key) { payload[key] = value; });
payload.consent = payload.consent === '1';
payload.client_request_id = uuid();
if (button) button.disabled = true;
if (message) message.textContent = '';
aliyunMetaInfo(config).then(function (metaInfo) {
if (metaInfo) payload.meta_info = metaInfo;
return request(config.restBase, config, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
}).then(function (data) {
render(data, shell);
}).catch(function (error) {
if (message) message.textContent = errorMessage(error, '创建认证会话失败,请重试。');
}).finally(function () {
if (button) button.disabled = false;
});
});
if (manualForm) manualForm.addEventListener('submit', function (event) {
event.preventDefault();
if (!currentUuid) return;
var files = manualForm.querySelector('[name="evidence[]"]');
if (files && files.files.length > 3) {
setMessage(shell, '证明材料最多上传 3 张');
return;
}
var button = manualForm.querySelector('[type="submit"]');
var body = new FormData();
Array.prototype.forEach.call((files && files.files) || [], function (file) { body.append('evidence[]', file); });
if (button) button.disabled = true;
request(config.restBase + '/' + encodeURIComponent(currentUuid) + '/manual', config, {
method: 'POST',
body: body,
}).then(function (data) {
manualForm.hidden = true;
render(data, shell);
}).catch(function (error) {
setMessage(shell, errorMessage(error, '人工审核提交失败,请重试。'));
}).finally(function () {
if (button) button.disabled = false;
});
});
if (statusBox && statusBox.hidden && createForm) createForm.hidden = false;
}
function open() {
stopPolling();
abortCurrent();
abortRefresh();
openSequence += 1;
var sequence = openSequence;
var shell = runtime.open({
html: template.innerHTML,
kind: 'identity',
className: 'aksrt-identity-modal-shell',
mobileBottom: true,
onContent: function (_, session) {
activeShell = session.shell;
bindForm(session.shell);
},
});
activeShell = shell;
loadCurrent(shell, sequence);
}
root.querySelectorAll('[data-auth-modal-open]').forEach(function (button) {
button.addEventListener('click', function (event) {
event.preventDefault();
open();
});
});
document.addEventListener('visibilitychange', function () {
if (!isOpen(activeShell)) return;
if (document.visibilityState !== 'visible') {
stopPolling();
abortCurrent();
abortRefresh();
return;
}
if (currentUuid && ['provider_pending', 'provider_processing'].indexOf(currentStage) !== -1) {
schedulePoll(config.pollInterval || 5);
}
});
document.addEventListener('aksrt:modal:close', function (event) {
if (!activeShell || !event.detail || event.detail.shell !== activeShell) return;
stopPolling();
abortCurrent();
abortRefresh();
activeShell = null;
});
}
window.AKSRTUserCenterAuth = { init: init };
})(window, document);