verstak/extension-firefox/popup/popup.js

225 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Verstak Bridge — Popup Script
// Shows connection status, queue count, recent events, and settings.
const STORAGE_KEY = 'verstak_queue';
const BRIDGE_KEY = 'verstak_bridge_config';
const RECENT_KEY = 'verstak_recent';
const DEFAULT_PORT = 9786;
document.addEventListener('DOMContentLoaded', () => {
// Force a fresh ping check on popup open, don't rely on stale cache
chrome.runtime.sendMessage({ type: 'FORCE_PING' });
// Read cached state for immediate render
restoreToggleState();
restoreSettingsPanel();
updateUI();
document.getElementById('sync-btn').addEventListener('click', forceSync);
document.getElementById('track-toggle').addEventListener('change', onToggle);
document.getElementById('settings-btn').addEventListener('click', toggleSettings);
document.getElementById('save-port-btn').addEventListener('click', savePort);
document.getElementById('test-port-btn').addEventListener('click', testPort);
});
function updateUI() {
chrome.storage.local.get([STORAGE_KEY, BRIDGE_KEY, RECENT_KEY], (data) => {
const queue = data[STORAGE_KEY] || [];
const config = data[BRIDGE_KEY] || {};
const recent = data[RECENT_KEY] || [];
// Queue count
document.getElementById('queue-count').textContent = queue.length;
// Bridge status
const statusEl = document.getElementById('bridge-status');
const portEl = document.getElementById('bridge-port');
const dotEl = document.getElementById('status-dot');
if (config.bridgeReachable === true) {
statusEl.textContent = 'Доступен';
statusEl.className = 'value online';
dotEl.className = 'dot online';
portEl.textContent = '127.0.0.1:' + (config.port || DEFAULT_PORT);
} else if (config.bridgeReachable === false) {
statusEl.textContent = 'Недоступен';
statusEl.className = 'value offline';
dotEl.className = 'dot offline';
portEl.textContent = '127.0.0.1:' + (config.port || DEFAULT_PORT);
} else {
// Unknown — not yet checked
statusEl.textContent = 'Проверка...';
statusEl.className = 'value';
dotEl.className = 'dot';
portEl.textContent = '127.0.0.1:' + (config.port || DEFAULT_PORT);
}
// Update settings panel port field
const portInput = document.getElementById('port-input');
if (portInput && !portInput.dataset.userEdited) {
portInput.value = config.port || DEFAULT_PORT;
}
// Recent events
renderRecent(recent, config.bridgeReachable);
});
}
function renderRecent(events, reachable) {
const list = document.getElementById('event-list');
list.innerHTML = '';
if (!events || events.length === 0) {
list.innerHTML = '<p class="empty">Нет событий</p>';
return;
}
const lastFive = events.slice(-5).reverse();
for (const ev of lastFive) {
const item = document.createElement('div');
item.className = 'event-item';
item.innerHTML = `
<div class="domain">${escapeHtml(ev.domain || '?')}</div>
<div class="title">${escapeHtml(truncate(ev.title || ev.url, 60))}</div>
<div class="duration">${ev.active_seconds || 0}с${reachable ? '' : ' (ожидает)'}</div>
`;
list.appendChild(item);
}
}
function forceSync() {
const btn = document.getElementById('sync-btn');
btn.disabled = true;
btn.textContent = '⏳ Отправка...';
chrome.runtime.sendMessage({ type: 'FORCE_FLUSH' }, () => {
setTimeout(() => {
updateUI();
btn.disabled = false;
btn.textContent = '🔄 Отправить сейчас';
}, 1000);
});
}
function onToggle(e) {
const enabled = e.target.checked;
chrome.storage.local.set({ 'verstak_tracking_enabled': enabled });
chrome.runtime.sendMessage({ type: 'SET_TRACKING', enabled });
}
function restoreToggleState() {
chrome.storage.local.get('verstak_tracking_enabled', (data) => {
const enabled = data.verstak_tracking_enabled !== false;
document.getElementById('track-toggle').checked = enabled;
});
}
// --- Settings panel ---
function toggleSettings() {
const panel = document.getElementById('settings-panel');
const main = document.getElementById('main-panel');
const btn = document.getElementById('settings-btn');
if (panel.style.display === 'none' || !panel.style.display) {
panel.style.display = 'block';
main.style.display = 'none';
btn.textContent = '← Назад';
} else {
panel.style.display = 'none';
main.style.display = 'block';
btn.textContent = '⚙';
}
}
function restoreSettingsPanel() {
chrome.storage.local.get(BRIDGE_KEY, (data) => {
const config = data[BRIDGE_KEY] || {};
const portInput = document.getElementById('port-input');
if (portInput) {
portInput.value = config.port || DEFAULT_PORT;
}
});
}
function savePort() {
const portInput = document.getElementById('port-input');
const port = parseInt(portInput.value, 10);
const statusEl = document.getElementById('port-status');
if (isNaN(port) || port < 1024 || port > 65535) {
statusEl.textContent = 'Порт: 102465535';
statusEl.className = 'port-status error';
return;
}
chrome.storage.local.get(BRIDGE_KEY, (data) => {
const config = data[BRIDGE_KEY] || {};
config.port = port;
config.bridgeReachable = null; // Force re-check on next popup open
chrome.storage.local.set({ [BRIDGE_KEY]: config }, () => {
statusEl.textContent = 'Сохранено';
statusEl.className = 'port-status ok';
// Trigger immediate ping with new port
chrome.runtime.sendMessage({ type: 'FORCE_PING' });
setTimeout(() => { updateUI(); statusEl.textContent = ''; }, 1500);
});
});
}
function testPort() {
const portInput = document.getElementById('port-input');
const port = parseInt(portInput.value, 10);
const statusEl = document.getElementById('port-status');
if (isNaN(port) || port < 1024 || port > 65535) {
statusEl.textContent = 'Порт: 102465535';
statusEl.className = 'port-status error';
return;
}
statusEl.textContent = 'Проверка...';
statusEl.className = 'port-status';
fetch(`http://127.0.0.1:${port}/api/ping`, { signal: AbortSignal.timeout(3000) })
.then((res) => {
if (res.ok) {
statusEl.textContent = '✓ Сервер отвечает';
statusEl.className = 'port-status ok';
} else {
statusEl.textContent = `✗ Ошибка ${res.status}`;
statusEl.className = 'port-status error';
}
})
.catch((err) => {
if (err.name === 'TimeoutError' || err.name === 'AbortError') {
statusEl.textContent = '✗ Нет ответа (таймаут). Возможно, порт занят другим приложением или Verstak не запущен';
} else {
statusEl.textContent = '✗ Сервер недоступен';
}
statusEl.className = 'port-status error';
});
}
// Listen for updates from background
chrome.runtime.onMessage.addListener((msg) => {
if (msg.type === 'UI_UPDATE') {
updateUI();
}
});
// --- Helpers ---
function truncate(s, n) {
if (!s) return '';
return s.length > n ? s.substring(0, n) + '...' : s;
}
function escapeHtml(s) {
if (!s) return '';
return s
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}