const els = { serverUrl: document.querySelector('#server-url'), serverAddress: document.querySelector('#server-address'), loginInput: document.querySelector('#login-input'), usePassword: document.querySelector('#use-password'), passwordField: document.querySelector('#password-field'), passwordInput: document.querySelector('#password-input'), startBtn: document.querySelector('#start-btn'), pairingCard: document.querySelector('#pairing-card'), shortCode: document.querySelector('#short-code'), pairingHint: document.querySelector('#pairing-hint'), pairingExpire: document.querySelector('#pairing-expire'), cancelBtn: document.querySelector('#cancel-btn'), status: document.querySelector('#status'), sessionCard: document.querySelector('#session-card'), sessionLogin: document.querySelector('#session-login'), sessionId: document.querySelector('#session-id'), sessionType: document.querySelector('#session-type'), resumeBtn: document.querySelector('#resume-btn'), disconnectBtn: document.querySelector('#disconnect-btn'), connectionPill: document.querySelector('#connection-pill'), }; let state = { settings: { serverLogin: 'shineupme', serverHttp: 'https://shineup.me', serverUrl: 'wss://shineup.me/ws', login: '', }, pairing: { active: false, pairingId: '', expiresAtMs: 0, }, session: null, connectionOnline: false, status: { text: '', kind: 'info', }, }; let refreshTimer = 0; let saveSettingsTimer = 0; function setStatus(message, kind = 'info') { els.status.textContent = String(message || ''); els.status.className = `status ${kind === 'error' ? 'error' : 'info'}`; els.status.classList.toggle('hidden', !message); } function setConnectedPill(connected) { els.connectionPill.textContent = connected ? 'online' : 'offline'; els.connectionPill.className = connected ? 'pill pill-online' : 'pill pill-offline'; } function formatRemaining(ms) { const safe = Math.max(0, Math.floor(Number(ms || 0) / 1000)); const minutes = Math.floor(safe / 60); const seconds = safe % 60; return `${minutes} мин ${seconds} сек`; } function applyState(nextState) { state = nextState || state; const serverValue = String(state?.settings?.serverLogin || 'shineupme'); const serverAddressValue = String(state?.settings?.serverHttp || 'https://shineup.me'); const loginValue = String(state?.settings?.login || ''); if (document.activeElement !== els.serverUrl) { els.serverUrl.value = serverValue; } els.serverAddress.textContent = `Текущий адрес: ${serverAddressValue}`; if (document.activeElement !== els.loginInput) { els.loginInput.value = loginValue; } setConnectedPill(!!state?.connectionOnline); setStatus(state?.status?.text || '', state?.status?.kind || 'info'); const session = state?.session; if (session) { els.sessionCard.classList.remove('hidden'); els.sessionLogin.textContent = session.login || '—'; els.sessionId.textContent = session.sessionId || '—'; els.sessionType.textContent = String(session.sessionType || 50) === '50' ? 'wallet' : String(session.sessionType || '—'); } else { els.sessionCard.classList.add('hidden'); els.sessionLogin.textContent = '—'; els.sessionId.textContent = '—'; els.sessionType.textContent = 'wallet'; } const pairing = state?.pairing || {}; if (pairing.active) { els.pairingCard.classList.remove('hidden'); const shortCode = String(pairing.shortCode || els.shortCode.dataset.shortCode || els.shortCode.textContent || '0000000'); els.shortCode.dataset.shortCode = shortCode; els.shortCode.textContent = shortCode; els.pairingHint.textContent = pairing.trustedSessionOnline ? 'Покажите код на доверенном устройстве и подтвердите выпуск wallet-session.' : 'Сейчас нет онлайн доверенной сессии. Откройте другое устройство и подтвердите заявку.'; const leftMs = Number(pairing.expiresAtMs || 0) - Date.now(); els.pairingExpire.textContent = leftMs > 0 ? `Код действителен ещё ${formatRemaining(leftMs)}.` : 'Время ожидания истекло.'; els.startBtn.disabled = true; } else { els.pairingCard.classList.add('hidden'); els.shortCode.textContent = '0000000'; delete els.shortCode.dataset.shortCode; els.pairingExpire.textContent = ''; els.startBtn.disabled = false; } } function normalizeError(response, fallback) { return response?.error || fallback || 'Unknown error'; } function sendMessage(type, payload = {}) { return new Promise((resolve, reject) => { chrome.runtime.sendMessage({ type, payload }, (response) => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message || 'Runtime message failed')); return; } if (!response?.ok) { reject(new Error(normalizeError(response, 'Wallet operation failed'))); return; } if (response?.state) applyState(response.state); resolve(response); }); }); } async function refreshState() { const response = await sendMessage('wallet:getState'); applyState(response.state); } async function saveSettings() { await sendMessage('wallet:saveSettings', { serverLogin: String(els.serverUrl.value || '').trim(), login: String(els.loginInput.value || '').trim(), }); } function scheduleSaveSettings() { if (saveSettingsTimer) { window.clearTimeout(saveSettingsTimer); } saveSettingsTimer = window.setTimeout(() => { saveSettingsTimer = 0; void saveSettings(); }, 250); } async function startPairing() { const login = String(els.loginInput.value || '').trim(); if (!login) { setStatus('Введите логин.', 'error'); return; } setStatus('Создаём wallet-session заявку...', 'info'); els.startBtn.disabled = true; try { const response = await sendMessage('wallet:startPairing', { login, usePassword: !!els.usePassword.checked, password: String(els.passwordInput.value || ''), serverLogin: String(els.serverUrl.value || '').trim(), }); applyState(response.state); } catch (error) { els.startBtn.disabled = false; setStatus(error.message || 'Не удалось начать pairing.', 'error'); } } async function cancelPairing() { try { await sendMessage('wallet:cancelPairing'); } catch (error) { setStatus(error.message || 'Не удалось отменить pairing.', 'error'); } } async function resumeSession() { setStatus('Проверяем сохранённую wallet-session...', 'info'); try { await sendMessage('wallet:resumeSession'); } catch (error) { setStatus(error.message || 'Не удалось восстановить session.', 'error'); } } async function disconnectSession() { try { await sendMessage('wallet:disconnectSession'); } catch (error) { setStatus(error.message || 'Не удалось удалить session.', 'error'); } } function startUiRefreshLoop() { stopUiRefreshLoop(); refreshTimer = window.setInterval(() => { void refreshState(); }, 1000); } function stopUiRefreshLoop() { if (refreshTimer) { window.clearInterval(refreshTimer); refreshTimer = 0; } } function bindUi() { els.usePassword.addEventListener('change', () => { els.passwordField.classList.toggle('hidden', !els.usePassword.checked); if (!els.usePassword.checked) { els.passwordInput.value = ''; } }); els.serverUrl.addEventListener('input', () => { scheduleSaveSettings(); }); els.serverUrl.addEventListener('change', () => { void saveSettings(); }); els.loginInput.addEventListener('input', () => { scheduleSaveSettings(); }); els.loginInput.addEventListener('change', () => { void saveSettings(); }); els.startBtn.addEventListener('click', () => { void startPairing(); }); els.cancelBtn.addEventListener('click', () => { void cancelPairing(); }); els.resumeBtn.addEventListener('click', () => { void resumeSession(); }); els.disconnectBtn.addEventListener('click', () => { void disconnectSession(); }); } async function init() { bindUi(); await refreshState(); startUiRefreshLoop(); } window.addEventListener('beforeunload', () => { stopUiRefreshLoop(); if (saveSettingsTimer) { window.clearTimeout(saveSettingsTimer); saveSettingsTimer = 0; } }); void init();