311 lines
12 KiB
JavaScript
311 lines
12 KiB
JavaScript
/** Shared authentication state machine for modal and /sign/. */
|
|
(function () {
|
|
'use strict';
|
|
|
|
var config = window.aksrtData || {};
|
|
var apiRoot = String(config.apiUrl || config.restUrl || '/wp-json/aksrt/v1').replace(/\/$/, '');
|
|
var nonce = config.restNonce || '';
|
|
|
|
function request(path, data) {
|
|
return fetch(apiRoot + '/auth/' + path, {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce },
|
|
body: JSON.stringify(data),
|
|
}).then(function (response) {
|
|
return response.json().catch(function () { return {}; }).then(function (body) {
|
|
if (!response.ok || body.success === false) {
|
|
throw new Error(body.message || (body.data && body.data.message) || '请求失败,请稍后再试');
|
|
}
|
|
return body.data || body;
|
|
});
|
|
});
|
|
}
|
|
|
|
function formData(form) {
|
|
var data = {};
|
|
new FormData(form).forEach(function (value, key) {
|
|
data[key] = value;
|
|
});
|
|
return data;
|
|
}
|
|
|
|
function showMessage(root, text, success) {
|
|
var active = root.querySelector('[data-auth-panel]:not([hidden])');
|
|
var box = active && active.querySelector('[data-auth-message]');
|
|
if (!box) return;
|
|
box.textContent = text || '';
|
|
box.hidden = !text;
|
|
box.classList.toggle('is-success', !!success);
|
|
}
|
|
|
|
function activate(root, tab, updateUrl) {
|
|
var panel = root.querySelector('[data-auth-panel="' + tab + '"]');
|
|
if (!panel) return;
|
|
root.querySelectorAll('[data-auth-panel]').forEach(function (item) { item.hidden = item !== panel; });
|
|
root.dataset.activeTab = tab;
|
|
showMessage(root, '', false);
|
|
if (updateUrl && root.dataset.surface === 'page' && window.history && window.URL) {
|
|
var url = new URL(window.location.href);
|
|
url.searchParams.set('tab', tab);
|
|
window.history.replaceState({}, '', url.toString());
|
|
}
|
|
var focus = panel.querySelector('input:not([type="hidden"])');
|
|
if (focus) window.setTimeout(function () { focus.focus(); }, 30);
|
|
if (tab === 'oauth') loadQr(root);
|
|
}
|
|
|
|
function loadQr(root) {
|
|
var box = root.querySelector('[data-auth-qrcode]');
|
|
if (!box || box.dataset.loading === '1' || box.dataset.loaded === '1') return;
|
|
box.dataset.loading = '1';
|
|
request('qrcode-login', {}).then(function (result) {
|
|
box.dataset.loading = '0';
|
|
box.dataset.loaded = '1';
|
|
box.innerHTML = result.qr_html || '';
|
|
if (result.mode === 'scan' && result.token) pollQr(root, box, result.token, 0);
|
|
}).catch(function (error) {
|
|
box.dataset.loading = '0';
|
|
box.innerHTML = '<span>' + String(error.message).replace(/[&<>"']/g, '') + '</span>';
|
|
});
|
|
}
|
|
|
|
function pollQr(root, box, token, count) {
|
|
if (!document.documentElement.contains(box) || count >= 100) return;
|
|
window.setTimeout(function () {
|
|
request('check-qrcode-login', { token: token }).then(function (result) {
|
|
if (result.logged_in) {
|
|
showMessage(root, '登录成功', true);
|
|
window.location.assign(result.redirect || window.location.href);
|
|
return;
|
|
}
|
|
if (result.status === 'expired') {
|
|
box.dataset.loaded = '0';
|
|
box.innerHTML = '<button type="button" data-auth-refresh-qr>二维码已过期,点击刷新</button>';
|
|
return;
|
|
}
|
|
pollQr(root, box, token, count + 1);
|
|
}).catch(function () { pollQr(root, box, token, count + 1); });
|
|
}, 3000);
|
|
}
|
|
|
|
function submitQrCode(root, form) {
|
|
var data = formData(form);
|
|
var button = form.querySelector('button[type="submit"]');
|
|
if (button) button.disabled = true;
|
|
request('weixingzh-code', data).then(function (result) {
|
|
showMessage(root, '登录成功', true);
|
|
window.location.assign(result.redirect || window.location.href);
|
|
}).catch(function (error) {
|
|
if (button) button.disabled = false;
|
|
showMessage(root, error.message, false);
|
|
});
|
|
}
|
|
|
|
function setBusy(form, busy) {
|
|
var button = form.querySelector('.sign-submit');
|
|
if (!button) return;
|
|
button.disabled = busy;
|
|
var spinner = button.querySelector('.fa-spinner');
|
|
if (spinner) spinner.hidden = !busy;
|
|
}
|
|
|
|
function startCountdown(button, seconds) {
|
|
var original = button.dataset.label || button.textContent;
|
|
button.dataset.label = original;
|
|
button.disabled = true;
|
|
var left = Math.max(1, Number(seconds) || 60);
|
|
button.textContent = left + 's';
|
|
var timer = window.setInterval(function () {
|
|
left -= 1;
|
|
if (left <= 0) {
|
|
window.clearInterval(timer);
|
|
button.disabled = false;
|
|
button.textContent = original;
|
|
} else {
|
|
button.textContent = left + 's';
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function captchaScene(scene) {
|
|
return scene === 'resetpassword' ? 'lostpassword' : scene;
|
|
}
|
|
|
|
function verifyCaptcha(scene, callback) {
|
|
var state = window.AKSRTCaptchaState;
|
|
var captcha = state && state.scenes ? state.scenes[captchaScene(scene)] : null;
|
|
if (!captcha || typeof captcha.verify !== 'function') {
|
|
callback(true);
|
|
return;
|
|
}
|
|
captcha.verify(function (verified) { callback(verified !== false); });
|
|
}
|
|
|
|
function resetCaptcha(scene) {
|
|
var state = window.AKSRTCaptchaState;
|
|
var captcha = state && state.scenes ? state.scenes[captchaScene(scene)] : null;
|
|
if (captcha && typeof captcha.reset === 'function') captcha.reset();
|
|
}
|
|
|
|
function sendCode(root, button) {
|
|
var form = button.closest('form');
|
|
var target = form && form.querySelector('[name="target"]');
|
|
if (!target || !target.value.trim()) {
|
|
showMessage(root, '请先填写邮箱或手机号', false);
|
|
return;
|
|
}
|
|
button.disabled = true;
|
|
var payload = formData(form);
|
|
payload.scene = button.dataset.sendCode;
|
|
payload.target = target.value.trim();
|
|
payload.channel = (form.querySelector('[name="channel"]') || {}).value || '';
|
|
verifyCaptcha(payload.scene, function (verified) {
|
|
if (!verified) {
|
|
button.disabled = false;
|
|
return;
|
|
}
|
|
payload = formData(form);
|
|
payload.scene = button.dataset.sendCode;
|
|
payload.target = target.value.trim();
|
|
payload.channel = (form.querySelector('[name="channel"]') || {}).value || '';
|
|
request('code', payload).then(function (result) {
|
|
showMessage(root, result.message || '验证码已发送', true);
|
|
startCountdown(button, result.countdown || 60);
|
|
}).catch(function (error) {
|
|
button.disabled = false;
|
|
showMessage(root, error.message, false);
|
|
}).finally(function () {
|
|
resetCaptcha(payload.scene);
|
|
});
|
|
});
|
|
}
|
|
|
|
function submit(root, form) {
|
|
var action = form.dataset.authForm;
|
|
var payload = formData(form);
|
|
if (action === 'register' && payload.channel !== 'phone') {
|
|
payload.user_email = payload.target;
|
|
}
|
|
setBusy(form, true);
|
|
showMessage(root, '', false);
|
|
request(action, payload).then(function (result) {
|
|
showMessage(root, result.message || '操作成功', true);
|
|
if (result.redirect) window.setTimeout(function () { window.location.assign(result.redirect); }, 350);
|
|
}).catch(function (error) {
|
|
showMessage(root, error.message, false);
|
|
setBusy(form, false);
|
|
});
|
|
}
|
|
|
|
function toggleLoginMode(root, button) {
|
|
var password = root.querySelector('[data-auth-password-fields]');
|
|
var code = root.querySelector('[data-auth-code-fields]');
|
|
if (!password || !code) return;
|
|
var showCode = code.hidden;
|
|
code.hidden = !showCode;
|
|
password.hidden = showCode;
|
|
button.textContent = showCode ? '使用账号密码登录' : '使用验证码登录';
|
|
}
|
|
|
|
function initRoot(root) {
|
|
root.querySelectorAll('input').forEach(function (input) {
|
|
if (!input.hasAttribute('placeholder')) input.setAttribute('placeholder', ' ');
|
|
});
|
|
var initial = root.dataset.initialTab || 'signin';
|
|
if (initial === 'oauth' && window.matchMedia && window.matchMedia('(max-width: 767px)').matches) initial = 'signin';
|
|
activate(root, initial, false);
|
|
root.addEventListener('click', function (event) {
|
|
var tab = event.target.closest('[data-auth-tab]');
|
|
if (tab) { event.preventDefault(); activate(root, tab.dataset.authTab, true); return; }
|
|
var password = event.target.closest('[data-password-show]');
|
|
if (password) {
|
|
var input = password.closest('.floating-input').querySelector('input');
|
|
input.type = input.type === 'password' ? 'text' : 'password';
|
|
password.querySelector('i').className = input.type === 'password' ? 'fa fas fa-eye' : 'fa fas fa-eye-slash';
|
|
return;
|
|
}
|
|
var send = event.target.closest('[data-send-code]');
|
|
if (send) { event.preventDefault(); sendCode(root, send); return; }
|
|
var mode = event.target.closest('[data-login-mode]');
|
|
if (mode) { event.preventDefault(); toggleLoginMode(root, mode); }
|
|
var refreshQr = event.target.closest('[data-auth-refresh-qr]');
|
|
if (refreshQr) { event.preventDefault(); loadQr(root); }
|
|
});
|
|
root.addEventListener('submit', function (event) {
|
|
var qrForm = event.target.closest('[data-aksrt-weixingzh-code]');
|
|
if (qrForm) {
|
|
event.preventDefault();
|
|
if (!qrForm.reportValidity || qrForm.reportValidity()) submitQrCode(root, qrForm);
|
|
return;
|
|
}
|
|
var form = event.target.closest('[data-auth-form]');
|
|
if (!form) return;
|
|
event.preventDefault();
|
|
if (form.reportValidity && !form.reportValidity()) return;
|
|
submit(root, form);
|
|
});
|
|
}
|
|
|
|
var modal;
|
|
function authPageUrl(tab, redirect) {
|
|
var routes = config.routes || {};
|
|
var base = tab === 'signup' ? routes.register : routes.login;
|
|
var url = new URL(base || '/sign/', window.location.href);
|
|
url.searchParams.set('tab', tab || 'signin');
|
|
if (redirect) url.searchParams.set('redirect_to', redirect);
|
|
return url.toString();
|
|
}
|
|
|
|
function openModal(tab, redirect) {
|
|
if (!modal) {
|
|
window.location.assign(authPageUrl(tab, redirect));
|
|
return;
|
|
}
|
|
modal.classList.add('is-open');
|
|
modal.setAttribute('aria-hidden', 'false');
|
|
document.body.classList.add('auth-modal-open');
|
|
var root = modal.querySelector('[data-aksrt-auth]');
|
|
if (root) {
|
|
activate(root, tab || 'signin', false);
|
|
var redirectInput = root.querySelector('[name="redirect_to"]');
|
|
if (redirectInput && redirect) redirectInput.value = redirect;
|
|
}
|
|
}
|
|
|
|
function closeModal(storeDismissal) {
|
|
if (!modal) return;
|
|
modal.classList.remove('is-open');
|
|
modal.setAttribute('aria-hidden', 'true');
|
|
document.body.classList.remove('auth-modal-open');
|
|
if (storeDismissal && Number(modal.dataset.autoExpires) > 0) {
|
|
var until = Date.now() + Number(modal.dataset.autoExpires) * 3600000;
|
|
window.localStorage.setItem('aksrtAuthAutoUntil', String(until));
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
document.querySelectorAll('[data-aksrt-auth]').forEach(initRoot);
|
|
modal = document.querySelector('[data-auth-modal]');
|
|
document.addEventListener('click', function (event) {
|
|
var trigger = event.target.closest('[data-aksrt-auth-required], .aksrt-login-trigger, #nav-login-btn, .signin-loader, .signup-loader, .qrcode-signin');
|
|
if (!trigger) return;
|
|
event.preventDefault();
|
|
var tab = trigger.classList.contains('signup-loader') ? 'signup' : (trigger.classList.contains('qrcode-signin') ? 'oauth' : 'signin');
|
|
openModal(tab, trigger.dataset.authRedirect || '');
|
|
});
|
|
if (modal) {
|
|
modal.addEventListener('click', function (event) {
|
|
if (event.target.closest('[data-auth-close]')) closeModal(true);
|
|
});
|
|
document.addEventListener('keydown', function (event) { if (event.key === 'Escape') closeModal(true); });
|
|
if (modal.dataset.auto === '1' && Number(window.localStorage.getItem('aksrtAuthAutoUntil') || 0) < Date.now()) {
|
|
window.setTimeout(function () { openModal(modal.querySelector('[data-aksrt-auth]').dataset.initialTab || 'signin'); }, Math.max(1, Number(modal.dataset.autoWait) || 5) * 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
else init();
|
|
}());
|