import { renderHeader } from '../components/header.js'; import { authService, clearAuthMessages, state } from '../state.js'; import { toUserMessage } from '../services/ui-error-texts.js'; export const pageMeta = { id: 'register-view', title: 'Зарегистрироваться', showAppChrome: false }; export function render({ navigate }) { const screen = document.createElement('section'); screen.className = 'stack'; clearAuthMessages(); const form = document.createElement('div'); form.className = 'card stack'; const loginInput = document.createElement('input'); loginInput.className = 'input'; loginInput.type = 'text'; loginInput.value = state.registrationDraft.login; loginInput.placeholder = 'Введите логин'; const passwordInput = document.createElement('input'); passwordInput.className = 'input'; passwordInput.type = 'password'; passwordInput.value = state.registrationDraft.password; passwordInput.placeholder = 'Введите пароль (можно оставить пустым)'; const statusText = document.createElement('p'); statusText.className = 'meta-muted'; statusText.textContent = 'Проверка логина: не выполнена'; const formError = document.createElement('p'); formError.className = 'status-line is-unavailable'; formError.style.display = 'none'; const advanced = document.createElement('details'); advanced.className = 'card stack'; advanced.innerHTML = ` Расширенные

Схема derivation ключей: при непустом пароле используется Argon2id (средний профиль), затем из результата строится секрет для root/bch/dev ключей.

В derivation участвуют и логин, и пароль: одинаковый пароль у разных логинов даёт разные ключи.

Если пароль пустой — используется прежний тестовый режим совместимости (старый детерминированный вариант).

Для тесто оставьте пустой пароль.

Профиль Argon2id сейчас фиксированный: t=3, m=262144 KiB (256 MB), p=1, dkLen=32. Выбор уровня будет добавлен позже.

`; const checkButton = document.createElement('button'); checkButton.className = 'ghost-btn'; checkButton.type = 'button'; checkButton.textContent = 'Проверить логин'; async function runAvailabilityCheck() { const login = loginInput.value.trim(); if (!login) { statusText.textContent = 'Введите логин'; formError.style.display = 'none'; return false; } checkButton.disabled = true; checkButton.textContent = 'Проверка...'; try { await authService.reconnect(state.entrySettings.shineServer); const isFree = await authService.ensureLoginFree(login); statusText.textContent = isFree ? 'Логин свободен ✅' : 'Логин уже занят ❌'; statusText.className = isFree ? 'is-available' : 'is-unavailable'; formError.style.display = 'none'; return isFree; } catch (error) { statusText.textContent = toUserMessage(error, 'Не удалось проверить логин'); statusText.className = 'is-unavailable'; return false; } finally { checkButton.disabled = false; checkButton.textContent = 'Проверить логин'; } } checkButton.addEventListener('click', runAvailabilityCheck); form.innerHTML = ` `; form.children[0].append(loginInput); form.children[1].append(passwordInput); form.append(checkButton, statusText, advanced, formError); const actions = document.createElement('div'); actions.className = 'auth-footer-actions'; const backButton = document.createElement('button'); backButton.className = 'ghost-btn'; backButton.type = 'button'; backButton.textContent = 'Назад'; backButton.addEventListener('click', () => navigate('start-view')); const nextButton = document.createElement('button'); nextButton.className = 'primary-btn'; nextButton.type = 'button'; nextButton.textContent = 'Далее'; nextButton.addEventListener('click', async () => { formError.style.display = 'none'; const isFree = await runAvailabilityCheck(); if (!isFree) return; state.registrationDraft.login = loginInput.value.trim(); state.registrationDraft.password = passwordInput.value; state.registrationDraft.preGeneratedKeyBundle = null; // Показываем информационный экран пока генерируются ключи form.innerHTML = ''; const infoMsg = document.createElement('p'); infoMsg.className = 'auth-copy'; infoMsg.textContent = 'Из вашего логина и пароля (надеемся, что вы выбрали достаточно длинный и надёжный пароль) ' + 'генерируется секрет, из которого получаются root key, blockchain key и device key.'; const spinnerMsg = document.createElement('p'); spinnerMsg.className = 'meta-muted'; spinnerMsg.textContent = 'Генерация ключей...'; const genError = document.createElement('p'); genError.className = 'status-line is-unavailable'; genError.style.display = 'none'; form.append(infoMsg, spinnerMsg, genError); nextButton.disabled = true; backButton.disabled = true; try { const keyBundle = await authService.derivePasswordKeyBundle( state.registrationDraft.login, state.registrationDraft.password, ); state.registrationDraft.preGeneratedKeyBundle = keyBundle; navigate('registration-payment-view'); } catch (error) { genError.textContent = `Ошибка генерации ключей: ${error?.message || 'неизвестная ошибка'}`; genError.style.display = ''; spinnerMsg.style.display = 'none'; nextButton.disabled = false; backButton.disabled = false; } }); actions.append(backButton, nextButton); screen.append( renderHeader({ title: 'Зарегистрироваться', leftAction: { label: '←', onClick: () => navigate('start-view') }, }), form, actions, ); return screen; }