v0.7.24 - Fix admin API subscription issues: NIP-17 historical events and relay pubkey timing

This commit is contained in:
Your Name
2025-10-16 06:27:01 -04:00
parent 18b0ac44bf
commit 6c38aaebf3
8 changed files with 456 additions and 72 deletions

View File

@@ -22,6 +22,23 @@
--tab-border-opacity-logged-in: 0.1;
}
/* Dark Mode Overrides */
body.dark-mode {
--primary-color: #ffffff;
--secondary-color: #000000;
--accent-color: #ff0000;
--muted-color: #222222;
--border-color: var(--muted-color);
--tab-bg-logged-out: #000000;
--tab-color-logged-out: #ffffff;
--tab-border-logged-out: #ffffff;
--tab-bg-logged-in: #000000;
--tab-color-logged-in: #ffffff;
--tab-border-logged-in: #00ffff;
}
* {
margin: 0;
padding: 0;
@@ -65,6 +82,86 @@ body {
text-align: left;
}
.relay-info {
text-align: center;
flex: 1;
}
.relay-name {
font-size: 14px;
font-weight: bold;
color: var(--primary-color);
margin-bottom: 2px;
}
.relay-pubkey-container {
border: 1px solid transparent;
border-radius: var(--border-radius);
padding: 4px;
margin-top: 4px;
cursor: pointer;
transition: border-color 0.2s ease;
background-color: var(--secondary-color);
}
.relay-pubkey-container:hover {
border-color: var(--border-color);
}
.relay-pubkey-container.copied {
border-color: var(--accent-color);
animation: flash-accent 0.5s ease-in-out;
}
.relay-pubkey {
font-size: 8px;
color: var(--primary-color);
font-family: "Courier New", Courier, monospace;
line-height: 1.2;
white-space: pre-line;
text-align: center;
}
@keyframes flash-accent {
0% { border-color: var(--accent-color); }
50% { border-color: var(--accent-color); }
100% { border-color: transparent; }
}
.relay-description {
font-size: 10px;
color: var(--primary-color);
margin-bottom: 0;
}
.header-title {
margin: 0;
font-size: 24px;
font-weight: bolder;
color: var(--primary-color);
border: none;
padding: 0;
text-align: left;
display: flex;
gap: 2px;
}
.relay-letter {
position: relative;
display: inline-block;
transition: all 0.05s ease;
}
.relay-letter.underlined::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 2px;
background-color: var(--accent-color);
}
.header-user-name {
display: block;
font-weight: 500;
@@ -76,6 +173,7 @@ body {
.profile-area {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
cursor: pointer;
@@ -85,6 +183,14 @@ body {
margin-left: auto;
}
.admin-label {
font-size: 10px;
color: var(--primary-color);
font-weight: normal;
margin-bottom: 4px;
text-align: center;
}
.profile-container {
display: flex;
flex-direction: column;
@@ -127,13 +233,13 @@ body {
.logout-btn {
width: 100%;
padding: 10px 15px;
padding: 5px 10px;
background: none;
border: none;
color: var(--primary-color);
text-align: left;
cursor: pointer;
font-size: 14px;
font-size: 10px;
font-family: var(--font-family);
border-radius: var(--border-radius);
transition: background-color 0.2s ease;
@@ -253,10 +359,10 @@ button:active {
}
button:disabled {
background-color: #ccc;
color: var(--muted-color);
background-color: var(--muted-color);
color: var(--primary-color);
cursor: not-allowed;
border-color: #ccc;
border-color: var(--muted-color);
}
/* Flash animation for refresh button */
@@ -344,6 +450,10 @@ button:disabled {
font-size: 10px;
}
.config-table tbody tr:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.config-table-container {
overflow-x: auto;
max-width: 100%;
@@ -351,12 +461,13 @@ button:disabled {
.config-table th {
font-weight: bold;
height: 40px; /* Double the default height */
line-height: 40px; /* Center text vertically */
height: 24px; /* Base height for tbody rows */
line-height: 24px; /* Center text vertically */
}
.config-table tr:hover {
background-color: var(--muted-color);
.config-table td {
height: 16px; /* 50% taller than tbody rows would be */
line-height: 16px; /* Center text vertically */
}
/* Inline config value inputs - remove borders and padding to fit seamlessly in table cells */
@@ -694,12 +805,6 @@ button:disabled {
transition: all 0.2s ease;
}
.flex-section {
flex: 1;
min-width: 300px;
}
@media (max-width: 700px) {
body {
padding: 10px;

View File

@@ -10,21 +10,38 @@
<body>
<!-- Header with title and profile display -->
<header class="main-header">
<div class="header-content">
<div class="header-title">RELAY</div>
<div class="profile-area" id="profile-area" style="display: none;">
<div class="profile-container">
<img id="header-user-image" class="header-user-image" alt="Profile" style="display: none;">
<span id="header-user-name" class="header-user-name">Loading...</span>
<div class="section">
<div class="header-content">
<div class="header-title">
<span class="relay-letter" data-letter="R">R</span>
<span class="relay-letter" data-letter="E">E</span>
<span class="relay-letter" data-letter="L">L</span>
<span class="relay-letter" data-letter="A">A</span>
<span class="relay-letter" data-letter="Y">Y</span>
</div>
<!-- Logout dropdown -->
<div class="logout-dropdown" id="logout-dropdown" style="display: none;">
<button type="button" id="logout-btn" class="logout-btn">LOGOUT</button>
<div class="relay-info">
<div id="relay-name" class="relay-name">C-Relay</div>
<div id="relay-description" class="relay-description">Loading...</div>
<div id="relay-pubkey-container" class="relay-pubkey-container">
<div id="relay-pubkey" class="relay-pubkey">Loading...</div>
</div>
</div>
<div class="profile-area" id="profile-area" style="display: none;">
<div class="admin-label">admin</div>
<div class="profile-container">
<img id="header-user-image" class="header-user-image" alt="Profile" style="display: none;">
<span id="header-user-name" class="header-user-name">Loading...</span>
</div>
<!-- Logout dropdown -->
<div class="logout-dropdown" id="logout-dropdown" style="display: none;">
<button type="button" id="dark-mode-btn" class="logout-btn">🌙 DARK MODE</button>
<button type="button" id="logout-btn" class="logout-btn">LOGOUT</button>
</div>
</div>
</div>
</div>
</header>
</div>
<!-- Login Modal Overlay -->
<div id="login-modal" class="login-modal-overlay" style="display: none;">
@@ -49,29 +66,24 @@
<tr>
<th>Metric</th>
<th>Value</th>
<th>Description</th>
</tr>
</thead>
<tbody id="stats-overview-table-body">
<tr>
<td>Database Size</td>
<td id="db-size">-</td>
<td>Current database file size</td>
</tr>
<tr>
<td>Total Events</td>
<td id="total-events">-</td>
<td>Total number of events stored</td>
</tr>
<tr>
<td>Oldest Event</td>
<td id="oldest-event">-</td>
<td>Timestamp of oldest event</td>
</tr>
<tr>
<td>Newest Event</td>
<td id="newest-event">-</td>
<td>Timestamp of newest event</td>
</tr>
</tbody>
</table>
@@ -108,24 +120,20 @@
<tr>
<th>Period</th>
<th>Events</th>
<th>Description</th>
</tr>
</thead>
<tbody id="stats-time-table-body">
<tr>
<td>Last 24 Hours</td>
<td id="events-24h">-</td>
<td>Events in the last day</td>
</tr>
<tr>
<td>Last 7 Days</td>
<td id="events-7d">-</td>
<td>Events in the last week</td>
</tr>
<tr>
<td>Last 30 Days</td>
<td id="events-30d">-</td>
<td>Events in the last month</td>
</tr>
</tbody>
</table>

View File

@@ -22,6 +22,7 @@ let currentConfig = null;
// Global subscription state
let relayPool = null;
let subscriptionId = null;
let isSubscribed = false; // Flag to prevent multiple simultaneous subscriptions
// Relay connection state
let relayInfo = null;
let isRelayConnected = false;
@@ -353,6 +354,9 @@ async function setupAutomaticRelayConnection(showSections = false) {
// Mark as connected
isRelayConnected = true;
// Update relay info in header
updateRelayInfoInHeader();
// Only show admin sections if explicitly requested
if (showSections) {
updateAdminSectionsVisibility();
@@ -747,6 +751,8 @@ async function logout() {
}
relayPool = null;
subscriptionId = null;
// Reset subscription flag
isSubscribed = false;
}
await nlLite.logout();
@@ -758,6 +764,8 @@ async function logout() {
// Reset relay connection state
isRelayConnected = false;
relayPubkey = null;
// Reset subscription flag
isSubscribed = false;
// Reset UI - hide profile and show login modal
hideProfileFromHeader();
@@ -798,6 +806,12 @@ async function subscribeToConfiguration() {
try {
console.log('=== STARTING SIMPLEPOOL CONFIGURATION SUBSCRIPTION ===');
// Prevent multiple simultaneous subscription attempts
if (isSubscribed) {
console.log('Subscription already established, skipping duplicate subscription attempt');
return true;
}
if (!isLoggedIn) {
console.log('WARNING: Not logged in, but proceeding with subscription test');
}
@@ -810,16 +824,14 @@ async function subscribeToConfiguration() {
console.log(`Connecting to relay via SimplePool: ${url}`);
// Clean up existing pool
if (relayPool) {
console.log('Closing existing pool connection');
relayPool.close([url]);
relayPool = null;
subscriptionId = null;
// Reuse existing pool if available, otherwise create new one
if (!relayPool) {
console.log('Creating new SimplePool instance');
relayPool = new window.NostrTools.SimplePool();
} else {
console.log('Reusing existing SimplePool instance');
}
// Create new SimplePool instance
relayPool = new window.NostrTools.SimplePool();
subscriptionId = generateSubId();
console.log(`Generated subscription ID: ${subscriptionId}`);
@@ -838,6 +850,7 @@ async function subscribeToConfiguration() {
"#p": [userPubkey], // Only DMs directed to this user
limit: 50
}, {
since: Math.floor(Date.now() / 1000), // Start from current time
kinds: [1059], // NIP-17 GiftWrap events
"#p": [userPubkey], // Only GiftWrap events addressed to this user
limit: 50
@@ -940,6 +953,9 @@ async function subscribeToConfiguration() {
// Store subscription for cleanup
relayPool.currentSubscription = subscription;
// Mark as subscribed to prevent duplicate attempts
isSubscribed = true;
console.log('SimplePool subscription established');
return true;
@@ -1101,6 +1117,9 @@ function handleConfigQueryResponse(responseData) {
// Display the configuration using the original display function
displayConfiguration(syntheticEvent);
// Update relay info in header with config data
updateStoredRelayInfo(responseData);
log(`Configuration loaded: ${responseData.total_results} parameters`, 'INFO');
} else {
console.log('No configuration data received');
@@ -1335,14 +1354,16 @@ async function fetchConfiguration() {
throw new Error('Must be connected to relay to fetch configuration. Please use the Relay Connection section first.');
}
// First establish subscription to receive responses
// First establish subscription to receive responses (only if not already subscribed)
const subscriptionResult = await subscribeToConfiguration();
if (!subscriptionResult) {
throw new Error('Failed to establish admin response subscription');
}
// Wait a moment for subscription to be established
await new Promise(resolve => setTimeout(resolve, 500));
// Wait a moment for subscription to be established (only if we just created it)
if (!isSubscribed) {
await new Promise(resolve => setTimeout(resolve, 500));
}
// Send config query command if logged in
if (isLoggedIn && userPubkey && relayPool) {
@@ -1698,6 +1719,43 @@ if (logoutBtn) {
});
}
// Initialize dark mode button handler
const darkModeBtn = document.getElementById('dark-mode-btn');
if (darkModeBtn) {
darkModeBtn.addEventListener('click', function(e) {
e.stopPropagation(); // Prevent profile area click
toggleDarkMode();
});
}
// Initialize relay pubkey container click handler for clipboard copy
const relayPubkeyContainer = document.getElementById('relay-pubkey-container');
if (relayPubkeyContainer) {
relayPubkeyContainer.addEventListener('click', async function() {
const relayPubkeyElement = document.getElementById('relay-pubkey');
if (relayPubkeyElement && relayPubkeyElement.textContent !== 'Loading...') {
try {
// Get the full npub (remove line breaks for clipboard)
const fullNpub = relayPubkeyElement.textContent.replace(/\n/g, '');
await navigator.clipboard.writeText(fullNpub);
// Add copied class for visual feedback
relayPubkeyContainer.classList.add('copied');
// Remove the class after animation completes
setTimeout(() => {
relayPubkeyContainer.classList.remove('copied');
}, 500);
log('Relay npub copied to clipboard', 'INFO');
} catch (error) {
log('Failed to copy relay npub to clipboard', 'ERROR');
}
}
});
}
// Event handlers
fetchConfigBtn.addEventListener('click', function (e) {
e.preventDefault();
@@ -3181,16 +3239,90 @@ function addMessageToInbox(direction, message, timestamp, pubkey = null) {
}
}
// Update relay info in header
function updateRelayInfoInHeader() {
const relayNameElement = document.getElementById('relay-name');
const relayPubkeyElement = document.getElementById('relay-pubkey');
const relayDescriptionElement = document.getElementById('relay-description');
if (!relayNameElement || !relayPubkeyElement || !relayDescriptionElement) {
return;
}
// Get relay info from NIP-11 data or use defaults
const relayInfo = getRelayInfo();
const relayName = relayInfo.name || 'C-Relay';
const relayDescription = relayInfo.description || 'Nostr Relay';
// Convert relay pubkey to npub
let relayNpub = 'Loading...';
if (relayPubkey) {
try {
relayNpub = window.NostrTools.nip19.npubEncode(relayPubkey);
} catch (error) {
console.log('Failed to encode relay pubkey to npub:', error.message);
relayNpub = relayPubkey.substring(0, 16) + '...';
}
}
// Format npub into 3 lines of 21 characters each
let formattedNpub = relayNpub;
if (relayNpub.length === 63) {
formattedNpub = relayNpub.substring(0, 21) + '\n' +
relayNpub.substring(21, 42) + '\n' +
relayNpub.substring(42, 63);
}
relayNameElement.textContent = relayName;
relayPubkeyElement.textContent = formattedNpub;
relayDescriptionElement.textContent = relayDescription;
}
// Global variable to store relay info from NIP-11 or config
let relayInfoData = null;
// Helper function to get relay info from stored data
function getRelayInfo() {
// Return stored relay info if available, otherwise defaults
if (relayInfoData) {
return relayInfoData;
}
// Default values
return {
name: 'C-Relay',
description: 'Nostr Relay',
pubkey: relayPubkey
};
}
// Update stored relay info when config is loaded
function updateStoredRelayInfo(configData) {
if (configData && configData.data) {
// Extract relay info from config data
const relayName = configData.data.find(item => item.key === 'relay_name')?.value || 'C-Relay';
const relayDescription = configData.data.find(item => item.key === 'relay_description')?.value || 'Nostr Relay';
relayInfoData = {
name: relayName,
description: relayDescription,
pubkey: relayPubkey
};
// Update header immediately
updateRelayInfoInHeader();
}
}
// Helper function to get relay pubkey
function getRelayPubkey() {
// Use the dynamically fetched relay pubkey if available
if (relayPubkey && isRelayConnected) {
if (relayPubkey) {
return relayPubkey;
}
// Fallback to hardcoded value for testing/development
log('Warning: Using hardcoded relay pubkey. Please connect to relay first.', 'WARNING');
return '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa';
// No fallback - throw error if relay pubkey not available
throw new Error('Relay pubkey not available. Please connect to relay first.');
}
// Enhanced SimplePool message handler to capture test responses
@@ -3695,10 +3827,53 @@ document.addEventListener('DOMContentLoaded', () => {
});
// Dark mode functionality
function toggleDarkMode() {
const body = document.body;
const isDarkMode = body.classList.contains('dark-mode');
if (isDarkMode) {
body.classList.remove('dark-mode');
localStorage.setItem('darkMode', 'false');
updateDarkModeButton(false);
log('Switched to light mode', 'INFO');
} else {
body.classList.add('dark-mode');
localStorage.setItem('darkMode', 'true');
updateDarkModeButton(true);
log('Switched to dark mode', 'INFO');
}
}
function updateDarkModeButton(isDarkMode) {
const darkModeBtn = document.getElementById('dark-mode-btn');
if (darkModeBtn) {
darkModeBtn.textContent = isDarkMode ? 'LIGHT MODE' : 'DARK MODE';
}
}
function initializeDarkMode() {
const savedDarkMode = localStorage.getItem('darkMode');
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
const shouldBeDark = savedDarkMode === 'true' || (savedDarkMode === null && prefersDark);
if (shouldBeDark) {
document.body.classList.add('dark-mode');
updateDarkModeButton(true);
} else {
updateDarkModeButton(false);
}
}
// Initialize the app
document.addEventListener('DOMContentLoaded', () => {
console.log('C-Relay Admin API interface loaded');
// Initialize dark mode
initializeDarkMode();
// Start RELAY letter animation
startRelayAnimation();
// Ensure admin sections are hidden by default on page load
updateAdminSectionsVisibility();
@@ -3708,4 +3883,39 @@ document.addEventListener('DOMContentLoaded', () => {
// Enhance SimplePool for testing after initialization
setTimeout(enhancePoolForTesting, 2000);
}, 100);
});
});
// RELAY letter animation function
function startRelayAnimation() {
const letters = document.querySelectorAll('.relay-letter');
let currentIndex = 0;
function animateLetter() {
// Remove underline from all letters first
letters.forEach(letter => letter.classList.remove('underlined'));
// Add underline to current letter
if (letters[currentIndex]) {
letters[currentIndex].classList.add('underlined');
}
// Move to next letter
currentIndex++;
// If we've gone through all letters, remove all underlines and wait 4000ms then restart
if (currentIndex > letters.length) {
// Remove all underlines before the pause
letters.forEach(letter => letter.classList.remove('underlined'));
setTimeout(() => {
currentIndex = 0;
animateLetter();
}, 4000);
} else {
// Otherwise, continue to next letter after 200ms
setTimeout(animateLetter, 100);
}
}
// Start the animation
animateLetter();
}