import { createTopBar } from '../components/topbar.js'; import { authorizeLocalDemoSession, isLocalDemoAvailable, saveEntrySettings, state, } from '../state.js'; import { checkServerAvailabilityByKey, resolveAndCheckShineServerLogin } from '../services/server-health-service.js'; export const pageMeta = { id: 'entry-settings-view', title: 'Настройки входа', showAppChrome: false }; const SERVER_FIELDS = [ { key: 'solanaServer', label: 'Сервер Solana' }, { key: 'shineServerLogin', label: 'Сервер Сияния' }, { key: 'arweaveServer', label: 'Сервер Arweave' }, ]; export function render({ navigate }) { const screen = document.createElement('section'); screen.className = 'stack entry-settings'; const draft = { language: state.entrySettings.language, solanaServer: state.entrySettings.solanaServer, shineServerLogin: state.entrySettings.shineServerLogin, shineServerHttp: state.entrySettings.shineServerHttp, arweaveServer: state.entrySettings.arweaveServer, statuses: { ...state.entrySettings.statuses }, }; const timers = new Map(); let disposed = false; const checks = []; const body = document.createElement('div'); body.className = 'entry-servers'; SERVER_FIELDS.forEach((field) => { const block = document.createElement('div'); block.className = 'entry-server'; const head = document.createElement('div'); head.className = 'entry-server__head'; const title = document.createElement('label'); title.className = 'field-label'; title.htmlFor = `entry-${field.key}`; title.textContent = field.label; // Статус — он же кнопка повторной проверки. const checkButton = document.createElement('button'); checkButton.className = 'text-btn entry-status'; checkButton.type = 'button'; const input = document.createElement('input'); input.className = 'input'; input.id = `entry-${field.key}`; input.type = 'text'; input.autocapitalize = 'off'; input.spellcheck = false; input.value = draft[field.key]; const hint = document.createElement('p'); hint.className = 'entry-server__hint'; const applyStatus = (value, exactAddress = '') => { if (disposed) return; if (value !== 'checking') draft.statuses[field.key] = value; checkButton.classList.remove('is-available', 'is-unavailable', 'is-checking'); checkButton.disabled = false; if (value === 'available') { checkButton.textContent = 'Доступен'; checkButton.classList.add('is-available'); } else if (value === 'unavailable') { checkButton.textContent = 'Недоступен'; checkButton.classList.add('is-unavailable'); } else if (value === 'checking') { checkButton.textContent = 'Проверка…'; checkButton.classList.add('is-checking'); checkButton.disabled = true; } else { checkButton.textContent = 'Проверить'; } checkButton.setAttribute('aria-label', `${field.label}: ${checkButton.textContent}. Проверить ещё раз`); if (field.key === 'shineServerLogin') { const address = exactAddress || draft.shineServerHttp; hint.textContent = address || ''; hint.hidden = !address; } }; let checkSeq = 0; const runCheck = async () => { draft[field.key] = input.value.trim(); window.clearTimeout(timers.get(field.key)); // Результат устаревшей проверки (адрес успели поменять) не должен перезаписать новый. const seq = ++checkSeq; applyStatus('checking'); try { if (field.key === 'shineServerLogin') { const resolved = await resolveAndCheckShineServerLogin(input.value, draft.solanaServer); if (seq !== checkSeq) return; draft.shineServerHttp = resolved.httpBase; draft.shineServer = resolved.wsUrl; applyStatus(resolved.status, resolved.httpBase); } else { const next = await checkServerAvailabilityByKey(field.key, input.value, { solanaEndpoint: draft.solanaServer, }); if (seq !== checkSeq) return; applyStatus(next); } } catch { if (seq === checkSeq) applyStatus('unavailable'); } }; applyStatus(draft.statuses[field.key]); hint.hidden = field.key !== 'shineServerLogin' || !hint.textContent; checks.push(runCheck); checkButton.addEventListener('click', runCheck); input.addEventListener('input', () => { draft[field.key] = input.value; checkSeq += 1; applyStatus('idle'); window.clearTimeout(timers.get(field.key)); timers.set(field.key, window.setTimeout(() => { void runCheck(); }, 3000)); }); input.addEventListener('change', () => { void runCheck(); }); input.addEventListener('keydown', (event) => { if (event.key === 'Enter') { event.preventDefault(); void runCheck(); } }); head.append(title, checkButton); block.append(head, input, hint); body.append(block); }); const actions = document.createElement('div'); actions.className = 'entry-settings__actions'; if (isLocalDemoAvailable()) { const localDemoButton = document.createElement('button'); localDemoButton.className = 'secondary-btn'; localDemoButton.type = 'button'; localDemoButton.textContent = 'Открыть локальный тестовый режим'; localDemoButton.addEventListener('click', () => { if (!authorizeLocalDemoSession()) return; navigate('messages-list'); }); actions.append(localDemoButton); } const serverUiButton = document.createElement('button'); serverUiButton.className = 'secondary-btn'; serverUiButton.type = 'button'; serverUiButton.textContent = 'Настроить свой сервер ↗'; serverUiButton.addEventListener('click', () => { const url = new URL('server-ui.html', window.location.href); window.open(url.toString(), '_blank', 'noopener'); }); const saveButton = document.createElement('button'); saveButton.className = 'primary-btn'; saveButton.type = 'button'; saveButton.textContent = 'Сохранить'; saveButton.addEventListener('click', async () => { try { await saveEntrySettings(draft); navigate('start-view'); } catch (error) { window.alert(error?.message || 'Не удалось сохранить настройки входа.'); } }); actions.prepend(saveButton); actions.append(serverUiButton); screen.append( createTopBar({ title: 'Настройки входа', back: { label: '←', onClick: () => navigate('start-view') }, }), body, actions, ); screen.cleanup = () => { disposed = true; timers.forEach((timerId) => window.clearTimeout(timerId)); }; // Серверы проверяются сразу — зелёный «Доступен» видно без лишних нажатий. checks.forEach((check) => { void check(); }); return screen; }