Files
SHiNE-server/SHiNE-browser-plugin-wallet/popup.js
T

250 lines
8.3 KiB
JavaScript

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();