245 lines
8.1 KiB
JavaScript
245 lines
8.1 KiB
JavaScript
const els = {
|
||
serverUrl: document.querySelector('#server-url'),
|
||
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: {
|
||
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?.serverUrl || 'wss://shineup.me/ws');
|
||
const loginValue = String(state?.settings?.login || '');
|
||
if (document.activeElement !== els.serverUrl) {
|
||
els.serverUrl.value = serverValue;
|
||
}
|
||
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', {
|
||
serverUrl: 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 || ''),
|
||
serverUrl: 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();
|