713 lines
27 KiB
JavaScript
713 lines
27 KiB
JavaScript
|
|
/* ================================================================
|
|
IMPORTS
|
|
================================================================ */
|
|
import { initNDKPage, getPubkey, disconnect, getRelayData, getRelayStats, reconnectRelay, publishEvent, getVersion, updateVersionDisplay } from './js/init-ndk.mjs';
|
|
import { HamburgerMorphing } from "./hamburger_morphing/hamburger.mjs";
|
|
import { initFooterRelayStatus, updateFooterRelayStatus, initSidenavRelaySection, updateSidenavRelaySection, setRelayActivityState } from './js/relay-ui.mjs';
|
|
|
|
const versionInfo = await getVersion();
|
|
const VERSION = versionInfo.VERSION;
|
|
console.log(`[relays.html ${VERSION}] Loading...`);
|
|
|
|
/* ================================================================
|
|
SVG ICONS
|
|
================================================================ */
|
|
const SVG_UNCHECKED = `<svg class="svgBtn svgBtn_unsel" viewBox="0 0 10 10"><rect x="2" y="2" width="6" height="6" /></svg>`;
|
|
const SVG_CHECKED = `<svg class="svgBtn svgBtn_sel" viewBox="0 0 10 10"><path d="M2,6 L4,8 L8,2 " /></svg>`;
|
|
const SVG_CONNECTING = `<svg class="svgBtn" style="stroke: var(--muted-color); opacity: 0.5;" viewBox="0 0 10 10"><path d="M2,6 L4,8 L8,2 " /></svg>`;
|
|
const SVG_X_UNCHECKED = `<svg class="svgBtn svgBtn_unsel" viewBox="0 0 10 10"><path d="M2,2 L8,8 M8,2 L2,8" /></svg>`;
|
|
const SVG_X_PENDING = `<svg class="svgBtn svgBtn_sel" viewBox="0 0 10 10"><path d="M2,2 L8,8 M8,2 L2,8" /></svg>`;
|
|
|
|
/* ================================================================
|
|
GLOBAL VARIABLES
|
|
================================================================ */
|
|
let updateIntervalId = null;
|
|
let currentPubkey = null;
|
|
let reconnectingRelays = new Set(); // Track which relays are reconnecting
|
|
let currentRelayList = []; // Store current relay list with types
|
|
let pendingRemovalRelayUrl = null; // Two-click confirmation for relay deletion
|
|
let addRelayCanRead = true;
|
|
let addRelayCanWrite = true;
|
|
|
|
// Hamburger menu
|
|
let hamburgerInstance = null;
|
|
let isNavOpen = false;
|
|
|
|
// Version bar buttons
|
|
let logoutHamburger = null;
|
|
let themeToggleHamburger = null;
|
|
let isDarkMode = false;
|
|
|
|
/* ================================================================
|
|
DOM VARIABLES
|
|
================================================================ */
|
|
const divBody = document.getElementById("divBody");
|
|
const divSideNav = document.getElementById("divSideNav");
|
|
const divSideNavBody = document.getElementById("divSideNavBody");
|
|
const divRelays = document.getElementById("divRelays");
|
|
const divFooterCenter = document.getElementById("divFooterCenter");
|
|
const divFooterRight = document.getElementById("divFooterRight");
|
|
const txtRelayEvents = document.getElementById("txtRelayEvents");
|
|
|
|
/* ================================================================
|
|
HAMBURGER MENU
|
|
================================================================ */
|
|
function initHamburgerMenu() {
|
|
hamburgerInstance = new HamburgerMorphing('#divSvgHam', {
|
|
foreground: 'var(--primary-color)',
|
|
background: 'var(--secondary-color)',
|
|
hover: 'var(--accent-color)'
|
|
});
|
|
hamburgerInstance.animateTo('burger');
|
|
}
|
|
|
|
/* ================================================================
|
|
SIDENAV FUNCTIONS
|
|
================================================================ */
|
|
function openNav() {
|
|
divSideNav.style.zIndex = 3;
|
|
divSideNav.style.width = "50vw";
|
|
isNavOpen = true;
|
|
if (hamburgerInstance) {
|
|
hamburgerInstance.animateTo('arrow_left');
|
|
}
|
|
divSideNavBody.innerHTML = "Relay management options coming soon...";
|
|
|
|
// Initialize version bar buttons when sidenav opens (lazy load)
|
|
if (!logoutHamburger) {
|
|
logoutHamburger = new HamburgerMorphing('#logoutHamburgerContainer', {
|
|
size: 24,
|
|
foreground: 'var(--primary-color)',
|
|
background: 'var(--secondary-color)',
|
|
hover: 'var(--accent-color)'
|
|
});
|
|
logoutHamburger.animateTo('x');
|
|
}
|
|
|
|
if (!themeToggleHamburger) {
|
|
themeToggleHamburger = new HamburgerMorphing('#themeToggleHamburgerContainer', {
|
|
size: 24,
|
|
foreground: 'var(--primary-color)',
|
|
background: 'var(--secondary-color)',
|
|
hover: 'var(--accent-color)'
|
|
});
|
|
|
|
// Determine current theme
|
|
const savedTheme = localStorage.getItem('theme');
|
|
isDarkMode = savedTheme === 'dark' || document.body.classList.contains('dark-mode');
|
|
const initialShape = isDarkMode ? 'moon' : 'circle';
|
|
themeToggleHamburger.animateTo(initialShape);
|
|
}
|
|
}
|
|
|
|
function closeNav() {
|
|
divSideNav.style.width = "0vw";
|
|
divSideNav.style.zIndex = -1;
|
|
isNavOpen = false;
|
|
if (hamburgerInstance) {
|
|
hamburgerInstance.animateTo('burger');
|
|
}
|
|
}
|
|
|
|
function toggleNav() {
|
|
if (isNavOpen) {
|
|
closeNav();
|
|
} else {
|
|
openNav();
|
|
}
|
|
}
|
|
|
|
/* ================================================================
|
|
RELAY TABLE FUNCTIONS
|
|
================================================================ */
|
|
const getStatusText = (status) => {
|
|
// NDK relay statuses:
|
|
// 0 = DISCONNECTED, 1 = DISCONNECTING, 2 = RECONNECTING
|
|
// 3 = FLAPPING, 4 = CONNECTING, 5 = CONNECTED
|
|
// 6 = AUTH_REQUIRED, 7 = AUTHENTICATING, 8 = AUTHENTICATED
|
|
const statusMap = {
|
|
0: 'Disconnected',
|
|
1: 'Disconnecting',
|
|
2: 'Reconnecting',
|
|
3: 'Flapping',
|
|
4: 'Connecting',
|
|
5: 'Connected',
|
|
6: 'Auth Required',
|
|
7: 'Authenticating',
|
|
8: 'Authenticated'
|
|
};
|
|
return statusMap[status] || 'Unknown';
|
|
};
|
|
|
|
const formatConnectionTime = (timestamp) => {
|
|
if (!timestamp || timestamp === 0) return '-';
|
|
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const elapsed = now - timestamp;
|
|
|
|
if (elapsed < 0 || elapsed > 31536000) return '-'; // More than 1 year
|
|
|
|
if (elapsed < 60) return `${elapsed}s`;
|
|
if (elapsed < 3600) return `${Math.floor(elapsed / 60)}m`;
|
|
if (elapsed < 86400) return `${Math.floor(elapsed / 3600)}h`;
|
|
return `${Math.floor(elapsed / 86400)}d`;
|
|
};
|
|
|
|
const normalizeRelayUrlInput = (input) => {
|
|
const raw = (input || '').trim();
|
|
if (!raw) return null;
|
|
|
|
let candidate = raw;
|
|
if (!/^wss?:\/\//i.test(candidate)) {
|
|
candidate = `wss://${candidate}`;
|
|
}
|
|
|
|
try {
|
|
const parsed = new URL(candidate);
|
|
if (!['ws:', 'wss:'].includes(parsed.protocol)) return null;
|
|
|
|
const host = parsed.host.toLowerCase();
|
|
if (!host) return null;
|
|
|
|
const path = parsed.pathname && parsed.pathname !== '/'
|
|
? parsed.pathname.replace(/\/+$/, '')
|
|
: '';
|
|
|
|
return `wss://${host}${path}`;
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const relayExists = (relayUrl) => {
|
|
return currentRelayList.some(relay => relay.url === relayUrl);
|
|
};
|
|
|
|
const getAddRelayType = () => {
|
|
if (addRelayCanRead && addRelayCanWrite) return 'both';
|
|
if (addRelayCanRead) return 'read';
|
|
if (addRelayCanWrite) return 'write';
|
|
return 'both';
|
|
};
|
|
|
|
const createRelayTable = async (relays) => {
|
|
const safeRelays = Array.isArray(relays) ? relays : [];
|
|
|
|
// Store current relay list for publishing
|
|
currentRelayList = safeRelays;
|
|
|
|
// Get relay stats for read/write counts
|
|
const relayStats = await getRelayStats();
|
|
|
|
let html = '<table id="tblRelays">';
|
|
|
|
// Header
|
|
html += '<thead><tr>';
|
|
html += '<th class="tblCol tblColCenter">X</th>';
|
|
html += '<th class="tblCol tblColLeft">Relay</th>';
|
|
html += '<th class="tblCol tblColCenter">Connected</th>';
|
|
html += '<th class="tblCol tblColCenter">Read</th>';
|
|
html += '<th class="tblCol tblColCenter">Write</th>';
|
|
html += '<th class="tblCol tblColRight">Reads</th>';
|
|
html += '<th class="tblCol tblColRight">Writes</th>';
|
|
html += '<th class="tblCol tblColRight">Connection Time</th>';
|
|
html += '</tr></thead>';
|
|
|
|
// Body
|
|
html += '<tbody>';
|
|
for (let i = 0; i < safeRelays.length; i++) {
|
|
const relay = safeRelays[i];
|
|
const isConnected = relay.status === 5; // 5 = CONNECTED
|
|
const isReconnecting = reconnectingRelays.has(relay.url);
|
|
const rowClass = isConnected ? 'tblRelayRow tblRowConnected' : 'tblRelayRow';
|
|
const stats = relayStats[relay.url] || { readCount: 0, writeCount: 0 };
|
|
const relayType = relay.type || 'both';
|
|
const removeIcon = pendingRemovalRelayUrl === relay.url ? SVG_X_PENDING : SVG_X_UNCHECKED;
|
|
|
|
// Determine which icon to show for connection status
|
|
let statusIcon;
|
|
if (isReconnecting) {
|
|
statusIcon = SVG_CONNECTING;
|
|
} else if (isConnected) {
|
|
statusIcon = SVG_CHECKED;
|
|
} else {
|
|
statusIcon = SVG_UNCHECKED;
|
|
}
|
|
|
|
// Determine read/write checkboxes
|
|
const canRead = relayType === 'read' || relayType === 'both';
|
|
const canWrite = relayType === 'write' || relayType === 'both';
|
|
const readCheckbox = canRead ? SVG_CHECKED : SVG_UNCHECKED;
|
|
const writeCheckbox = canWrite ? SVG_CHECKED : SVG_UNCHECKED;
|
|
|
|
html += `<tr class="${rowClass}">`;
|
|
html += `<td class="tblCol tblColCenter divSvg" data-remove-relay-url="${relay.url}" style="cursor: pointer;">${removeIcon}</td>`;
|
|
html += `<td class="tblCol tblColLeft tblColRelay" title="${relay.url}">${relay.url}</td>`;
|
|
html += `<td class="tblCol tblColCenter divSvg" data-relay-url="${relay.url}" style="cursor: pointer;">${statusIcon}</td>`;
|
|
html += `<td class="tblCol tblColCenter divSvg" data-relay-index="${i}" data-toggle-type="read" style="cursor: pointer;">${readCheckbox}</td>`;
|
|
html += `<td class="tblCol tblColCenter divSvg" data-relay-index="${i}" data-toggle-type="write" style="cursor: pointer;">${writeCheckbox}</td>`;
|
|
html += `<td class="tblCol tblColRight">${stats.readCount}</td>`;
|
|
html += `<td class="tblCol tblColRight">${stats.writeCount}</td>`;
|
|
html += `<td class="tblCol tblColRight">${formatConnectionTime(relay.connectionTime)}</td>`;
|
|
html += '</tr>';
|
|
}
|
|
|
|
const addReadCheckbox = addRelayCanRead ? SVG_CHECKED : SVG_UNCHECKED;
|
|
const addWriteCheckbox = addRelayCanWrite ? SVG_CHECKED : SVG_UNCHECKED;
|
|
html += '<tr class="tblRelayAddRow">';
|
|
html += '<td class="tblCol tblColCenter">-</td>';
|
|
html += '<td class="tblCol tblColLeft"><input id="txtNewRelayUrl" class="relayInput" type="text" placeholder="Add relay (example: relay.damus.io or wss://relay.damus.io)" title="Press Enter to add relay" /></td>';
|
|
html += '<td class="tblCol tblColCenter">-</td>';
|
|
html += `<td class="tblCol tblColCenter divSvg" data-add-toggle-type="read" style="cursor: pointer;">${addReadCheckbox}</td>`;
|
|
html += `<td class="tblCol tblColCenter divSvg" data-add-toggle-type="write" style="cursor: pointer;">${addWriteCheckbox}</td>`;
|
|
html += '<td class="tblCol tblColRight">-</td>';
|
|
html += '<td class="tblCol tblColRight">-</td>';
|
|
html += '<td class="tblCol tblColRight">-</td>';
|
|
html += '</tr>';
|
|
|
|
html += '</tbody></table>';
|
|
|
|
divRelays.innerHTML = html;
|
|
|
|
// Add click handlers to remove icons
|
|
document.querySelectorAll('.divSvg[data-remove-relay-url]').forEach(cell => {
|
|
cell.addEventListener('click', async () => {
|
|
const relayUrl = cell.getAttribute('data-remove-relay-url');
|
|
await handleRelayRemoveClick(relayUrl);
|
|
});
|
|
});
|
|
|
|
// Add click handlers to connection status icons
|
|
document.querySelectorAll('.divSvg[data-relay-url]').forEach(cell => {
|
|
cell.addEventListener('click', () => {
|
|
const relayUrl = cell.getAttribute('data-relay-url');
|
|
handleRelayReconnect(relayUrl);
|
|
});
|
|
});
|
|
|
|
// Add click handlers to read/write toggle checkboxes
|
|
document.querySelectorAll('.divSvg[data-relay-index]').forEach(cell => {
|
|
cell.addEventListener('click', () => {
|
|
const relayIndex = parseInt(cell.getAttribute('data-relay-index'));
|
|
const toggleType = cell.getAttribute('data-toggle-type');
|
|
handleRelayTypeToggle(relayIndex, toggleType);
|
|
});
|
|
});
|
|
|
|
// Add click handlers to add-row read/write toggle checkboxes
|
|
document.querySelectorAll('.divSvg[data-add-toggle-type]').forEach(cell => {
|
|
cell.addEventListener('click', async () => {
|
|
const toggleType = cell.getAttribute('data-add-toggle-type');
|
|
await handleAddRelayTypeToggle(toggleType);
|
|
});
|
|
});
|
|
|
|
// Add Enter handler for relay input
|
|
const txtNewRelayUrl = document.getElementById('txtNewRelayUrl');
|
|
if (txtNewRelayUrl) {
|
|
txtNewRelayUrl.addEventListener('keydown', async (event) => {
|
|
if (event.key === 'Enter') {
|
|
event.preventDefault();
|
|
await handleAddRelaySubmit(txtNewRelayUrl.value);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update footer stats
|
|
const connectedCount = safeRelays.filter(r => r.status === 5).length;
|
|
divFooterRight.innerHTML = `Total: ${safeRelays.length} | Connected: ${connectedCount}`;
|
|
};
|
|
|
|
const handleRelayTypeToggle = async (relayIndex, toggleType) => {
|
|
if (relayIndex < 0 || relayIndex >= currentRelayList.length) return;
|
|
|
|
const relay = currentRelayList[relayIndex];
|
|
const currentType = relay.type || 'both';
|
|
|
|
console.log(`[relays.html] Toggling ${toggleType} for ${relay.url}, current type: ${currentType}`);
|
|
|
|
// Determine new type based on toggle
|
|
let newType = currentType;
|
|
|
|
if (toggleType === 'read') {
|
|
if (currentType === 'both') {
|
|
newType = 'write'; // Remove read, keep write
|
|
} else if (currentType === 'write') {
|
|
newType = 'both'; // Add read to write
|
|
} else if (currentType === 'read') {
|
|
newType = ''; // Remove read (relay becomes inactive)
|
|
} else {
|
|
newType = 'read'; // Add read
|
|
}
|
|
} else if (toggleType === 'write') {
|
|
if (currentType === 'both') {
|
|
newType = 'read'; // Remove write, keep read
|
|
} else if (currentType === 'read') {
|
|
newType = 'both'; // Add write to read
|
|
} else if (currentType === 'write') {
|
|
newType = ''; // Remove write (relay becomes inactive)
|
|
} else {
|
|
newType = 'write'; // Add write
|
|
}
|
|
}
|
|
|
|
// Don't allow empty type - must have at least read or write
|
|
if (!newType) {
|
|
console.warn('[relays.html] Cannot remove both read and write from relay');
|
|
return;
|
|
}
|
|
|
|
// Update local relay list
|
|
relay.type = newType;
|
|
|
|
// Refresh table to show updated checkboxes
|
|
await createRelayTable(currentRelayList);
|
|
|
|
// Publish updated relay list to Nostr
|
|
await publishRelayList();
|
|
};
|
|
|
|
const handleRelayRemoveClick = async (relayUrl) => {
|
|
if (!relayUrl) return;
|
|
|
|
if (pendingRemovalRelayUrl === relayUrl) {
|
|
currentRelayList = currentRelayList.filter(relay => relay.url !== relayUrl);
|
|
pendingRemovalRelayUrl = null;
|
|
await createRelayTable(currentRelayList);
|
|
await publishRelayList();
|
|
return;
|
|
}
|
|
|
|
pendingRemovalRelayUrl = relayUrl;
|
|
await createRelayTable(currentRelayList);
|
|
};
|
|
|
|
const handleAddRelayTypeToggle = async (toggleType) => {
|
|
if (toggleType === 'read') {
|
|
if (!addRelayCanRead && !addRelayCanWrite) {
|
|
addRelayCanRead = true;
|
|
} else {
|
|
addRelayCanRead = !addRelayCanRead;
|
|
}
|
|
} else if (toggleType === 'write') {
|
|
if (!addRelayCanRead && !addRelayCanWrite) {
|
|
addRelayCanWrite = true;
|
|
} else {
|
|
addRelayCanWrite = !addRelayCanWrite;
|
|
}
|
|
}
|
|
|
|
if (!addRelayCanRead && !addRelayCanWrite) {
|
|
// At least one of read/write must remain enabled
|
|
if (toggleType === 'read') {
|
|
addRelayCanRead = true;
|
|
} else {
|
|
addRelayCanWrite = true;
|
|
}
|
|
}
|
|
|
|
await createRelayTable(currentRelayList);
|
|
};
|
|
|
|
const handleAddRelaySubmit = async (inputValue) => {
|
|
const normalizedRelayUrl = normalizeRelayUrlInput(inputValue);
|
|
|
|
if (!normalizedRelayUrl) {
|
|
divFooterCenter.innerHTML = '❌ Invalid relay URL';
|
|
return;
|
|
}
|
|
|
|
if (relayExists(normalizedRelayUrl)) {
|
|
divFooterCenter.innerHTML = '⚠️ Relay already exists in your kind 10002 list';
|
|
return;
|
|
}
|
|
|
|
const newType = getAddRelayType();
|
|
currentRelayList.push({
|
|
url: normalizedRelayUrl,
|
|
status: 0,
|
|
connectionTime: 0,
|
|
type: newType
|
|
});
|
|
|
|
pendingRemovalRelayUrl = null;
|
|
await createRelayTable(currentRelayList);
|
|
await publishRelayList();
|
|
};
|
|
|
|
const publishRelayList = async () => {
|
|
if (!currentPubkey) {
|
|
console.error('[relays.html] No pubkey available');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Build kind 10002 event
|
|
const event = {
|
|
kind: 10002,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
tags: [],
|
|
content: ''
|
|
};
|
|
|
|
// Add relay tags with read/write markers
|
|
for (const relay of currentRelayList) {
|
|
const tag = ['r', relay.url];
|
|
const relayType = relay.type || 'both';
|
|
|
|
if (relayType !== 'both') {
|
|
tag.push(relayType); // Add 'read' or 'write' marker
|
|
}
|
|
// If 'both', don't add third element (default behavior)
|
|
|
|
event.tags.push(tag);
|
|
}
|
|
|
|
console.log('[relays.html] Publishing relay list:', event);
|
|
|
|
const result = await publishEvent(event);
|
|
console.log('[relays.html] Relay list published to:', result.relayResults.successful);
|
|
|
|
// Show success message in footer
|
|
divFooterCenter.innerHTML = `✅ Published to ${result.relayResults.successful.length} relays`;
|
|
setTimeout(() => {
|
|
if (currentPubkey) {
|
|
divFooterCenter.innerHTML = `${currentPubkey.slice(0, 8)}...${currentPubkey.slice(-8)}`;
|
|
}
|
|
}, 3000);
|
|
} catch (error) {
|
|
console.error('[relays.html] Failed to publish relay list:', error);
|
|
divFooterCenter.innerHTML = `❌ Publish failed: ${error.message}`;
|
|
}
|
|
};
|
|
|
|
const handleRelayReconnect = (relayUrl) => {
|
|
console.log('[relays.html] Reconnecting relay:', relayUrl);
|
|
|
|
// Add to reconnecting set
|
|
reconnectingRelays.add(relayUrl);
|
|
|
|
// Trigger reconnect
|
|
reconnectRelay(relayUrl);
|
|
|
|
// Refresh table to show connecting state
|
|
refreshRelayData();
|
|
|
|
// Remove from reconnecting set after 5 seconds (timeout)
|
|
setTimeout(() => {
|
|
reconnectingRelays.delete(relayUrl);
|
|
refreshRelayData();
|
|
}, 5000);
|
|
};
|
|
|
|
/* ================================================================
|
|
RELAY EVENT LOGGING
|
|
================================================================ */
|
|
const FILTER_RELAY = 'wss://relay.laantungir.net/'; // Only show events from this relay
|
|
|
|
const logRelayEvent = (relayUrl, eventType, data) => {
|
|
// Filter: only log events from the specified relay
|
|
if (relayUrl !== FILTER_RELAY) return;
|
|
|
|
const timestamp = new Date().toISOString().split('T')[1].slice(0, -1); // HH:MM:SS.mmm
|
|
let logLine = `[${timestamp}] ${relayUrl} - ${eventType.toUpperCase()}`;
|
|
|
|
if (data) {
|
|
if (typeof data === 'string') {
|
|
logLine += `: ${data}`;
|
|
} else {
|
|
logLine += `: ${JSON.stringify(data)}`;
|
|
}
|
|
}
|
|
|
|
// Add to top of textarea
|
|
const currentText = txtRelayEvents.value;
|
|
txtRelayEvents.value = logLine + '\n' + currentText;
|
|
|
|
// Limit to last 1000 lines to prevent memory issues
|
|
const lines = txtRelayEvents.value.split('\n');
|
|
if (lines.length > 1000) {
|
|
txtRelayEvents.value = lines.slice(0, 1000).join('\n');
|
|
}
|
|
};
|
|
|
|
const refreshRelayData = async () => {
|
|
console.log('[relay2.html] Refreshing relay data...');
|
|
try {
|
|
const relays = await getRelayData();
|
|
createRelayTable(relays);
|
|
} catch (error) {
|
|
console.error('[relay2.html] Error getting relay data:', error);
|
|
divRelays.innerHTML = '<div style="text-align: center; padding: 50px;">Error loading relay data</div>';
|
|
}
|
|
};
|
|
|
|
/* ================================================================
|
|
UPDATE FOOTER
|
|
================================================================ */
|
|
const UpdateFooter = async () => {
|
|
try {
|
|
// Update relay status visuals in footer and sidenav
|
|
await updateFooterRelayStatus();
|
|
await updateSidenavRelaySection();
|
|
|
|
// Update center section with pubkey
|
|
if (currentPubkey) {
|
|
divFooterCenter.innerHTML = `${currentPubkey.slice(0, 8)}...${currentPubkey.slice(-8)}`;
|
|
} else {
|
|
divFooterCenter.innerHTML = 'Not authenticated';
|
|
}
|
|
} catch (error) {
|
|
console.error('[relay2.html] Error updating footer:', error);
|
|
}
|
|
};
|
|
|
|
/* ================================================================
|
|
LOGOUT
|
|
================================================================ */
|
|
const Logout = async () => {
|
|
console.log("[relay2.html] Starting logout process...");
|
|
|
|
if (updateIntervalId) {
|
|
clearInterval(updateIntervalId);
|
|
updateIntervalId = null;
|
|
}
|
|
|
|
disconnect();
|
|
|
|
if (window.NOSTR_LOGIN_LITE && window.NOSTR_LOGIN_LITE.logout) {
|
|
await window.NOSTR_LOGIN_LITE.logout();
|
|
}
|
|
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
|
|
if (window.indexedDB) {
|
|
const databases = await window.indexedDB.databases();
|
|
for (const db of databases) {
|
|
if (db.name) {
|
|
window.indexedDB.deleteDatabase(db.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log("[relay2.html] Logged out, reloading page");
|
|
location.reload(true);
|
|
};
|
|
|
|
/* ================================================================
|
|
EVENT LISTENERS
|
|
================================================================ */
|
|
|
|
/* ================================================================
|
|
INITIALIZATION
|
|
================================================================ */
|
|
(async function main() {
|
|
console.log("[relay2.html] Starting initialization...");
|
|
|
|
try {
|
|
// Initialize hamburger menu first
|
|
initHamburgerMenu();
|
|
|
|
// Add click handler to hamburger
|
|
const divSvgHam = document.getElementById('divSvgHam');
|
|
if (divSvgHam) {
|
|
divSvgHam.addEventListener('click', toggleNav);
|
|
}
|
|
|
|
// Initialize version bar buttons
|
|
const themeToggleButton = document.getElementById('themeToggleButton');
|
|
const logoutButton = document.getElementById('logoutButton');
|
|
|
|
if (themeToggleButton) {
|
|
themeToggleButton.addEventListener('click', () => {
|
|
isDarkMode = !isDarkMode;
|
|
if (isDarkMode) {
|
|
localStorage.setItem('theme', 'dark');
|
|
} else {
|
|
localStorage.setItem('theme', 'light');
|
|
}
|
|
// Save sidenav state before reload
|
|
localStorage.setItem('sidenavWasOpen', isNavOpen ? 'true' : 'false');
|
|
window.location.reload();
|
|
});
|
|
}
|
|
|
|
if (logoutButton) {
|
|
logoutButton.addEventListener('click', async () => {
|
|
try {
|
|
await Logout();
|
|
} catch (error) {
|
|
console.error('Logout failed:', error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initialize NDK (handles authentication automatically)
|
|
await initNDKPage();
|
|
|
|
// Get authenticated pubkey
|
|
currentPubkey = await getPubkey();
|
|
console.log("[relay2.html] Authenticated as:", currentPubkey);
|
|
|
|
// Initialize relay UI components
|
|
initFooterRelayStatus();
|
|
initSidenavRelaySection();
|
|
|
|
// Listen for relay events from worker (for debug logging)
|
|
// Note: We need to listen on the worker port, not window
|
|
// This will be set up via a custom event from init-ndk.mjs
|
|
window.addEventListener('ndkRelayEvent', (event) => {
|
|
const { relayUrl, event: eventType, data } = event.detail;
|
|
logRelayEvent(relayUrl, eventType, data);
|
|
});
|
|
|
|
// Listen for relay activity broadcasts from worker
|
|
window.addEventListener('ndkRelayActivity', (event) => {
|
|
const { relayUrl, activity, stats } = event.detail;
|
|
console.log(`[relay2.html] Relay activity: ${relayUrl} - ${activity}`, stats);
|
|
setRelayActivityState(relayUrl, activity);
|
|
});
|
|
|
|
// Listen for relay activity broadcasts from worker (alternative message format)
|
|
window.addEventListener('message', (event) => {
|
|
if (event.data && event.data.type === 'relayActivity') {
|
|
const { relayUrl, activity } = event.data;
|
|
console.log(`[relay2.html] Relay activity: ${relayUrl} - ${activity}`);
|
|
setRelayActivityState(relayUrl, activity);
|
|
}
|
|
});
|
|
|
|
// Get initial relay data
|
|
await refreshRelayData();
|
|
|
|
// Restore sidenav state if it was open before theme toggle
|
|
const sidenavWasOpen = localStorage.getItem('sidenavWasOpen');
|
|
if (sidenavWasOpen === 'true') {
|
|
localStorage.removeItem('sidenavWasOpen');
|
|
openNav();
|
|
}
|
|
|
|
// Start update loops
|
|
updateIntervalId = setInterval(UpdateFooter, 1000);
|
|
|
|
// Update version display
|
|
await updateVersionDisplay();
|
|
|
|
// Refresh relay table every 5 seconds
|
|
setInterval(refreshRelayData, 5000);
|
|
|
|
console.log('[relay2.html] Initialization complete');
|
|
} catch (error) {
|
|
console.error('[relay2.html] Initialization failed:', error);
|
|
divBody.innerHTML = `<div style="text-align: center; padding: 50px;">
|
|
<div style="font-size: 24px; margin-bottom: 20px; color: red;">❌ Authentication Error</div>
|
|
<div style="font-size: 16px; color: #666;">${error.message}</div>
|
|
<div style="margin-top: 20px;">
|
|
<button onclick="location.reload()" style="padding: 10px 20px; font-size: 16px;">Retry</button>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
})();
|
|
|