/**
* BridgeSwarm Dashboard - UI logic
*/
const SETTINGS_KEY = 'bridgeSwarmSettings';
let currentState = null;
let settings = {};
function $(id) {
return document.getElementById(id);
}
function log(...args) {
console.log('[BridgeSwarm-dashboard]', ...args);
}
// Format time ago
function timeAgo(timestamp) {
const seconds = Math.floor((Date.now() - timestamp) / 1000);
if (seconds < 60) return seconds + 's ago';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return minutes + 'm ago';
const hours = Math.floor(minutes / 60);
if (hours < 24) return hours + 'h ago';
return Math.floor(hours / 24) + 'd ago';
}
// Format uptime
function formatUptime(ms) {
const seconds = Math.floor(ms / 1000);
if (seconds < 60) return seconds + 's';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return minutes + 'm ' + (seconds % 60) + 's';
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
return hours + 'h ' + mins + 'm';
}
// Truncate string
function truncate(str, len = 16) {
if (!str) return '';
return str.length > len ? str.slice(0, len) + '…' : str;
}
// Fetch state from background
async function fetchState() {
return new Promise((resolve) => {
chrome.runtime.sendMessage(
{ target: 'bridge-swarm-native', action: 'getState' },
(response) => {
if (response && response.ok) {
resolve(response.state);
} else {
resolve(null);
}
}
);
});
}
// Load settings
function loadSettings() {
chrome.storage.local.get(SETTINGS_KEY, (result) => {
settings = result[SETTINGS_KEY] || {};
updateSettingsUI();
});
}
// Update settings UI
function updateSettingsUI() {
$('toggleNotify').classList.toggle('active', settings.notifyOnDisconnect === true);
$('toggleDebug').classList.toggle('active', settings.debug === true);
$('toggleDisableFileUrls')?.classList.toggle('active', settings.disableOnFileUrls === true);
$('defaultAppName').value = settings.defaultAppName || 'bridge-swarm';
$('defaultMaxPeers').value = settings.defaultMaxPeers || '';
$('defaultRequestTimeoutMs').value = settings.defaultRequestTimeoutMs || '';
$('readyTimeoutMs').value = settings.readyTimeoutMs || '';
}
// Save settings
function saveSettings() {
settings.notifyOnDisconnect = $('toggleNotify').classList.contains('active');
settings.debug = $('toggleDebug').classList.contains('active');
settings.disableOnFileUrls = $('toggleDisableFileUrls')?.classList.contains('active') || false;
settings.defaultAppName = $('defaultAppName').value || 'bridge-swarm';
settings.defaultMaxPeers = parseInt($('defaultMaxPeers').value) || 0;
settings.defaultRequestTimeoutMs = parseInt($('defaultRequestTimeoutMs').value) || 0;
settings.readyTimeoutMs = parseInt($('readyTimeoutMs').value) || 0;
chrome.storage.local.set({ [SETTINGS_KEY]: settings }, () => {
log('Settings saved');
});
}
// Update dashboard
function updateDashboard(state) {
currentState = state;
// Status banner
const banner = $('statusBanner');
const statusText = $('statusText');
if (state.hostConnected) {
banner.className = 'status-banner connected';
statusText.textContent = 'Native host connected';
} else {
banner.className = 'status-banner disconnected';
statusText.textContent = 'Native host disconnected';
}
// Stats
$('dashSwarms').textContent = state.stats.totalSwarms;
$('dashConnections').textContent = state.stats.totalConnections;
$('dashTabs').textContent = state.tabs.length;
$('dashUptime').textContent = formatUptime(Date.now() - state.stats.uptime);
// Badges
$('swarmCount').textContent = state.stats.totalSwarms;
$('connCount').textContent = state.stats.totalConnections;
$('tabCount').textContent = state.tabs.length;
// Recent connections
const connections = [];
for (const swarm of state.swarms) {
for (const conn of swarm.connections) {
connections.push(conn);
}
}
const recentConnBody = $('recentConnections');
if (connections.length === 0) {
recentConnBody.innerHTML = '
| No active connections |
';
} else {
recentConnBody.innerHTML = connections.slice(0, 10).map(conn => `
| ${truncate(conn.connId)} |
${truncate(conn.swarmId, 12)} |
${truncate(conn.peerKey, 12)} |
${timeAgo(conn.createdAt)} |
`).join('');
}
}
// Update swarms table
function updateSwarmsTable(state) {
const tbody = $('swarmsTable');
if (state.swarms.length === 0) {
tbody.innerHTML = '| No active swarms |
';
return;
}
tbody.innerHTML = state.swarms.map(swarm => `
| ${truncate(swarm.swarmId, 20)} |
${swarm.appName} |
${swarm.connections.length} |
${swarm.tabCount} |
|
`).join('');
}
// Update connections table
function updateConnectionsTable(state) {
const connections = [];
for (const swarm of state.swarms) {
for (const conn of swarm.connections) {
connections.push(conn);
}
}
const tbody = $('connectionsTable');
if (connections.length === 0) {
tbody.innerHTML = '| No active connections |
';
return;
}
tbody.innerHTML = connections.map(conn => `
| ${truncate(conn.connId, 20)} |
${truncate(conn.swarmId, 16)} |
${truncate(conn.peerKey, 16)} |
${timeAgo(conn.createdAt)} |
`).join('');
}
// Update tabs table
function updateTabsTable(state) {
const tbody = $('tabsTable');
if (state.tabs.length === 0) {
tbody.innerHTML = '| No active tabs |
';
return;
}
tbody.innerHTML = state.tabs.map(tab => `
| ${tab.tabId} |
${tab.swarmIds.map(id => `${truncate(id, 12)}`).join(', ') || 'none'} |
`).join('');
}
// Destroy swarm
async function destroySwarm(swarmId) {
if (!confirm(`Destroy swarm "${swarmId}"? This will disconnect all peers.`)) {
return;
}
chrome.runtime.sendMessage(
{ target: 'bridge-swarm-native', action: 'destroySwarm', payload: { swarmId } },
(response) => {
if (response && response.ok) {
log('Swarm destroyed:', swarmId);
refresh();
} else {
log('Failed to destroy swarm:', response?.error);
}
}
);
}
// Make destroySwarm global
window.destroySwarm = destroySwarm;
// Refresh all data
async function refresh() {
const state = await fetchState();
if (state) {
updateDashboard(state);
updateSwarmsTable(state);
updateConnectionsTable(state);
updateTabsTable(state);
}
}
// Navigation
function setupNavigation() {
const navItems = document.querySelectorAll('.nav-item');
const pages = document.querySelectorAll('.page');
navItems.forEach(item => {
item.addEventListener('click', () => {
const page = item.dataset.page;
// Update nav
navItems.forEach(i => i.classList.remove('active'));
item.classList.add('active');
// Update page
pages.forEach(p => p.classList.remove('active'));
$(`page-${page}`).classList.add('active');
});
});
}
// Setup event listeners
function setupEvents() {
// Refresh button
$('refreshBtn').addEventListener('click', refresh);
// Toggle switches
document.querySelectorAll('.toggle').forEach(toggle => {
toggle.addEventListener('click', () => {
toggle.classList.toggle('active');
});
});
// Save settings
$('btnSaveSettings').addEventListener('click', saveSettings);
// Logs functionality
let logs = [];
let autoScroll = true;
// Register as dashboard
chrome.runtime.sendMessage(
{ target: 'bridge-swarm-native', action: 'registerDashboard' },
(response) => {
if (response && response.logs) {
logs = response.logs;
updateLogsDisplay();
}
}
);
// Listen for log updates
chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'bridge-swarm-logs' && message.logs) {
logs = message.logs;
updateLogsDisplay();
}
});
function updateLogsDisplay() {
const container = $('logsContainer');
if (!container) return;
if (logs.length === 0) {
container.innerHTML = 'No logs yet
';
return;
}
container.innerHTML = logs.map(entry => {
const time = new Date(entry.timestamp).toLocaleTimeString();
return `
${time}
${escapeHtml(entry.message)}
`;
}).join('');
if (autoScroll) {
container.scrollTop = container.scrollHeight;
}
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// Clear logs button
$('btnClearLogs')?.addEventListener('click', () => {
logs = [];
updateLogsDisplay();
});
// Auto-scroll toggle
$('btnAutoScroll')?.addEventListener('click', () => {
autoScroll = !autoScroll;
$('btnAutoScroll').textContent = 'Auto-scroll: ' + (autoScroll ? 'ON' : 'OFF');
});
// Unregister dashboard on page unload
window.addEventListener('beforeunload', () => {
chrome.runtime.sendMessage(
{ target: 'bridge-swarm-native', action: 'unregisterDashboard' },
() => {}
);
});
}
// Initialize
async function init() {
log('Dashboard initializing...');
setupNavigation();
setupEvents();
loadSettings();
await refresh();
// Auto-refresh every 2 seconds
setInterval(refresh, 2000);
}
init();