Compare commits

...

2 Commits

5 changed files with 106 additions and 432 deletions

View File

@@ -6,7 +6,7 @@
--muted-color: #dddddd;
--border-color: var(--muted-color);
--font-family: "Courier New", Courier, monospace;
--border-radius: 15px;
--border-radius: 5px;
--border-width: 1px;
/* Floating Tab Variables (8) */
@@ -326,23 +326,6 @@ button:disabled {
border-color: var(--accent-color);
}
/* Authentication warning message */
.auth-warning-message {
margin-bottom: 15px;
padding: 12px;
background-color: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: var(--border-radius);
color: #856404;
}
.warning-content {
line-height: 1.4;
}
.warning-content strong {
color: #d68910;
}
.config-table {
border: 1px solid var(--border-color);
@@ -713,15 +696,6 @@ button:disabled {
transition: all 0.2s ease;
}
/* Main Sections Wrapper */
.main-sections-wrapper {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
display: flex;
flex-wrap: wrap;
gap: var(--border-width);
}
.flex-section {
flex: 1;

View File

@@ -33,58 +33,6 @@
</div>
</div>
<!-- Main Sections Wrapper -->
<div class="main-sections-wrapper">
<!-- Relay Connection Section -->
<div id="relay-connection-section" class="flex-section">
<div class="section">
<h2>RELAY CONNECTION</h2>
<div class="input-group">
<label for="relay-connection-url">Relay URL:</label>
<input type="text" id="relay-connection-url" value=""
placeholder="ws://localhost:8888 or wss://relay.example.com">
</div>
<div class="input-group">
<label for="relay-pubkey-manual">Relay Pubkey (if not available via NIP-11):</label>
<input type="text" id="relay-pubkey-manual" placeholder="64-character hex pubkey"
pattern="[0-9a-fA-F]{64}" title="64-character hexadecimal public key">
</div>
<div class="inline-buttons">
<button type="button" id="connect-relay-btn">CONNECT TO RELAY</button>
<button type="button" id="disconnect-relay-btn" disabled>DISCONNECT</button>
<button type="button" id="restart-relay-btn" disabled>RESTART RELAY</button>
</div>
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</div>
<!-- Relay Information Display -->
<div id="relay-info-display" class="hidden">
<h3>Relay Information (NIP-11)</h3>
<table class="config-table" id="relay-info-table">
<thead>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
</thead>
<tbody id="relay-info-table-body">
</tbody>
</table>
</div>
</div>
</div>
</div> <!-- End Main Sections Wrapper -->
<!-- DATABASE STATISTICS Section -->
<div class="section flex-section" id="databaseStatisticsSection" style="display: none;">
<div class="section-header">

View File

@@ -26,6 +26,8 @@ let subscriptionId = null;
let relayInfo = null;
let isRelayConnected = false;
let relayPubkey = null;
// Simple relay URL object (replaces DOM element)
let relayConnectionUrl = { value: '' };
// Database statistics auto-refresh
let statsAutoRefreshInterval = null;
let countdownInterval = null;
@@ -46,13 +48,6 @@ const persistentUserPubkey = document.getElementById('persistent-user-pubkey');
const persistentUserAbout = document.getElementById('persistent-user-about');
const persistentUserDetails = document.getElementById('persistent-user-details');
const fetchConfigBtn = document.getElementById('fetch-config-btn');
// Relay connection elements
const relayConnectionUrl = document.getElementById('relay-connection-url');
const relayPubkeyManual = document.getElementById('relay-pubkey-manual');
const relayConnectionStatus = document.getElementById('relay-connection-status');
const connectRelayBtn = document.getElementById('connect-relay-btn');
const disconnectRelayBtn = document.getElementById('disconnect-relay-btn');
const restartRelayBtn = document.getElementById('restart-relay-btn');
const configDisplay = document.getElementById('config-display');
const configTableBody = document.getElementById('config-table-body');
@@ -83,40 +78,6 @@ function log(message, type = 'INFO') {
// UI logging removed - using console only
}
// Show authentication warning message
function showAuthenticationWarning(message) {
// Remove any existing warning
hideAuthenticationWarning();
// Create warning element
const warningDiv = document.createElement('div');
warningDiv.id = 'auth-warning-message';
warningDiv.className = 'auth-warning-message';
warningDiv.innerHTML = `
<div class="warning-content">
<strong>⚠️ Authentication Issue:</strong> ${message}
<br><br>
<small>This usually means your pubkey is not authorized as an admin for this relay.
Please check that you are using the correct admin pubkey that was shown during relay startup.</small>
</div>
`;
// Insert warning at the top of the relay connection section
const relaySection = document.getElementById('relay-connection-section');
if (relaySection) {
relaySection.insertBefore(warningDiv, relaySection.firstChild);
}
log(`Authentication warning displayed: ${message}`, 'WARNING');
}
// Hide authentication warning message
function hideAuthenticationWarning() {
const warningDiv = document.getElementById('auth-warning-message');
if (warningDiv) {
warningDiv.remove();
}
}
// NIP-59 helper: randomize created_at to thwart time-analysis (past 2 days)
function randomNow() {
@@ -237,239 +198,9 @@ async function testWebSocketConnection(wsUrl) {
});
}
// Connect to relay (NIP-11 + WebSocket test)
async function connectToRelay() {
try {
const url = relayConnectionUrl.value.trim();
if (!url) {
throw new Error('Please enter a relay URL');
}
// Update UI to show connecting state
updateRelayConnectionStatus('connecting');
connectRelayBtn.disabled = true;
log(`Connecting to relay: ${url}`, 'INFO');
// Clear any previous authentication warnings
hideAuthenticationWarning();
let fetchedRelayInfo;
try {
// Step 1: Try to fetch NIP-11 relay information
fetchedRelayInfo = await fetchRelayInfo(url);
// Check if NIP-11 response includes a pubkey
if (fetchedRelayInfo.pubkey) {
// NIP-11 provided pubkey - populate the manual input field
log(`NIP-11 provided relay pubkey: ${fetchedRelayInfo.pubkey.substring(0, 16)}...`, 'INFO');
relayPubkeyManual.value = fetchedRelayInfo.pubkey;
} else {
// NIP-11 response missing pubkey, check for manual input
log('NIP-11 response missing pubkey, checking for manual input...', 'INFO');
const manualPubkey = relayPubkeyManual.value.trim();
if (!manualPubkey) {
throw new Error('Relay NIP-11 response does not include a pubkey. Please enter the relay pubkey manually (shown during relay startup).');
}
if (!/^[0-9a-fA-F]{64}$/.test(manualPubkey)) {
throw new Error('Manual relay pubkey must be exactly 64 hexadecimal characters');
}
log(`Using manual relay pubkey: ${manualPubkey.substring(0, 16)}...`, 'INFO');
// Add manual pubkey to the fetched relay info
fetchedRelayInfo.pubkey = manualPubkey;
// If relay info was completely empty, create minimal info
if (Object.keys(fetchedRelayInfo).length === 1) {
fetchedRelayInfo = {
name: 'C-Relay (Manual Config)',
description: 'C-Relay instance - pubkey provided manually',
pubkey: manualPubkey,
contact: 'admin@manual.config.relay',
supported_nips: [1, 9, 11, 13, 15, 20, 33, 40, 42],
software: 'https://github.com/0xtrr/c-relay',
version: '1.0.0'
};
}
}
} catch (nip11Error) {
// If NIP-11 completely fails (network error, etc.), require manual pubkey
const manualPubkey = relayPubkeyManual.value.trim();
if (!manualPubkey) {
throw new Error(`NIP-11 fetch failed: ${nip11Error.message}. Please enter the relay pubkey manually if the relay hasn't been configured yet.`);
}
if (!/^[0-9a-fA-F]{64}$/.test(manualPubkey)) {
throw new Error('Manual relay pubkey must be exactly 64 hexadecimal characters');
}
log(`NIP-11 failed, using manual relay pubkey: ${manualPubkey.substring(0, 16)}...`, 'INFO');
// Create minimal relay info with manual pubkey
fetchedRelayInfo = {
name: 'C-Relay (Manual Config)',
description: 'C-Relay instance - pubkey provided manually',
pubkey: manualPubkey,
contact: 'admin@manual.config.relay',
supported_nips: [1, 9, 11, 13, 15, 20, 33, 40, 42],
software: 'https://github.com/0xtrr/c-relay',
version: '1.0.0'
};
}
// Step 2: Test WebSocket connection
await testWebSocketConnection(url);
// Step 3: Update global state
relayInfo = fetchedRelayInfo;
relayPubkey = fetchedRelayInfo.pubkey;
isRelayConnected = true;
// Step 4: Update UI
updateRelayConnectionStatus('connected');
updateAdminSectionsVisibility();
// Step 5: Relay URL updated
// Step 6: Automatically load configuration and auth rules
log('Relay connected successfully. Auto-loading configuration and auth rules...', 'INFO');
// Auto-fetch configuration
setTimeout(() => {
fetchConfiguration().catch(error => {
log('Auto-fetch configuration failed: ' + error.message, 'ERROR');
});
}, 500);
// Auto-fetch auth rules
setTimeout(() => {
loadAuthRules().catch(error => {
log('Auto-fetch auth rules failed: ' + error.message, 'ERROR');
});
}, 1000);
// Auto-fetch database statistics
setTimeout(() => {
sendStatsQuery().catch(error => {
log('Auto-fetch statistics failed: ' + error.message, 'ERROR');
});
}, 1500);
log(`Successfully connected to relay: ${relayInfo.name || 'Unknown'}`, 'INFO');
} catch (error) {
log(`Failed to connect to relay: ${error.message}`, 'ERROR');
// Check if this is an authentication-related error
if (error.message.includes('authentication') ||
error.message.includes('auth') ||
error.message.includes('permission') ||
error.message.includes('unauthorized') ||
error.message.includes('forbidden')) {
updateRelayConnectionStatus('auth_error');
showAuthenticationWarning(error.message);
} else {
updateRelayConnectionStatus('error');
}
// Reset state on failure
relayInfo = null;
relayPubkey = null;
isRelayConnected = false;
} finally {
connectRelayBtn.disabled = false;
}
}
// Disconnect from relay
function disconnectFromRelay() {
try {
log('Disconnecting from relay...', 'INFO');
// Clean up relay pool if exists
if (relayPool) {
const url = relayConnectionUrl.value.trim();
if (url) {
relayPool.close([url]);
}
relayPool = null;
subscriptionId = null;
}
// Reset state
relayInfo = null;
relayPubkey = null;
isRelayConnected = false;
// Update UI
updateRelayConnectionStatus('disconnected');
hideRelayInfo();
updateAdminSectionsVisibility();
// Hide any authentication warnings
hideAuthenticationWarning();
log('Disconnected from relay', 'INFO');
} catch (error) {
log(`Error during relay disconnection: ${error.message}`, 'ERROR');
}
}
// Update relay connection status UI
function updateRelayConnectionStatus(status) {
if (!relayConnectionStatus) return;
switch (status) {
case 'connecting':
relayConnectionStatus.textContent = 'CONNECTING...';
relayConnectionStatus.className = 'status connected';
connectRelayBtn.disabled = true;
disconnectRelayBtn.disabled = true;
restartRelayBtn.disabled = true;
break;
case 'connected':
relayConnectionStatus.textContent = 'CONNECTED';
relayConnectionStatus.className = 'status connected';
connectRelayBtn.disabled = true;
disconnectRelayBtn.disabled = false;
restartRelayBtn.disabled = false;
break;
case 'disconnected':
relayConnectionStatus.textContent = 'NOT CONNECTED';
relayConnectionStatus.className = 'status disconnected';
connectRelayBtn.disabled = false;
disconnectRelayBtn.disabled = true;
restartRelayBtn.disabled = true;
break;
case 'error':
relayConnectionStatus.textContent = 'CONNECTION FAILED';
relayConnectionStatus.className = 'status error';
connectRelayBtn.disabled = false;
disconnectRelayBtn.disabled = true;
restartRelayBtn.disabled = true;
break;
case 'auth_error':
relayConnectionStatus.textContent = 'AUTHENTICATION FAILED';
relayConnectionStatus.className = 'status error';
connectRelayBtn.disabled = false;
disconnectRelayBtn.disabled = true;
restartRelayBtn.disabled = true;
break;
}
}
// Hide relay information display (placeholder for removed functionality)
function hideRelayInfo() {
// Relay info display functionality has been removed
console.log('Relay info display functionality has been removed');
}
// Check for existing authentication state with multiple API methods and retry logic
async function checkExistingAuthWithRetries() {
@@ -560,13 +291,85 @@ async function restoreAuthenticationState(pubkey) {
showProfileInHeader();
loadUserProfile();
// Note: Configuration fetching now requires explicit relay connection
// User must connect to relay manually after login
console.log('✅ Authentication state restored - connect to relay to fetch configuration');
// Automatically set up relay connection (but don't show admin sections yet)
await setupAutomaticRelayConnection();
console.log('✅ Authentication state restored successfully');
}
// Automatically set up relay connection based on current page URL
async function setupAutomaticRelayConnection(showSections = false) {
try {
// Get the current page URL and convert to WebSocket URL
const currentUrl = window.location.href;
let relayUrl = '';
if (currentUrl.startsWith('https://')) {
relayUrl = currentUrl.replace('https://', 'wss://');
} else if (currentUrl.startsWith('http://')) {
relayUrl = currentUrl.replace('http://', 'ws://');
} else {
// Fallback for development
relayUrl = 'ws://localhost:8888';
}
// Remove any path components to get just the base URL
const url = new URL(relayUrl);
relayUrl = `${url.protocol}//${url.host}`;
// Set the relay URL
relayConnectionUrl.value = relayUrl;
console.log('🔗 Auto-setting relay URL to:', relayUrl);
// Fetch relay info to get pubkey
try {
const httpUrl = relayUrl.replace('ws', 'http').replace('wss', 'https');
const relayInfo = await fetchRelayInfo(httpUrl);
if (relayInfo && relayInfo.pubkey) {
relayPubkey = relayInfo.pubkey;
console.log('🔑 Auto-fetched relay pubkey:', relayPubkey.substring(0, 16) + '...');
} else {
// Use fallback pubkey
relayPubkey = '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa';
console.log('⚠️ Using fallback relay pubkey');
}
} catch (error) {
console.log('⚠️ Could not fetch relay info, using fallback pubkey:', error.message);
relayPubkey = '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa';
}
// Initialize relay pool for admin API communication
if (!relayPool) {
relayPool = new window.NostrTools.SimplePool();
console.log('🔌 Initialized SimplePool for admin API communication');
}
// Set up subscription to receive admin API responses
await subscribeToConfiguration();
console.log('📡 Subscription established for admin API responses');
// Mark as connected
isRelayConnected = true;
// Only show admin sections if explicitly requested
if (showSections) {
updateAdminSectionsVisibility();
}
console.log('✅ Automatic relay connection setup complete');
} catch (error) {
console.error('❌ Failed to setup automatic relay connection:', error);
// Still mark as connected to allow basic functionality
isRelayConnected = true;
if (showSections) {
updateAdminSectionsVisibility();
}
}
}
// Legacy function for backward compatibility
async function checkExistingAuth() {
return await checkExistingAuthWithRetries();
@@ -599,6 +402,8 @@ async function initializeApp() {
if (wasAlreadyLoggedIn) {
console.log('User was already logged in, showing profile in header');
showProfileInHeader();
// Show admin sections since user is already authenticated and relay is connected
updateAdminSectionsVisibility();
} else {
console.log('No existing authentication found, showing login modal');
showLoginModal();
@@ -628,20 +433,8 @@ function handleAuthEvent(event) {
showProfileInHeader();
loadUserProfile();
// Automatically attempt to connect to relay after successful login
console.log('Login successful. Automatically attempting to connect to relay...');
setTimeout(() => {
connectToRelay().catch(error => {
console.log(`Automatic relay connection failed: ${error.message}`);
// Check if this is an authentication-related error
if (error.message.includes('authentication') ||
error.message.includes('auth') ||
error.message.includes('permission') ||
error.message.includes('unauthorized')) {
showAuthenticationWarning(error.message);
}
});
}, 500); // Small delay to allow profile loading to complete
// Automatically set up relay connection and show admin sections
setupAutomaticRelayConnection(true);
} else if (error) {
console.log(`Authentication error: ${error}`);
@@ -656,11 +449,9 @@ function handleLogoutEvent() {
isLoggedIn = false;
currentConfig = null;
// Clean up relay connection
disconnectFromRelay();
// Hide any authentication warnings
hideAuthenticationWarning();
// Reset relay connection state
isRelayConnected = false;
relayPubkey = null;
// Reset UI - hide profile and show login modal
hideProfileFromHeader();
@@ -688,7 +479,18 @@ function updateAdminSectionsVisibility() {
// Start/stop auto-refresh based on visibility
if (shouldShow && databaseStatisticsSection && databaseStatisticsSection.style.display === 'block') {
// Load statistics immediately, then start auto-refresh
sendStatsQuery().catch(error => {
console.log('Auto-fetch statistics failed: ' + error.message);
});
startStatsAutoRefresh();
// Also load configuration and auth rules automatically when sections become visible
fetchConfiguration().catch(error => {
console.log('Auto-fetch configuration failed: ' + error.message);
});
loadAuthRules().catch(error => {
console.log('Auto-load auth rules failed: ' + error.message);
});
} else {
stopStatsAutoRefresh();
}
@@ -935,8 +737,6 @@ async function logout() {
// Stop auto-refresh before disconnecting
stopStatsAutoRefresh();
// Clean up relay connection
disconnectFromRelay();
// Clean up configuration pool
if (relayPool) {
@@ -955,8 +755,9 @@ async function logout() {
isLoggedIn = false;
currentConfig = null;
// Hide any authentication warnings
hideAuthenticationWarning();
// Reset relay connection state
isRelayConnected = false;
relayPubkey = null;
// Reset UI - hide profile and show login modal
hideProfileFromHeader();
@@ -966,7 +767,6 @@ async function logout() {
updateAdminSectionsVisibility();
log('Logged out successfully', 'INFO');
} catch (error) {
log('Logout failed: ' + error.message, 'ERROR');
}
@@ -1910,28 +1710,6 @@ fetchConfigBtn.addEventListener('click', function (e) {
// Relay connection event handlers
connectRelayBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
connectToRelay().catch(error => {
console.log('Relay connection failed: ' + error.message);
});
});
disconnectRelayBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
disconnectFromRelay();
});
restartRelayBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
sendRestartCommand().catch(error => {
log(`Restart command failed: ${error.message}`, 'ERROR');
});
});
// ================================
// AUTH RULES MANAGEMENT FUNCTIONS
@@ -3916,37 +3694,11 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
// Set default relay URL based on where the page is being served from
function setDefaultRelayUrl() {
const relayUrlInput = document.getElementById('relay-connection-url');
if (!relayUrlInput) return;
// Get the current page's protocol and hostname
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const hostname = window.location.hostname;
const port = window.location.port;
// Construct the relay URL
let relayUrl;
if (hostname === 'localhost' || hostname === '127.0.0.1') {
// For localhost, default to ws://localhost:8888
relayUrl = 'ws://localhost:8888';
} else {
// For production, use the same hostname with WebSocket protocol
// Remove port from URL since relay typically runs on standard ports (80/443)
relayUrl = `${protocol}//${hostname}`;
}
relayUrlInput.value = relayUrl;
log(`Default relay URL set to: ${relayUrl}`, 'INFO');
}
// Initialize the app
document.addEventListener('DOMContentLoaded', () => {
console.log('C-Relay Admin API interface loaded');
// Set default relay URL based on current page location
setDefaultRelayUrl();
// Ensure admin sections are hidden by default on page load
updateAdminSectionsVisibility();

View File

@@ -1 +1 @@
3928044
4042238

File diff suppressed because one or more lines are too long