Please login with your Nostr identity to access the admin interface.
@@ -491,19 +494,21 @@
-
-
+
+
RELAY CONNECTION
-
+
-
+
If the relay hasn't been configured yet, enter the relay pubkey shown during startup
@@ -532,135 +537,143 @@
-
-
-
-
-
-
-
-
READY TO FETCH
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameter
+
Value
+
Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Edit Configuration
+
+
+
+
+
+
+
+
+
+
+
-
NO CONFIGURATION LOADED
-
-
-
+
+
+
+
AUTH RULES MANAGEMENT
+
●
+
+
+
+
+
+
+
+
+
+
+
+
-
Parameter
-
Value
+
Rule Type
+
Pattern Type
+
Pattern Value
+
Action
+
Status
Actions
-
+
-
-
-
-
-
-
-
Edit Configuration
-
-
+
+
+
+
+
+
MANAGE PUBKEY ACCESS
+
Add pubkeys to whitelist (allow) or blacklist (deny) access
+
+
+
+ Enter nsec (will auto-convert) or 64-character hex pubkey
+
+
+ ⚠️ WARNING: Adding whitelist rules changes relay behavior to whitelist-only
+ mode.
+ Only whitelisted users will be able to interact with the relay.
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
AUTH RULES MANAGEMENT
-
●
-
-
-
-
-
-
+
+
+
Auth System Status
+
CHECKING...
+
+ Rules: Loading...
+
-
-
-
-
-
-
-
Rule Type
-
Pattern Type
-
Pattern Value
-
Action
-
Status
-
Actions
-
-
-
-
-
-
-
-
-
-
-
-
MANAGE PUBKEY ACCESS
-
Add pubkeys to whitelist (allow) or blacklist (deny) access
-
-
-
- Enter nsec (will auto-convert) or 64-character hex pubkey
-
-
- ⚠️ WARNING: Adding whitelist rules changes relay behavior to whitelist-only mode.
- Only whitelisted users will be able to interact with the relay.
-
-
-
-
-
-
-
-
-
-
-
Auth System Status
-
CHECKING...
-
- Rules: Loading...
-
-
-
+
+
ADMIN API TESTS
Test the admin API functionality with real-time event logging. Login required for authenticated tests.
-
+
- SYSTEM: Test interface ready. Click buttons below to test admin API functions.
+ SYSTEM: Test interface ready. Click buttons below to test admin
+ API functions.
@@ -688,8 +701,10 @@
-
-
+
+
This pubkey will be used for blacklist/whitelist tests
@@ -743,16 +758,15 @@
let relayInfo = null;
let isRelayConnected = false;
let relayPubkey = null;
-
+
// DOM elements
const loginSection = document.getElementById('login-section');
- const mainInterface = document.getElementById('main-interface');
+ // const mainInterface = document.getElementById('main-interface');
const persistentUserName = document.getElementById('persistent-user-name');
const persistentUserPubkey = document.getElementById('persistent-user-pubkey');
const persistentUserAbout = document.getElementById('persistent-user-about');
const persistentUserDetails = document.getElementById('persistent-user-details');
const relayUrl = document.getElementById('relay-url');
- const relayStatus = document.getElementById('relay-status');
const fetchConfigBtn = document.getElementById('fetch-config-btn');
// Relay connection elements
const relayConnectionUrl = document.getElementById('relay-connection-url');
@@ -761,7 +775,6 @@
const connectRelayBtn = document.getElementById('connect-relay-btn');
const disconnectRelayBtn = document.getElementById('disconnect-relay-btn');
const testWebSocketBtn = document.getElementById('test-websocket-btn');
- const configStatus = document.getElementById('config-status');
const configDisplay = document.getElementById('config-display');
const configViewMode = document.getElementById('config-view-mode');
const configEditMode = document.getElementById('config-edit-mode');
@@ -852,33 +865,33 @@
return new Promise((resolve, reject) => {
try {
log(`Testing WebSocket connection to: ${wsUrl}`, 'INFO');
-
+
const ws = new WebSocket(wsUrl);
const timeout = setTimeout(() => {
ws.close();
reject(new Error('WebSocket connection timeout (10s)'));
}, 10000);
-
+
ws.onopen = () => {
clearTimeout(timeout);
log('WebSocket connection successful', 'INFO');
ws.close();
resolve(true);
};
-
+
ws.onerror = (error) => {
clearTimeout(timeout);
log(`WebSocket connection failed: ${error.message || 'Unknown error'}`, 'ERROR');
reject(new Error('WebSocket connection failed'));
};
-
+
ws.onclose = (event) => {
if (event.code !== 1000) { // 1000 = normal closure
clearTimeout(timeout);
reject(new Error(`WebSocket closed unexpectedly: ${event.code} ${event.reason}`));
}
};
-
+
} catch (error) {
log(`WebSocket test error: ${error.message}`, 'ERROR');
reject(error);
@@ -905,7 +918,7 @@
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
@@ -914,7 +927,7 @@
} 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).');
@@ -928,7 +941,7 @@
// 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 = {
@@ -942,7 +955,7 @@
};
}
}
-
+
} catch (nip11Error) {
// If NIP-11 completely fails (network error, etc.), require manual pubkey
const manualPubkey = relayPubkeyManual.value.trim();
@@ -1021,7 +1034,7 @@
function disconnectFromRelay() {
try {
log('Disconnecting from relay...', 'INFO');
-
+
// Clean up relay pool if exists
if (relayPool) {
const url = relayConnectionUrl.value.trim();
@@ -1031,18 +1044,18 @@
relayPool = null;
subscriptionId = null;
}
-
+
// Reset state
relayInfo = null;
relayPubkey = null;
isRelayConnected = false;
-
+
// Update UI
updateRelayConnectionStatus('disconnected');
hideRelayInfo();
-
+
log('Disconnected from relay', 'INFO');
-
+
} catch (error) {
log(`Error during relay disconnection: ${error.message}`, 'ERROR');
}
@@ -1051,7 +1064,7 @@
// Update relay connection status UI
function updateRelayConnectionStatus(status) {
if (!relayConnectionStatus) return;
-
+
switch (status) {
case 'connecting':
relayConnectionStatus.textContent = 'CONNECTING...';
@@ -1093,13 +1106,13 @@
// Check for existing authentication state with multiple API methods and retry logic
async function checkExistingAuthWithRetries() {
console.log('Starting authentication state detection with retry logic...');
-
+
const maxAttempts = 10;
const delay = 500; // ms between attempts
-
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
console.log(`Authentication detection attempt ${attempt}/${maxAttempts}`);
-
+
try {
// Method 1: Try window.NOSTR_LOGIN_LITE.getAuthState()
if (window.NOSTR_LOGIN_LITE && typeof window.NOSTR_LOGIN_LITE.getAuthState === 'function') {
@@ -1111,7 +1124,7 @@
return true;
}
}
-
+
// Method 2: Try nlLite.getPublicKey()
if (nlLite && typeof nlLite.getPublicKey === 'function') {
console.log('Trying nlLite.getPublicKey()...');
@@ -1122,7 +1135,7 @@
return true;
}
}
-
+
// Method 3: Try window.nostr.getPublicKey() (NIP-07)
if (window.nostr && typeof window.nostr.getPublicKey === 'function') {
console.log('Trying window.nostr.getPublicKey()...');
@@ -1133,7 +1146,7 @@
return true;
}
}
-
+
// Method 4: Check localStorage directly for NOSTR_LOGIN_LITE data
const localStorageData = localStorage.getItem('NOSTR_LOGIN_LITE_DATA');
if (localStorageData) {
@@ -1148,14 +1161,14 @@
console.log('Failed to parse localStorage data:', parseError.message);
}
}
-
+
console.log(`❌ Attempt ${attempt}: No authentication found via any method`);
-
+
// Wait before next attempt (except for last attempt)
if (attempt < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, delay));
}
-
+
} catch (error) {
console.log(`❌ Attempt ${attempt} failed:`, error.message);
if (attempt < maxAttempts) {
@@ -1163,7 +1176,7 @@
}
}
}
-
+
console.log('🔍 Authentication detection completed - no existing auth found after all attempts');
return false;
}
@@ -1171,19 +1184,19 @@
// Helper function to restore authentication state
async function restoreAuthenticationState(pubkey) {
console.log('🔄 Restoring authentication state for pubkey:', pubkey);
-
+
userPubkey = pubkey;
isLoggedIn = true;
-
+
// Show main interface
showMainInterface();
loadUserProfile();
updateLoginLogoutButton();
-
+
// 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');
-
+
console.log('✅ Authentication state restored successfully');
}
@@ -1273,23 +1286,21 @@
// Handle logout events
function handleLogoutEvent() {
console.log('Logout event received');
-
+
userPubkey = null;
isLoggedIn = false;
currentConfig = null;
-
+
// Clean up relay connection
disconnectFromRelay();
-
+
// Reset UI
- mainInterface.classList.add('hidden');
+ // mainInterface.classList.add('hidden');
loginSection.classList.remove('hidden');
updateConfigStatus(false);
- relayStatus.textContent = 'READY TO FETCH';
- relayStatus.className = 'status disconnected';
updateLoginLogoutButton();
hideAuthRulesSection();
-
+
console.log('Logout event handled successfully');
}
@@ -1309,7 +1320,7 @@
// Show main interface after login
function showMainInterface() {
loginSection.classList.add('hidden');
- mainInterface.classList.remove('hidden');
+ // mainInterface.classList.remove('hidden');
updateLoginLogoutButton();
}
@@ -1396,11 +1407,9 @@
currentConfig = null;
// Reset UI - keep persistent auth container visible
- mainInterface.classList.add('hidden');
+ // mainInterface.classList.add('hidden');
loginSection.classList.remove('hidden');
updateConfigStatus(false);
- relayStatus.textContent = 'READY TO FETCH';
- relayStatus.className = 'status disconnected';
updateLoginLogoutButton();
console.log('Logged out successfully');
@@ -1412,12 +1421,8 @@
function updateConfigStatus(loaded) {
if (loaded) {
- configStatus.textContent = 'CONFIGURATION LOADED';
- configStatus.className = 'status connected';
configDisplay.classList.remove('hidden');
} else {
- configStatus.textContent = 'NO CONFIGURATION LOADED';
- configStatus.className = 'status disconnected';
configDisplay.classList.add('hidden');
}
}
@@ -1445,8 +1450,6 @@
}
console.log(`Connecting to relay via SimplePool: ${url}`);
- relayStatus.textContent = 'CONNECTING...';
- relayStatus.className = 'status connected';
// Clean up existing pool
if (relayPool) {
@@ -1462,9 +1465,6 @@
console.log(`Generated subscription ID: ${subscriptionId}`);
- relayStatus.textContent = 'CONNECTED - SUBSCRIBING...';
- relayStatus.className = 'status connected';
-
// Subscribe to kind 23457 events (admin response events)
const subscription = relayPool.subscribeMany([url], [{
since: Math.floor(Date.now() / 1000),
@@ -1488,9 +1488,6 @@
// Process admin response event
processAdminResponse(event);
-
- relayStatus.textContent = 'SUBSCRIBED - LIVE UPDATES';
- relayStatus.className = 'status connected';
},
oneose() {
console.log('EOSE received - End of stored events');
@@ -1498,19 +1495,10 @@
if (!currentConfig) {
console.log('No configuration events were received');
- configStatus.textContent = 'NO CONFIGURATION EVENTS FOUND';
- configStatus.className = 'status error';
- relayStatus.textContent = 'SUBSCRIBED - NO EVENTS FOUND';
- relayStatus.className = 'status error';
- } else {
- relayStatus.textContent = 'SUBSCRIBED - LIVE UPDATES';
- relayStatus.className = 'status connected';
}
},
onclose(reason) {
console.log('Subscription closed:', reason);
- relayStatus.textContent = 'SUBSCRIPTION CLOSED';
- relayStatus.className = 'status error';
updateConfigStatus(false);
}
});
@@ -1525,9 +1513,6 @@
console.error('Configuration subscription failed:', error.message);
console.error('Configuration subscription failed:', error);
console.error('Error stack:', error.stack);
-
- relayStatus.textContent = 'SUBSCRIPTION FAILED';
- relayStatus.className = 'status error';
return false;
}
}
@@ -1589,7 +1574,7 @@
// Handle auth query responses - updated to match backend response types
if (responseData.query_type &&
(responseData.query_type.includes('auth_rules') ||
- responseData.query_type.includes('auth'))) {
+ responseData.query_type.includes('auth'))) {
console.log('Routing to auth query handler');
handleAuthQueryResponse(responseData);
return;
@@ -1605,7 +1590,7 @@
// Handle config query responses - updated to match backend response types
if (responseData.query_type &&
(responseData.query_type.includes('config') ||
- responseData.query_type.startsWith('config_'))) {
+ responseData.query_type.startsWith('config_'))) {
console.log('Routing to config query handler');
handleConfigQueryResponse(responseData);
return;
@@ -1649,7 +1634,7 @@
// Convert the config response data to the format expected by displayConfiguration
if (responseData.data && responseData.data.length > 0) {
console.log('Converting config response to display format...');
-
+
// Create a synthetic event structure for displayConfiguration
const syntheticEvent = {
id: 'config_response_' + Date.now(),
@@ -1659,7 +1644,7 @@
content: 'Configuration from admin API',
tags: []
};
-
+
// Convert config data to tags format
responseData.data.forEach(config => {
const key = config.key || config.config_key;
@@ -1668,25 +1653,23 @@
syntheticEvent.tags.push([key, value]);
}
});
-
+
console.log('Synthetic event created:', syntheticEvent);
console.log('Calling displayConfiguration with synthetic event...');
-
+
// Display the configuration using the original display function
displayConfiguration(syntheticEvent);
-
+
log(`Configuration loaded: ${responseData.total_results} parameters`, 'INFO');
} else {
console.log('No configuration data received');
updateConfigStatus(false);
- configStatus.textContent = 'NO CONFIGURATION DATA RECEIVED';
- configStatus.className = 'status error';
}
// Also log to test interface for debugging
if (typeof logTestEvent === 'function') {
logTestEvent('RECV', `Config query response: ${responseData.query_type}, ${responseData.total_results} results`, 'CONFIG_QUERY');
-
+
if (responseData.data && responseData.data.length > 0) {
logTestEvent('RECV', '=== CONFIGURATION VALUES ===', 'CONFIG');
responseData.data.forEach((config, index) => {
@@ -1694,7 +1677,7 @@
const value = config.value || config.config_value || 'undefined';
const category = config.category || 'general';
const dataType = config.data_type || 'string';
-
+
logTestEvent('RECV', `${key}: ${value} (${dataType}, ${category})`, 'CONFIG');
});
logTestEvent('RECV', '=== END CONFIGURATION VALUES ===', 'CONFIG');
@@ -1714,7 +1697,7 @@
if (responseData.status === 'success') {
const updatesApplied = responseData.updates_applied || 0;
log(`Configuration updated successfully: ${updatesApplied} parameters changed`, 'INFO');
-
+
// Show success message with details
if (responseData.data && Array.isArray(responseData.data)) {
responseData.data.forEach((config, index) => {
@@ -1725,18 +1708,18 @@
}
});
}
-
+
// Automatically refresh configuration display after successful update
setTimeout(() => {
fetchConfiguration().catch(error => {
console.log('Auto-refresh configuration failed after update: ' + error.message);
});
}, 1000);
-
+
} else {
const errorMessage = responseData.message || responseData.error || 'Unknown error';
log(`Configuration update failed: ${errorMessage}`, 'ERROR');
-
+
// Show detailed error information if available
if (responseData.data && Array.isArray(responseData.data)) {
responseData.data.forEach((config, index) => {
@@ -1750,7 +1733,7 @@
// Log to test interface for debugging
if (typeof logTestEvent === 'function') {
logTestEvent('RECV', `Config update response: ${responseData.status}`, 'CONFIG_UPDATE');
-
+
if (responseData.data && responseData.data.length > 0) {
responseData.data.forEach((config, index) => {
const status = config.status === 'success' ? '✓' : '✗';
@@ -1776,28 +1759,28 @@
if (responseData.data && Array.isArray(responseData.data)) {
currentAuthRules = responseData.data;
console.log('Updated currentAuthRules with', currentAuthRules.length, 'rules');
-
+
// Always show the auth rules table when we receive data
console.log('Auto-showing auth rules table since we received data...');
showAuthRulesTable();
-
+
updateAuthRulesStatus('loaded');
log(`Loaded ${responseData.total_results} auth rules from relay`, 'INFO');
} else {
currentAuthRules = [];
console.log('No auth rules data received, cleared currentAuthRules');
-
+
// Show empty table
console.log('Auto-showing auth rules table with empty data...');
showAuthRulesTable();
-
+
updateAuthRulesStatus('loaded');
log('No auth rules found on relay', 'INFO');
}
if (typeof logTestEvent === 'function') {
logTestEvent('RECV', `Auth query response: ${responseData.query_type}, ${responseData.total_results} results`, 'AUTH_QUERY');
-
+
if (responseData.data && responseData.data.length > 0) {
responseData.data.forEach((rule, index) => {
logTestEvent('RECV', `Rule ${index + 1}: ${rule.rule_type} - ${rule.pattern_value || rule.rule_target}`, 'AUTH_RULE');
@@ -1853,7 +1836,7 @@
if (responseData.status === 'success') {
const rulesProcessed = responseData.rules_processed || 0;
log(`Successfully processed ${rulesProcessed} auth rule modifications`, 'INFO');
-
+
// Refresh the auth rules display to show the new rules
if (authRulesTableContainer && authRulesTableContainer.style.display !== 'none') {
loadAuthRules();
@@ -1864,7 +1847,7 @@
if (typeof logTestEvent === 'function') {
logTestEvent('RECV', `Auth rule response: ${responseData.operation} - ${responseData.status}`, 'AUTH_RULE');
-
+
if (responseData.processed_rules) {
responseData.processed_rules.forEach((rule, index) => {
logTestEvent('RECV', `Processed rule ${index + 1}: ${rule.rule_type} - ${rule.pattern_value || rule.rule_target}`, 'AUTH_RULE');
@@ -1877,24 +1860,24 @@
async function decryptFromRelay(encryptedContent) {
try {
console.log('Decrypting content from relay...');
-
+
// Get the relay public key for decryption
const relayPubkey = getRelayPubkey();
-
+
// Use NIP-07 extension's NIP-44 decrypt method
if (!window.nostr || !window.nostr.nip44) {
throw new Error('NIP-44 decryption not available via NIP-07 extension');
}
-
+
const decryptedContent = await window.nostr.nip44.decrypt(relayPubkey, encryptedContent);
-
+
if (!decryptedContent) {
throw new Error('NIP-44 decryption returned empty result');
}
-
+
console.log('Successfully decrypted content from relay');
return decryptedContent;
-
+
} catch (error) {
console.error('NIP-44 decryption failed:', error);
throw error;
@@ -1905,38 +1888,38 @@
async function fetchConfiguration() {
try {
console.log('=== FETCHING CONFIGURATION VIA ADMIN API ===');
-
+
// Require both login and relay connection
if (!isLoggedIn || !userPubkey) {
throw new Error('Must be logged in to fetch configuration');
}
-
+
if (!isRelayConnected || !relayPubkey) {
throw new Error('Must be connected to relay to fetch configuration. Please use the Relay Connection section first.');
}
-
+
// First establish subscription to receive responses
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));
-
+
// Send config query command if logged in
if (isLoggedIn && userPubkey && relayPool) {
console.log('Sending config query command...');
-
+
// Create command array for getting configuration
const command_array = '["config_query", "all"]';
-
+
// Encrypt the command content using NIP-44
const encrypted_content = await encryptForRelay(command_array);
if (!encrypted_content) {
throw new Error('Failed to encrypt config query command');
}
-
+
// Create kind 23456 admin event
const configEvent = {
kind: 23456,
@@ -1947,22 +1930,22 @@
],
content: encrypted_content
};
-
+
// Sign the event
const signedEvent = await window.nostr.signEvent(configEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
console.log('Config query event signed, publishing...');
-
+
// Publish via SimplePool with detailed error diagnostics
const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -1979,29 +1962,23 @@
}
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected the event. Details: ${errorDetails}`);
}
-
+
console.log('Config query command sent successfully - waiting for response...');
- configStatus.textContent = 'CONFIGURATION QUERY SENT - WAITING FOR RESPONSE';
- configStatus.className = 'status connected';
-
+
} else {
console.log('Not logged in - only subscription established for testing');
- configStatus.textContent = 'SUBSCRIPTION ESTABLISHED - LOGIN REQUIRED FOR CONFIG QUERY';
- configStatus.className = 'status disconnected';
}
-
+
return true;
-
+
} catch (error) {
console.error('Failed to fetch configuration:', error);
- configStatus.textContent = 'CONFIGURATION FETCH FAILED';
- configStatus.className = 'status error';
return false;
}
}
@@ -2251,7 +2228,7 @@
await sendConfigUpdateCommand(configObjects);
console.log('Configuration update command sent successfully');
-
+
// Exit edit mode
exitEditMode();
@@ -2273,13 +2250,13 @@
// Create command array for config update (per README.md spec)
// Format: ["config_update", [config_objects_array]]
const command_array = JSON.stringify(["config_update", configObjects]);
-
+
// Encrypt using NIP-44
const encrypted_content = await encryptForRelay(command_array);
if (!encrypted_content) {
throw new Error('Failed to encrypt config_update command');
}
-
+
// Create kind 23456 admin event (unified admin API)
const configEvent = {
kind: 23456,
@@ -2288,22 +2265,22 @@
tags: [["p", getRelayPubkey()]], // Per README.md spec
content: encrypted_content
};
-
+
// Sign the event
const signedEvent = await window.nostr.signEvent(configEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
console.log(`Config update event signed with ${configObjects.length} objects`);
-
+
// Publish via SimplePool with detailed error diagnostics
const url = relayUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -2320,15 +2297,15 @@
}
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected config update event. Details: ${errorDetails}`);
}
-
+
console.log(`Config update command sent successfully with ${configObjects.length} configuration objects`);
-
+
// Log for testing
if (typeof logTestEvent === 'function') {
logTestEvent('SENT', `Config update command: ${configObjects.length} objects`, 'CONFIG_UPDATE');
@@ -2440,7 +2417,7 @@
log('Please enter a relay URL first', 'ERROR');
return;
}
-
+
testWebSocketConnection(url)
.then(() => {
log('WebSocket test successful', 'INFO');
@@ -2488,7 +2465,7 @@
function hideAuthRulesSection() {
if (authRulesSection) {
authRulesSection.style.display = 'none';
-
+
// Add null checks for all elements
if (authRulesTableContainer) {
authRulesTableContainer.style.display = 'none';
@@ -2499,7 +2476,7 @@
if (authRulesStatusDisplay) {
authRulesStatusDisplay.style.display = 'none';
}
-
+
currentAuthRules = [];
editingAuthRule = null;
log('Auth rules section hidden', 'INFO');
@@ -2509,7 +2486,7 @@
// Update auth rules status indicator
function updateAuthRulesStatus(status) {
if (!authRulesStatus) return;
-
+
switch (status) {
case 'ready':
authRulesStatus.textContent = 'READY';
@@ -2546,13 +2523,13 @@
// Create command array for getting all auth rules
const command_array = '["auth_query", "all"]';
-
+
// Encrypt the command content using NIP-44
const encrypted_content = await encryptForRelay(command_array);
if (!encrypted_content) {
throw new Error('Failed to encrypt auth query command');
}
-
+
// Create kind 23456 admin event
const authEvent = {
kind: 23456,
@@ -2563,22 +2540,22 @@
],
content: encrypted_content
};
-
+
// Sign the event
const signedEvent = await window.nostr.signEvent(authEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
log('Sending auth rules query to relay...', 'INFO');
-
+
// Publish via SimplePool with detailed error diagnostics
const url = relayUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -2595,13 +2572,13 @@
}
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected auth rules query event. Details: ${errorDetails}`);
}
-
+
log('Auth rules query sent successfully - waiting for response...', 'INFO');
updateAuthRulesStatus('loaded');
@@ -2620,7 +2597,7 @@
console.log('Rules to display:', rules);
console.log('Rules length:', rules ? rules.length : 'undefined');
console.log('authRulesTableContainer display:', authRulesTableContainer ? authRulesTableContainer.style.display : 'element not found');
-
+
if (!authRulesTableBody) {
console.log('ERROR: authRulesTableBody element not found');
return;
@@ -2664,7 +2641,7 @@
authRulesCount.textContent = `Total Rules: ${rules.length} (${activeRules} active)`;
console.log(`Updated status display: ${rules.length} total, ${activeRules} active`);
}
-
+
console.log('=== END DISPLAY AUTH RULES DEBUG ===');
}
@@ -2674,16 +2651,16 @@
console.log('authRulesTableContainer element:', authRulesTableContainer);
console.log('authRulesStatusDisplay element:', authRulesStatusDisplay);
console.log('Current display style:', authRulesTableContainer ? authRulesTableContainer.style.display : 'element not found');
-
+
if (authRulesTableContainer) {
authRulesTableContainer.style.display = 'block';
console.log('Set authRulesTableContainer display to block');
-
+
if (authRulesStatusDisplay) {
authRulesStatusDisplay.style.display = 'block';
console.log('Set authRulesStatusDisplay display to block');
}
-
+
// If we already have cached auth rules, display them immediately
if (currentAuthRules && currentAuthRules.length >= 0) {
console.log('Displaying cached auth rules:', currentAuthRules.length, 'rules');
@@ -2716,20 +2693,20 @@
// Show edit auth rule form
function editAuthRule(index) {
if (index < 0 || index >= currentAuthRules.length) return;
-
+
const rule = currentAuthRules[index];
editingAuthRule = { ...rule, index: index };
-
+
if (authRuleFormTitle && authRuleForm) {
authRuleFormTitle.textContent = 'Edit Auth Rule';
-
+
// Populate form fields
document.getElementById('authRuleType').value = rule.rule_type || '';
document.getElementById('authPatternType').value = rule.pattern_type || rule.operation || '';
document.getElementById('authPatternValue').value = rule.pattern_value || rule.rule_target || '';
document.getElementById('authRuleAction').value = rule.action || 'allow';
document.getElementById('authRuleDescription').value = rule.description || '';
-
+
authRuleFormContainer.style.display = 'block';
log(`Editing auth rule: ${rule.rule_type} - ${rule.pattern_value || rule.rule_target}`, 'INFO');
}
@@ -2738,15 +2715,15 @@
// Delete auth rule using admin API
async function deleteAuthRule(index) {
if (index < 0 || index >= currentAuthRules.length) return;
-
+
const rule = currentAuthRules[index];
const confirmMsg = `Delete auth rule: ${rule.rule_type} - ${rule.pattern_value || rule.rule_target}?`;
-
+
if (!confirm(confirmMsg)) return;
try {
log(`Deleting auth rule: ${rule.rule_type} - ${rule.pattern_value || rule.rule_target}`, 'INFO');
-
+
if (!isLoggedIn || !userPubkey) {
throw new Error('Must be logged in to delete auth rules');
}
@@ -2760,15 +2737,15 @@
const rule_type = rule.rule_type;
const pattern_type = rule.pattern_type || 'pubkey';
const pattern_value = rule.pattern_value || rule.rule_target;
-
+
const command_array = `["system_command", "delete_auth_rule", "${rule_type}", "${pattern_type}", "${pattern_value}"]`;
-
+
// Encrypt the command content using NIP-44
const encrypted_content = await encryptForRelay(command_array);
if (!encrypted_content) {
throw new Error('Failed to encrypt delete auth rule command');
}
-
+
// Create kind 23456 admin event
const authEvent = {
kind: 23456,
@@ -2779,22 +2756,22 @@
],
content: encrypted_content
};
-
+
// Sign the event
const signedEvent = await window.nostr.signEvent(authEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
log('Sending delete auth rule command to relay...', 'INFO');
-
+
// Publish via SimplePool with detailed error diagnostics
const url = relayUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -2811,15 +2788,15 @@
}
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected delete auth rule event. Details: ${errorDetails}`);
}
-
+
log('Delete auth rule command sent successfully - waiting for response...', 'INFO');
-
+
// Remove from local array immediately for UI responsiveness
currentAuthRules.splice(index, 1);
displayAuthRules(currentAuthRules);
@@ -2902,19 +2879,19 @@
if (editingAuthRule) {
log(`Updating auth rule: ${ruleData.rule_type} - ${ruleData.pattern_value}`, 'INFO');
-
+
// TODO: Implement actual rule update via WebSocket kind 23456 event
// For now, just update local array
currentAuthRules[editingAuthRule.index] = { ...ruleData, id: editingAuthRule.id || Date.now() };
-
+
log('Auth rule updated (placeholder implementation)', 'INFO');
} else {
log(`Adding new auth rule: ${ruleData.rule_type} - ${ruleData.pattern_value}`, 'INFO');
-
+
// TODO: Implement actual rule creation via WebSocket kind 23456 event
// For now, just add to local array
currentAuthRules.push({ ...ruleData, id: Date.now() });
-
+
log('Auth rule added (placeholder implementation)', 'INFO');
}
@@ -2928,34 +2905,34 @@
// Update existing logout and showMainInterface functions to handle auth rules
const originalLogout = logout;
- logout = async function() {
+ logout = async function () {
hideAuthRulesSection();
await originalLogout();
};
const originalShowMainInterface = showMainInterface;
- showMainInterface = function() {
+ showMainInterface = function () {
originalShowMainInterface();
showAuthRulesSection();
};
// Auth rules event handlers
if (viewAuthRulesBtn) {
- viewAuthRulesBtn.addEventListener('click', function(e) {
+ viewAuthRulesBtn.addEventListener('click', function (e) {
e.preventDefault();
showAuthRulesTable();
});
}
if (addAuthRuleBtn) {
- addAuthRuleBtn.addEventListener('click', function(e) {
+ addAuthRuleBtn.addEventListener('click', function (e) {
e.preventDefault();
showAddAuthRuleForm();
});
}
if (refreshAuthRulesBtn) {
- refreshAuthRulesBtn.addEventListener('click', function(e) {
+ refreshAuthRulesBtn.addEventListener('click', function (e) {
e.preventDefault();
loadAuthRules();
});
@@ -2966,7 +2943,7 @@
}
if (cancelAuthRuleBtn) {
- cancelAuthRuleBtn.addEventListener('click', function(e) {
+ cancelAuthRuleBtn.addEventListener('click', function (e) {
e.preventDefault();
hideAuthRuleForm();
});
@@ -2981,14 +2958,14 @@
if (!input || input.trim().length === 0) {
return null;
}
-
+
const trimmed = input.trim();
-
+
// If it's already 64-char hex, return as-is
if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {
return trimmed;
}
-
+
// If it starts with nsec1, try to decode
if (trimmed.startsWith('nsec1')) {
try {
@@ -3007,7 +2984,7 @@
return null;
}
}
-
+
return null; // Invalid format
}
@@ -3015,16 +2992,16 @@
function addBlacklistRule() {
const input = document.getElementById('authRulePubkey');
const statusDiv = document.getElementById('authRuleStatus');
-
+
if (!input || !statusDiv) return;
-
+
const inputValue = input.value.trim();
if (!inputValue) {
statusDiv.className = 'rule-status error';
statusDiv.textContent = 'Please enter a pubkey or nsec';
return;
}
-
+
// Convert nsec to hex if needed
const hexPubkey = nsecToHex(inputValue);
if (!hexPubkey) {
@@ -3032,17 +3009,17 @@
statusDiv.textContent = 'Invalid pubkey format. Please enter nsec1... or 64-character hex';
return;
}
-
+
// Validate hex length
if (hexPubkey.length !== 64) {
statusDiv.className = 'rule-status error';
statusDiv.textContent = 'Invalid pubkey length. Must be exactly 64 characters';
return;
}
-
+
statusDiv.className = 'rule-status';
statusDiv.textContent = 'Adding to blacklist...';
-
+
// Create auth rule data
const ruleData = {
rule_type: 'pubkey_blacklist',
@@ -3050,14 +3027,14 @@
pattern_value: hexPubkey,
action: 'deny'
};
-
+
// Add to WebSocket queue for processing
addAuthRuleViaWebSocket(ruleData)
.then(() => {
statusDiv.className = 'rule-status success';
statusDiv.textContent = `Pubkey ${hexPubkey.substring(0, 16)}... added to blacklist`;
input.value = '';
-
+
// Refresh auth rules display if visible
if (authRulesTableContainer && authRulesTableContainer.style.display !== 'none') {
loadAuthRules();
@@ -3074,16 +3051,16 @@
const input = document.getElementById('authRulePubkey');
const statusDiv = document.getElementById('authRuleStatus');
const warningDiv = document.getElementById('whitelistWarning');
-
+
if (!input || !statusDiv) return;
-
+
const inputValue = input.value.trim();
if (!inputValue) {
statusDiv.className = 'rule-status error';
statusDiv.textContent = 'Please enter a pubkey or nsec';
return;
}
-
+
// Convert nsec to hex if needed
const hexPubkey = nsecToHex(inputValue);
if (!hexPubkey) {
@@ -3091,22 +3068,22 @@
statusDiv.textContent = 'Invalid pubkey format. Please enter nsec1... or 64-character hex';
return;
}
-
+
// Validate hex length
if (hexPubkey.length !== 64) {
statusDiv.className = 'rule-status error';
statusDiv.textContent = 'Invalid pubkey length. Must be exactly 64 characters';
return;
}
-
+
// Show whitelist warning
if (warningDiv) {
warningDiv.style.display = 'block';
}
-
+
statusDiv.className = 'rule-status';
statusDiv.textContent = 'Adding to whitelist...';
-
+
// Create auth rule data
const ruleData = {
rule_type: 'pubkey_whitelist',
@@ -3114,14 +3091,14 @@
pattern_value: hexPubkey,
action: 'allow'
};
-
+
// Add to WebSocket queue for processing
addAuthRuleViaWebSocket(ruleData)
.then(() => {
statusDiv.className = 'rule-status success';
statusDiv.textContent = `Pubkey ${hexPubkey.substring(0, 16)}... added to whitelist`;
input.value = '';
-
+
// Refresh auth rules display if visible
if (authRulesTableContainer && authRulesTableContainer.style.display !== 'none') {
loadAuthRules();
@@ -3138,17 +3115,17 @@
if (!isLoggedIn || !userPubkey) {
throw new Error('Must be logged in to add auth rules');
}
-
+
if (!relayPool) {
throw new Error('SimplePool connection not available');
}
-
+
try {
log(`Adding auth rule: ${ruleData.rule_type} - ${ruleData.pattern_value.substring(0, 16)}...`, 'INFO');
-
+
// Map client-side rule types to command array format (matching working tests)
let commandRuleType, commandPatternType;
-
+
switch (ruleData.rule_type) {
case 'pubkey_blacklist':
commandRuleType = 'blacklist';
@@ -3165,17 +3142,17 @@
default:
throw new Error(`Unknown rule type: ${ruleData.rule_type}`);
}
-
+
// Create command array in the same format as working tests
// Format: ["blacklist", "pubkey", "abc123..."] or ["whitelist", "pubkey", "def456..."]
const command_array = `["${commandRuleType}", "${commandPatternType}", "${ruleData.pattern_value}"]`;
-
+
// Encrypt the command content using NIP-44 (same as working tests)
const encrypted_content = await encryptForRelay(command_array);
if (!encrypted_content) {
throw new Error('Failed to encrypt auth rule command');
}
-
+
// Create kind 23456 admin event with encrypted content (same as working tests)
const authEvent = {
kind: 23456,
@@ -3186,7 +3163,7 @@
],
content: encrypted_content
};
-
+
// DEBUG: Log the complete event structure being sent
console.log('=== AUTH RULE EVENT DEBUG (FIXED FORMAT) ===');
console.log('Original Rule Data:', ruleData);
@@ -3194,20 +3171,20 @@
console.log('Encrypted Content:', encrypted_content.substring(0, 50) + '...');
console.log('Auth Event (before signing):', JSON.stringify(authEvent, null, 2));
console.log('=== END AUTH RULE EVENT DEBUG ===');
-
+
// Sign the event using the standard NIP-07 interface
const signedEvent = await window.nostr.signEvent(authEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
// Publish via SimplePool with detailed error diagnostics
const url = relayUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -3224,15 +3201,15 @@
}
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected add auth rule event. Details: ${errorDetails}`);
}
-
+
log('Auth rule added successfully', 'INFO');
-
+
} catch (error) {
log(`Failed to add auth rule: ${error.message}`, 'ERROR');
throw error;
@@ -3247,11 +3224,11 @@
function logTestEvent(direction, message, type = 'INFO') {
const testLog = document.getElementById('test-event-log');
if (!testLog) return;
-
+
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
-
+
const directionColor = direction === 'SENT' ? '#007bff' : '#28a745';
logEntry.innerHTML = `
${timestamp}
@@ -3259,7 +3236,7 @@
[${type}]
${message}
`;
-
+
testLog.appendChild(logEntry);
testLog.scrollTop = testLog.scrollHeight;
}
@@ -3270,24 +3247,24 @@
logTestEvent('ERROR', 'Must be logged in to test admin API', 'ERROR');
return;
}
-
+
if (!relayPool) {
logTestEvent('ERROR', 'SimplePool connection not available', 'ERROR');
return;
}
-
+
try {
logTestEvent('INFO', 'Testing Get Auth Rules command...', 'TEST');
-
+
// Create command array for getting auth rules
const command_array = '["auth_query", "all"]';
-
+
// Encrypt the command content using NIP-44
const encrypted_content = await encryptForRelay(command_array);
if (!encrypted_content) {
throw new Error('Failed to encrypt auth query command');
}
-
+
// Create kind 23456 admin event
const authEvent = {
kind: 23456,
@@ -3298,22 +3275,22 @@
],
content: encrypted_content
};
-
+
// Sign the event
const signedEvent = await window.nostr.signEvent(authEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
logTestEvent('SENT', `Get Auth Rules event: ${JSON.stringify(signedEvent)}`, 'EVENT');
-
+
// Publish via SimplePool with detailed error diagnostics
const url = relayUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -3324,15 +3301,15 @@
logTestEvent('ERROR', `Test Add Blacklist relay ${index} publish failed: ${result.reason?.message || result.reason}`, 'PUBLISH');
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected test add blacklist event. Details: ${errorDetails}`);
}
-
+
logTestEvent('INFO', 'Get Auth Rules command sent successfully', 'SUCCESS');
-
+
} catch (error) {
logTestEvent('ERROR', `Get Auth Rules test failed: ${error.message}`, 'ERROR');
}
@@ -3344,24 +3321,24 @@
logTestEvent('ERROR', 'Must be logged in to test admin API', 'ERROR');
return;
}
-
+
if (!relayPool) {
logTestEvent('ERROR', 'SimplePool connection not available', 'ERROR');
return;
}
-
+
try {
logTestEvent('INFO', 'Testing Clear All Auth Rules command...', 'TEST');
-
+
// Create command array for clearing auth rules
const command_array = '["system_command", "clear_all_auth_rules"]';
-
+
// Encrypt the command content using NIP-44
const encrypted_content = await encryptForRelay(command_array);
if (!encrypted_content) {
throw new Error('Failed to encrypt clear auth rules command');
}
-
+
// Create kind 23456 admin event
const authEvent = {
kind: 23456,
@@ -3372,22 +3349,22 @@
],
content: encrypted_content
};
-
+
// Sign the event
const signedEvent = await window.nostr.signEvent(authEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
logTestEvent('SENT', `Clear Auth Rules event: ${JSON.stringify(signedEvent)}`, 'EVENT');
-
+
// Publish via SimplePool with detailed error diagnostics
const url = relayUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -3398,15 +3375,15 @@
logTestEvent('ERROR', `Test Add Whitelist relay ${index} publish failed: ${result.reason?.message || result.reason}`, 'PUBLISH');
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected test add whitelist event. Details: ${errorDetails}`);
}
-
+
logTestEvent('INFO', 'Clear Auth Rules command sent successfully', 'SUCCESS');
-
+
} catch (error) {
logTestEvent('ERROR', `Clear Auth Rules test failed: ${error.message}`, 'ERROR');
}
@@ -3416,35 +3393,35 @@
async function testAddBlacklist() {
const testPubkeyInput = document.getElementById('test-pubkey-input');
let testPubkey = testPubkeyInput ? testPubkeyInput.value.trim() : '';
-
+
// Use a default test pubkey if none provided
if (!testPubkey) {
testPubkey = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
logTestEvent('INFO', `Using default test pubkey: ${testPubkey}`, 'INFO');
}
-
+
if (!isLoggedIn || !userPubkey) {
logTestEvent('ERROR', 'Must be logged in to test admin API', 'ERROR');
return;
}
-
+
if (!relayPool) {
logTestEvent('ERROR', 'SimplePool connection not available', 'ERROR');
return;
}
-
+
try {
logTestEvent('INFO', `Testing Add Blacklist for pubkey: ${testPubkey.substring(0, 16)}...`, 'TEST');
-
+
// Create command array for adding blacklist rule
const command_array = `["blacklist", "pubkey", "${testPubkey}"]`;
-
+
// Encrypt the command content using NIP-44
const encrypted_content = await encryptForRelay(command_array);
if (!encrypted_content) {
throw new Error('Failed to encrypt blacklist command');
}
-
+
// Create kind 23456 admin event
const authEvent = {
kind: 23456,
@@ -3455,22 +3432,22 @@
],
content: encrypted_content
};
-
+
// Sign the event
const signedEvent = await window.nostr.signEvent(authEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
logTestEvent('SENT', `Add Blacklist event: ${JSON.stringify(signedEvent)}`, 'EVENT');
-
+
// Publish via SimplePool with detailed error diagnostics
const url = relayUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -3481,15 +3458,15 @@
logTestEvent('ERROR', `Test Config Query relay ${index} publish failed: ${result.reason?.message || result.reason}`, 'PUBLISH');
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected test config query event. Details: ${errorDetails}`);
}
-
+
logTestEvent('INFO', 'Add Blacklist command sent successfully', 'SUCCESS');
-
+
} catch (error) {
logTestEvent('ERROR', `Add Blacklist test failed: ${error.message}`, 'ERROR');
}
@@ -3499,35 +3476,35 @@
async function testAddWhitelist() {
const testPubkeyInput = document.getElementById('test-pubkey-input');
let testPubkey = testPubkeyInput ? testPubkeyInput.value.trim() : '';
-
+
// Use a default test pubkey if none provided
if (!testPubkey) {
testPubkey = 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890';
logTestEvent('INFO', `Using default test pubkey: ${testPubkey}`, 'INFO');
}
-
+
if (!isLoggedIn || !userPubkey) {
logTestEvent('ERROR', 'Must be logged in to test admin API', 'ERROR');
return;
}
-
+
if (!relayPool) {
logTestEvent('ERROR', 'SimplePool connection not available', 'ERROR');
return;
}
-
+
try {
logTestEvent('INFO', `Testing Add Whitelist for pubkey: ${testPubkey.substring(0, 16)}...`, 'TEST');
-
+
// Create command array for adding whitelist rule
const command_array = `["whitelist", "pubkey", "${testPubkey}"]`;
-
+
// Encrypt the command content using NIP-44
const encrypted_content = await encryptForRelay(command_array);
if (!encrypted_content) {
throw new Error('Failed to encrypt whitelist command');
}
-
+
// Create kind 23456 admin event
const authEvent = {
kind: 23456,
@@ -3538,22 +3515,22 @@
],
content: encrypted_content
};
-
+
// Sign the event
const signedEvent = await window.nostr.signEvent(authEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
logTestEvent('SENT', `Add Whitelist event: ${JSON.stringify(signedEvent)}`, 'EVENT');
-
+
// Publish via SimplePool
const url = relayUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -3564,15 +3541,15 @@
logTestEvent('ERROR', `Test Post Event relay ${index} publish failed: ${result.reason?.message || result.reason}`, 'PUBLISH');
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected test post event. Details: ${errorDetails}`);
}
-
+
logTestEvent('INFO', 'Add Whitelist command sent successfully', 'SUCCESS');
-
+
} catch (error) {
logTestEvent('ERROR', `Add Whitelist test failed: ${error.message}`, 'ERROR');
}
@@ -3584,24 +3561,24 @@
logTestEvent('ERROR', 'Must be logged in to test admin API', 'ERROR');
return;
}
-
+
if (!relayPool) {
logTestEvent('ERROR', 'SimplePool connection not available', 'ERROR');
return;
}
-
+
try {
logTestEvent('INFO', 'Testing Config Query command...', 'TEST');
-
+
// Create command array for getting configuration
const command_array = '["config_query", "all"]';
-
+
// Encrypt the command content using NIP-44
const encrypted_content = await encryptForRelay(command_array);
if (!encrypted_content) {
throw new Error('Failed to encrypt config query command');
}
-
+
// Create kind 23456 admin event
const configEvent = {
kind: 23456,
@@ -3612,22 +3589,22 @@
],
content: encrypted_content
};
-
+
// Sign the event
const signedEvent = await window.nostr.signEvent(configEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
logTestEvent('SENT', `Config Query event: ${JSON.stringify(signedEvent)}`, 'EVENT');
-
+
// Publish via SimplePool with detailed error diagnostics
const url = relayUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -3638,15 +3615,15 @@
logTestEvent('ERROR', `Test Config Query relay ${index} publish failed: ${result.reason?.message || result.reason}`, 'PUBLISH');
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected test config query event. Details: ${errorDetails}`);
}
-
+
logTestEvent('INFO', 'Config Query command sent successfully', 'SUCCESS');
-
+
} catch (error) {
logTestEvent('ERROR', `Config Query test failed: ${error.message}`, 'ERROR');
}
@@ -3658,15 +3635,15 @@
logTestEvent('ERROR', 'Must be logged in to test event posting', 'ERROR');
return;
}
-
+
if (!relayPool) {
logTestEvent('ERROR', 'SimplePool connection not available', 'ERROR');
return;
}
-
+
try {
logTestEvent('INFO', 'Testing basic event posting...', 'TEST');
-
+
// Create a simple kind 1 text note event
const testEvent = {
kind: 1,
@@ -3678,26 +3655,26 @@
],
content: `Test event from C-Relay Admin API at ${new Date().toISOString()}`
};
-
+
logTestEvent('SENT', `Test event (before signing): ${JSON.stringify(testEvent)}`, 'EVENT');
-
+
// Sign the event using NIP-07
const signedEvent = await window.nostr.signEvent(testEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
-
+
logTestEvent('SENT', `Signed test event: ${JSON.stringify(signedEvent)}`, 'EVENT');
-
+
// Publish via SimplePool to the same relay with detailed error diagnostics
const url = relayUrl.value.trim();
logTestEvent('INFO', `Publishing to relay: ${url}`, 'INFO');
-
+
const publishPromises = relayPool.publish([url], signedEvent);
-
+
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
const results = await Promise.allSettled(publishPromises);
-
+
// Log detailed publish results for diagnostics
let successCount = 0;
results.forEach((result, index) => {
@@ -3708,16 +3685,16 @@
logTestEvent('ERROR', `Test Post Event relay ${index} publish failed: ${result.reason?.message || result.reason}`, 'PUBLISH');
}
});
-
+
// Throw error if all relays failed
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected test post event. Details: ${errorDetails}`);
}
-
+
logTestEvent('INFO', 'Test event published successfully!', 'SUCCESS');
logTestEvent('INFO', 'Check if the event appears in the subscription above...', 'INFO');
-
+
} catch (error) {
logTestEvent('ERROR', `Post Event test failed: ${error.message}`, 'ERROR');
console.error('Post Event test error:', error);
@@ -3728,47 +3705,47 @@
async function encryptForRelay(content) {
try {
logTestEvent('INFO', `Encrypting content: ${content}`, 'DEBUG');
-
+
// Get the relay public key for encryption
const relayPubkey = getRelayPubkey();
-
+
// Check if we have access to NIP-44 encryption via nostr-tools
if (!window.NostrTools || !window.NostrTools.nip44) {
throw new Error('NIP-44 encryption not available - nostr-tools library missing');
}
-
+
// Get user's private key for encryption
// We need to use the NIP-07 extension to get the private key
if (!window.nostr || !window.nostr.nip44) {
throw new Error('NIP-44 encryption not available via NIP-07 extension');
}
-
+
// Use NIP-07 extension's NIP-44 encrypt method
const encrypted_content = await window.nostr.nip44.encrypt(relayPubkey, content);
-
+
if (!encrypted_content) {
throw new Error('NIP-44 encryption returned empty result');
}
-
+
logTestEvent('INFO', `Successfully encrypted content using NIP-44`, 'DEBUG');
logTestEvent('INFO', `Encrypted content: ${encrypted_content.substring(0, 50)}...`, 'DEBUG');
-
+
return encrypted_content;
} catch (error) {
logTestEvent('ERROR', `NIP-44 encryption failed: ${error.message}`, 'ERROR');
-
+
// Fallback: Try using nostr-tools directly if NIP-07 fails
try {
logTestEvent('INFO', 'Attempting fallback encryption with nostr-tools...', 'DEBUG');
-
+
if (!window.NostrTools || !window.NostrTools.nip44) {
throw new Error('nostr-tools NIP-44 not available');
}
-
+
// We need the user's private key, but we can't get it directly
// This is a security limitation - we should use NIP-07
throw new Error('Cannot access private key for direct encryption - use NIP-07 extension');
-
+
} catch (fallbackError) {
logTestEvent('ERROR', `Fallback encryption failed: ${fallbackError.message}`, 'ERROR');
return null;
@@ -3782,7 +3759,7 @@
if (relayPubkey && isRelayConnected) {
return relayPubkey;
}
-
+
// Fallback to hardcoded value for testing/development
log('Warning: Using hardcoded relay pubkey. Please connect to relay first.', 'WARNING');
return '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa';
@@ -3801,19 +3778,19 @@
// Generate 32 random bytes (64 hex characters) for a valid pubkey
const randomBytes = new Uint8Array(32);
crypto.getRandomValues(randomBytes);
-
+
// Convert to hex string
const hexPubkey = Array.from(randomBytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('');
-
+
// Set the generated key in the input field
const testPubkeyInput = document.getElementById('test-pubkey-input');
if (testPubkeyInput) {
testPubkeyInput.value = hexPubkey;
logTestEvent('INFO', `Generated random test pubkey: ${hexPubkey.substring(0, 16)}...`, 'KEYGEN');
}
-
+
return hexPubkey;
}
@@ -3828,31 +3805,31 @@
const testPostEventBtn = document.getElementById('test-post-event-btn');
const clearTestLogBtn = document.getElementById('clear-test-log-btn');
const generateTestKeyBtn = document.getElementById('generate-test-key-btn');
-
+
if (testGetAuthRulesBtn) {
testGetAuthRulesBtn.addEventListener('click', testGetAuthRules);
}
-
+
if (testClearAuthRulesBtn) {
testClearAuthRulesBtn.addEventListener('click', testClearAuthRules);
}
-
+
if (testAddBlacklistBtn) {
testAddBlacklistBtn.addEventListener('click', testAddBlacklist);
}
-
+
if (testAddWhitelistBtn) {
testAddWhitelistBtn.addEventListener('click', testAddWhitelist);
}
-
+
if (testConfigQueryBtn) {
testConfigQueryBtn.addEventListener('click', testConfigQuery);
}
-
+
if (testPostEventBtn) {
testPostEventBtn.addEventListener('click', testPostEvent);
}
-
+
if (clearTestLogBtn) {
clearTestLogBtn.addEventListener('click', () => {
const testLog = document.getElementById('test-event-log');
@@ -3861,11 +3838,11 @@
}
});
}
-
+
if (generateTestKeyBtn) {
generateTestKeyBtn.addEventListener('click', generateRandomTestKey);
}
-
+
// Show test input section when needed
const testInputSection = document.getElementById('test-input-section');
if (testInputSection) {
@@ -3876,10 +3853,10 @@
// Initialize the app
document.addEventListener('DOMContentLoaded', () => {
console.log('C-Relay Admin API interface loaded');
-
+
// Initialize login/logout button state
updateLoginLogoutButton();
-
+
setTimeout(() => {
initializeApp();
// Enhance SimplePool for testing after initialization
diff --git a/relay.pid b/relay.pid
index daac316..872a7a5 100644
--- a/relay.pid
+++ b/relay.pid
@@ -1 +1 @@
-1182553
+1371445