Улучшен вход пользователя в аккаунт

This commit is contained in:
AidarKC
2026-08-28 16:51:14 +04:00
parent f2fdb8195c
commit 357a05f7ec
25 changed files with 821 additions and 270 deletions
+64 -55
View File
@@ -73,7 +73,7 @@ export function render({ navigate }) {
screen.append(
renderHeader({
title: 'Войти через другое устройство',
leftAction: { label: '←', onClick: () => navigate('login-view') },
leftAction: { label: '←', onClick: () => { void cancelActivePairingAndBack(); } },
}),
);
@@ -82,22 +82,17 @@ export function render({ navigate }) {
panel.innerHTML = '<h1 class="login-panel-title">Войти через другое устройство</h1>';
const formCard = document.createElement('div');
formCard.className = 'card stack';
formCard.className = 'card stack login-device-preparation';
formCard.innerHTML = `
<label class="stack">
<span class="field-label">Введите логин</span>
<input class="input" id="pair-login" type="text" autocomplete="username" placeholder="" value="" />
</label>
<label class="checkbox-row">
<input type="checkbox" id="pair-use-password" />
использовать доп. пароль
</label>
<label class="stack">
<span class="field-label">Пароль подключения</span>
<p class="auth-copy" id="pair-login-label"></p>
<input id="pair-login" type="hidden" value="" />
<input id="pair-use-password" type="checkbox" hidden />
<label class="stack" id="pair-password-wrap" style="display:none;">
<span class="field-label">Дополнительный пароль подключения</span>
<input class="input" id="pair-password" type="password" autocomplete="current-password" placeholder="Пароль, заданный на другом устройстве" />
</label>
<button class="primary-btn" type="button" id="pair-start-btn">Получить код</button>
<p class="meta-muted" id="pair-mode-hint">Получить код для входа можно только если сейчас есть хотя бы одно другое активное устройство этого пользователя в сети.</p>
<button class="primary-btn" type="button" id="pair-start-btn" style="display:none;">Получить код</button>
<p class="meta-muted" id="pair-mode-hint">Создаём код для входа</p>
`;
const status = document.createElement('p');
@@ -110,10 +105,14 @@ export function render({ navigate }) {
resultWrap.innerHTML = codeCardHtml();
const loginInput = formCard.querySelector('#pair-login');
const loginLabelEl = formCard.querySelector('#pair-login-label');
const usePasswordInput = formCard.querySelector('#pair-use-password');
const passwordInput = formCard.querySelector('#pair-password');
const startBtn = formCard.querySelector('#pair-start-btn');
const modeHintEl = formCard.querySelector('#pair-mode-hint');
loginInput.value = String(state.loginDraft.login || '').trim();
loginLabelEl.textContent = loginInput.value ? `Вход для @${loginInput.value}` : '';
const shortCodeEl = resultWrap.querySelector('#pairing-short-code');
const statusHintEl = resultWrap.querySelector('#pairing-status-hint');
const onlineHintEl = resultWrap.querySelector('#pairing-online-hint');
@@ -127,12 +126,11 @@ export function render({ navigate }) {
const syncPasswordUi = () => {
const usePassword = !!usePasswordInput.checked;
passwordInput.parentElement.style.display = usePassword ? '' : 'none';
startBtn.style.display = usePassword ? '' : 'none';
modeHintEl.textContent = usePassword
? 'Получить код для входа можно только если сейчас есть хотя бы одно другое активное устройство этого пользователя в сети. Если на доверённом устройстве включён доп. пароль, введите его.'
: 'Получить код для входа можно только если сейчас есть хотя бы одно другое активное устройство этого пользователя в сети.';
if (!usePassword) {
passwordInput.value = '';
}
? 'На доверённом устройстве для входа по коду включён дополнительный пароль.'
: 'Код можно подтвердить на уже подключённом устройстве. Если оно сейчас не в сети, заявка будет ждать до истечения срока.';
if (!usePassword) passwordInput.value = '';
};
const stopPolling = () => {
@@ -157,6 +155,8 @@ export function render({ navigate }) {
activePairingId = '';
activeExpiresAtMs = 0;
startBtn.disabled = false;
startBtn.style.display = '';
startBtn.textContent = 'Получить новый код';
cancelBtn.style.display = 'none';
resetCodeCard(resultWrap, shortCodeEl, statusHintEl, onlineHintEl, expireHintEl);
setStatus(status, 'Время ожидания истекло. Получите новый код.', 'error');
@@ -310,14 +310,16 @@ export function render({ navigate }) {
setAuthBusy(true);
setAuthError('');
setAuthInfo('');
setStatus(status, 'Проверяем пользователя и создаём pairing-заявку...', 'info');
setStatus(status, 'Создаём код для входа…', 'info');
clearActivePairing();
try {
await authService.reconnect(state.entrySettings.shineServer);
const user = await authService.getUser(login);
if (!user?.exists) {
throw new Error('Пользователь не найден.');
const resolved = await authService.resolveLoginForAuth(login);
if (String(resolved?.resolution || '').toUpperCase() !== 'LOCAL') {
state.loginDraft.login = String(resolved?.login || login).trim();
navigate('login-view');
return;
}
requesterMaterial = await createRequesterPairingMaterial();
@@ -339,59 +341,66 @@ export function render({ navigate }) {
shortCodeEl.textContent = formatPairingShortCode(payload?.shortCode || '');
statusHintEl.textContent = 'Откройте на доверенном устройстве: Настройки -> Устройства -> Подключить устройство -> Подключить по коду.';
onlineHintEl.textContent = payload?.trustedSessionOnline
? 'Сейчас есть хотя бы одна онлайн доверенная сессия, которая может принять заявку.'
: 'Сейчас нет онлайн доверенной сессии. Заявка будет ждать, пока пользователь откроет уже подключённое устройство.';
? 'Доверенное устройство сейчас в сети и может сразу принять заявку.'
: 'Доверенное устройство сейчас не в сети. Заявка будет ждать его подключения.';
resultWrap.style.display = '';
cancelBtn.style.display = '';
startCountdown(payload?.expiresAtMs);
state.loginDraft.login = login;
setStatus(status, 'Код создан. Ожидаем подтверждение на другом устройстве...', 'info');
schedulePoll();
} catch (error) {
startBtn.disabled = false;
const message = toUserMessage(error, 'Не удалось начать вход через другое устройство.');
setAuthError(message);
setStatus(status, message, 'error');
if (String(error?.code || '').toUpperCase() === 'PAIRING_PASSWORD_INVALID' && !usePassword) {
usePasswordInput.checked = true;
syncPasswordUi();
modeHintEl.textContent = 'Для этого аккаунта включён дополнительный пароль подключения. Введите его, чтобы получить код.';
setStatus(status, 'Введите дополнительный пароль подключения.', 'info');
window.setTimeout(() => passwordInput.focus(), 0);
} else {
startBtn.style.display = '';
startBtn.textContent = 'Повторить';
const message = toUserMessage(error, 'Не удалось начать вход через другое устройство.');
setAuthError(message);
setStatus(status, message, 'error');
}
} finally {
setAuthBusy(false);
}
});
cancelBtn.addEventListener('click', async () => {
if (!activePairingId || !requesterMaterial?.sessionKey) {
clearActivePairing();
startBtn.disabled = false;
cancelBtn.style.display = 'none';
return;
const cancelActivePairingAndBack = async () => {
const pairingId = activePairingId;
const requesterSessionKey = requesterMaterial?.sessionKey;
isDisposed = true;
stopPolling();
stopCountdown();
if (pairingId && requesterSessionKey) {
try {
await authService.cancelTrustedDeviceLogin(pairingId, requesterSessionKey);
} catch {
// Навигацию назад не блокируем из-за ошибки отмены уже созданной заявки.
}
}
cancelBtn.disabled = true;
try {
await authService.cancelTrustedDeviceLogin(activePairingId, requesterMaterial.sessionKey);
clearActivePairing();
startBtn.disabled = false;
setStatus(status, 'Ожидание подключения отменено.', 'info');
} catch (error) {
const message = toUserMessage(error, 'Не удалось отменить ожидание подключения.');
setAuthError(message);
setStatus(status, message, 'error');
} finally {
cancelBtn.disabled = false;
cancelBtn.style.display = activePairingId ? '' : 'none';
}
});
navigate('login-view');
};
screen.cleanup = () => {
isDisposed = true;
stopPolling();
stopCountdown();
if (activePairingId && requesterMaterial?.sessionKey) {
void authService.cancelTrustedDeviceLogin(activePairingId, requesterMaterial.sessionKey).catch(() => {});
}
};
const resultActions = document.createElement('div');
resultActions.className = 'row';
resultActions.append(cancelBtn);
resultWrap.append(resultActions);
panel.append(formCard, status, resultWrap);
screen.append(panel);
if (!String(loginInput.value || '').trim()) {
window.setTimeout(() => navigate('login-view'), 0);
} else {
// После проверки логина на предыдущем экране код создаётся сразу.
window.setTimeout(() => startBtn.click(), 0);
}
return screen;
}