diff --git a/api/index.html b/api/index.html
index 066384c..2c88a60 100644
--- a/api/index.html
+++ b/api/index.html
@@ -184,7 +184,7 @@
padding: 8px;
text-align: left;
font-family: var(--font-family);
- font-size: 10px;
+ font-size: 10px;
}
.config-table-container {
@@ -509,7 +509,7 @@
- If the relay hasn't been configured yet, enter the relay pubkey shown during startup
+
@@ -546,116 +546,101 @@
-
-
-
-
-
-
-
-
-
-
Parameter
-
Value
-
Actions
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
RELAY CONFIGURATION
+
+
+
+
+
+
+
Parameter
+
Value
+
Actions
+
+
+
+
+
-
-
Edit Configuration
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
AUTH RULES MANAGEMENT
-
●
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
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.
-
-
-
-
-
-
+
+
Edit Configuration
+
+
-
-
-
-
-
Auth System Status
-
CHECKING...
-
- Rules: Loading...
+
+
+
+
+
+
+
+
+
+
AUTH RULES MANAGEMENT
+
+
+
+
+
+
+
+
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.
+
+
+
+
+
+
+
+
+
+
+
@@ -766,7 +751,6 @@
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 fetchConfigBtn = document.getElementById('fetch-config-btn');
// Relay connection elements
const relayConnectionUrl = document.getElementById('relay-connection-url');
@@ -992,10 +976,7 @@
// Step 4: Update UI
updateRelayConnectionStatus('connected');
- // Step 5: Update the old relay URL field for backward compatibility
- if (relayUrl) {
- relayUrl.value = url;
- }
+ // Step 5: Relay URL updated
// Step 6: Automatically load configuration and auth rules
log('Relay connected successfully. Auto-loading configuration and auth rules...', 'INFO');
@@ -1308,7 +1289,7 @@
function disconnectFromRelay() {
if (relayPool) {
console.log('Cleaning up relay pool connection...');
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
if (url) {
relayPool.close([url]);
}
@@ -1392,7 +1373,7 @@
// Clean up configuration pool
if (relayPool) {
console.log('Closing configuration pool...');
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
if (url) {
relayPool.close([url]);
}
@@ -1760,7 +1741,7 @@
currentAuthRules = responseData.data;
console.log('Updated currentAuthRules with', currentAuthRules.length, 'rules');
- // Always show the auth rules table when we receive data
+ // Always show the auth rules table when we receive data (no VIEW RULES button anymore)
console.log('Auto-showing auth rules table since we received data...');
showAuthRulesTable();
@@ -1770,7 +1751,7 @@
currentAuthRules = [];
console.log('No auth rules data received, cleared currentAuthRules');
- // Show empty table
+ // Show empty table (no VIEW RULES button anymore)
console.log('Auto-showing auth rules table with empty data...');
showAuthRulesTable();
@@ -2275,7 +2256,7 @@
console.log(`Config update event signed with ${configObjects.length} objects`);
// Publish via SimplePool with detailed error diagnostics
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
@@ -2437,9 +2418,6 @@
// DOM elements for auth rules
const authRulesSection = document.getElementById('authRulesSection');
- const authRulesStatus = document.getElementById('authRulesStatus');
- const viewAuthRulesBtn = document.getElementById('viewAuthRulesBtn');
- const addAuthRuleBtn = document.getElementById('addAuthRuleBtn');
const refreshAuthRulesBtn = document.getElementById('refreshAuthRulesBtn');
const authRulesTableContainer = document.getElementById('authRulesTableContainer');
const authRulesTableBody = document.getElementById('authRulesTableBody');
@@ -2448,9 +2426,6 @@
const authRuleFormTitle = document.getElementById('authRuleFormTitle');
const saveAuthRuleBtn = document.getElementById('saveAuthRuleBtn');
const cancelAuthRuleBtn = document.getElementById('cancelAuthRuleBtn');
- const authRulesStatusDisplay = document.getElementById('authRulesStatusDisplay');
- const authSystemStatus = document.getElementById('authSystemStatus');
- const authRulesCount = document.getElementById('authRulesCount');
// Show auth rules section after login
function showAuthRulesSection() {
@@ -2473,9 +2448,6 @@
if (authRuleFormContainer) {
authRuleFormContainer.style.display = 'none';
}
- if (authRulesStatusDisplay) {
- authRulesStatusDisplay.style.display = 'none';
- }
currentAuthRules = [];
editingAuthRule = null;
@@ -2483,28 +2455,9 @@
}
}
- // Update auth rules status indicator
+ // Update auth rules status indicator (removed - no status element)
function updateAuthRulesStatus(status) {
- if (!authRulesStatus) return;
-
- switch (status) {
- case 'ready':
- authRulesStatus.textContent = 'READY';
- authRulesStatus.className = 'status disconnected';
- break;
- case 'loading':
- authRulesStatus.textContent = 'LOADING';
- authRulesStatus.className = 'status connected';
- break;
- case 'loaded':
- authRulesStatus.textContent = 'LOADED';
- authRulesStatus.className = 'status connected';
- break;
- case 'error':
- authRulesStatus.textContent = 'ERROR';
- authRulesStatus.className = 'status error';
- break;
- }
+ // Status element removed - no-op
}
// Load auth rules from relay using admin API
@@ -2550,7 +2503,7 @@
log('Sending auth rules query to relay...', 'INFO');
// Publish via SimplePool with detailed error diagnostics
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
@@ -2636,31 +2589,21 @@
});
// Update status display
- if (authRulesCount) {
- const activeRules = rules.filter(r => r.enabled !== false).length;
- authRulesCount.textContent = `Total Rules: ${rules.length} (${activeRules} active)`;
- console.log(`Updated status display: ${rules.length} total, ${activeRules} active`);
- }
+ console.log(`Total Rules: ${rules.length}, Active Rules: ${rules.filter(r => r.enabled !== false).length}`);
console.log('=== END DISPLAY AUTH RULES DEBUG ===');
}
- // Show auth rules table
+ // Show auth rules table (automatically called when auth rules are loaded)
function showAuthRulesTable() {
console.log('=== SHOW AUTH RULES TABLE DEBUG ===');
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');
@@ -2766,7 +2709,7 @@
log('Sending delete auth rule command to relay...', 'INFO');
// Publish via SimplePool with detailed error diagnostics
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
@@ -2917,20 +2860,6 @@
};
// Auth rules event handlers
- if (viewAuthRulesBtn) {
- viewAuthRulesBtn.addEventListener('click', function (e) {
- e.preventDefault();
- showAuthRulesTable();
- });
- }
-
- if (addAuthRuleBtn) {
- addAuthRuleBtn.addEventListener('click', function (e) {
- e.preventDefault();
- showAddAuthRuleForm();
- });
- }
-
if (refreshAuthRulesBtn) {
refreshAuthRulesBtn.addEventListener('click', function (e) {
e.preventDefault();
@@ -2991,34 +2920,29 @@
// Add blacklist rule (updated to use combined input)
function addBlacklistRule() {
const input = document.getElementById('authRulePubkey');
- const statusDiv = document.getElementById('authRuleStatus');
- if (!input || !statusDiv) return;
+ if (!input) return;
const inputValue = input.value.trim();
if (!inputValue) {
- statusDiv.className = 'rule-status error';
- statusDiv.textContent = 'Please enter a pubkey or nsec';
+ log('Please enter a pubkey or nsec', 'ERROR');
return;
}
// Convert nsec to hex if needed
const hexPubkey = nsecToHex(inputValue);
if (!hexPubkey) {
- statusDiv.className = 'rule-status error';
- statusDiv.textContent = 'Invalid pubkey format. Please enter nsec1... or 64-character hex';
+ log('Invalid pubkey format. Please enter nsec1... or 64-character hex', 'ERROR');
return;
}
// Validate hex length
if (hexPubkey.length !== 64) {
- statusDiv.className = 'rule-status error';
- statusDiv.textContent = 'Invalid pubkey length. Must be exactly 64 characters';
+ log('Invalid pubkey length. Must be exactly 64 characters', 'ERROR');
return;
}
- statusDiv.className = 'rule-status';
- statusDiv.textContent = 'Adding to blacklist...';
+ log('Adding to blacklist...', 'INFO');
// Create auth rule data
const ruleData = {
@@ -3031,8 +2955,7 @@
// Add to WebSocket queue for processing
addAuthRuleViaWebSocket(ruleData)
.then(() => {
- statusDiv.className = 'rule-status success';
- statusDiv.textContent = `Pubkey ${hexPubkey.substring(0, 16)}... added to blacklist`;
+ log(`Pubkey ${hexPubkey.substring(0, 16)}... added to blacklist`, 'INFO');
input.value = '';
// Refresh auth rules display if visible
@@ -3041,38 +2964,33 @@
}
})
.catch(error => {
- statusDiv.className = 'rule-status error';
- statusDiv.textContent = `Failed to add rule: ${error.message}`;
+ log(`Failed to add rule: ${error.message}`, 'ERROR');
});
}
// Add whitelist rule (updated to use combined input)
function addWhitelistRule() {
const input = document.getElementById('authRulePubkey');
- const statusDiv = document.getElementById('authRuleStatus');
const warningDiv = document.getElementById('whitelistWarning');
- if (!input || !statusDiv) return;
+ if (!input) return;
const inputValue = input.value.trim();
if (!inputValue) {
- statusDiv.className = 'rule-status error';
- statusDiv.textContent = 'Please enter a pubkey or nsec';
+ log('Please enter a pubkey or nsec', 'ERROR');
return;
}
// Convert nsec to hex if needed
const hexPubkey = nsecToHex(inputValue);
if (!hexPubkey) {
- statusDiv.className = 'rule-status error';
- statusDiv.textContent = 'Invalid pubkey format. Please enter nsec1... or 64-character hex';
+ log('Invalid pubkey format. Please enter nsec1... or 64-character hex', 'ERROR');
return;
}
// Validate hex length
if (hexPubkey.length !== 64) {
- statusDiv.className = 'rule-status error';
- statusDiv.textContent = 'Invalid pubkey length. Must be exactly 64 characters';
+ log('Invalid pubkey length. Must be exactly 64 characters', 'ERROR');
return;
}
@@ -3081,8 +2999,7 @@
warningDiv.style.display = 'block';
}
- statusDiv.className = 'rule-status';
- statusDiv.textContent = 'Adding to whitelist...';
+ log('Adding to whitelist...', 'INFO');
// Create auth rule data
const ruleData = {
@@ -3095,8 +3012,7 @@
// Add to WebSocket queue for processing
addAuthRuleViaWebSocket(ruleData)
.then(() => {
- statusDiv.className = 'rule-status success';
- statusDiv.textContent = `Pubkey ${hexPubkey.substring(0, 16)}... added to whitelist`;
+ log(`Pubkey ${hexPubkey.substring(0, 16)}... added to whitelist`, 'INFO');
input.value = '';
// Refresh auth rules display if visible
@@ -3105,8 +3021,7 @@
}
})
.catch(error => {
- statusDiv.className = 'rule-status error';
- statusDiv.textContent = `Failed to add rule: ${error.message}`;
+ log(`Failed to add rule: ${error.message}`, 'ERROR');
});
}
@@ -3179,7 +3094,7 @@
}
// Publish via SimplePool with detailed error diagnostics
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
@@ -3285,7 +3200,7 @@
logTestEvent('SENT', `Get Auth Rules event: ${JSON.stringify(signedEvent)}`, 'EVENT');
// Publish via SimplePool with detailed error diagnostics
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
@@ -3359,7 +3274,7 @@
logTestEvent('SENT', `Clear Auth Rules event: ${JSON.stringify(signedEvent)}`, 'EVENT');
// Publish via SimplePool with detailed error diagnostics
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
@@ -3442,7 +3357,7 @@
logTestEvent('SENT', `Add Blacklist event: ${JSON.stringify(signedEvent)}`, 'EVENT');
// Publish via SimplePool with detailed error diagnostics
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
diff --git a/make_and_restart_relay.sh b/make_and_restart_relay.sh
index a9ff6d0..712c9bd 100755
--- a/make_and_restart_relay.sh
+++ b/make_and_restart_relay.sh
@@ -63,7 +63,7 @@ while [[ $# -gt 0 ]]; do
shift 2
fi
;;
- --preserve-database)
+ -d|--preserve-database)
PRESERVE_DATABASE=true
shift
;;
diff --git a/relay.pid b/relay.pid
index 872a7a5..322df05 100644
--- a/relay.pid
+++ b/relay.pid
@@ -1 +1 @@
-1371445
+1412551
diff --git a/src/main.c b/src/main.c
index ff2e4e2..5430435 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1716,7 +1716,15 @@ cJSON* generate_relay_info_json() {
return info;
}
-// Handle NIP-11 HTTP request
+// NIP-11 HTTP session data structure for managing buffer lifetime
+struct nip11_session_data {
+ char* json_buffer;
+ size_t json_length;
+ int headers_sent;
+ int body_sent;
+};
+
+// Handle NIP-11 HTTP request with proper asynchronous buffer management
int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
log_info("Handling NIP-11 relay information request");
@@ -1805,6 +1813,23 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
size_t json_len = strlen(json_string);
+ // Allocate session data to manage buffer lifetime across callbacks
+ struct nip11_session_data* session_data = malloc(sizeof(struct nip11_session_data));
+ if (!session_data) {
+ log_error("Failed to allocate NIP-11 session data");
+ free(json_string);
+ return -1;
+ }
+
+ // Store JSON buffer in session data for asynchronous handling
+ session_data->json_buffer = json_string;
+ session_data->json_length = json_len;
+ session_data->headers_sent = 0;
+ session_data->body_sent = 0;
+
+ // Store session data in WSI user data for callback access
+ lws_set_wsi_user(wsi, session_data);
+
// Prepare HTTP response with CORS headers
unsigned char buf[LWS_PRE + 1024];
unsigned char *p = &buf[LWS_PRE];
@@ -1813,70 +1838,66 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
// Add status
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) {
- free(json_string);
+ free(session_data->json_buffer);
+ free(session_data);
return -1;
}
// Add content type
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
(unsigned char*)"application/nostr+json", 22, &p, end)) {
- free(json_string);
+ free(session_data->json_buffer);
+ free(session_data);
return -1;
}
// Add content length
if (lws_add_http_header_content_length(wsi, json_len, &p, end)) {
- free(json_string);
+ free(session_data->json_buffer);
+ free(session_data);
return -1;
}
// Add CORS headers as required by NIP-11
if (lws_add_http_header_by_name(wsi, (unsigned char*)"access-control-allow-origin:",
(unsigned char*)"*", 1, &p, end)) {
- free(json_string);
+ free(session_data->json_buffer);
+ free(session_data);
return -1;
}
if (lws_add_http_header_by_name(wsi, (unsigned char*)"access-control-allow-headers:",
(unsigned char*)"content-type, accept", 20, &p, end)) {
- free(json_string);
+ free(session_data->json_buffer);
+ free(session_data);
return -1;
}
if (lws_add_http_header_by_name(wsi, (unsigned char*)"access-control-allow-methods:",
(unsigned char*)"GET, OPTIONS", 12, &p, end)) {
- free(json_string);
+ free(session_data->json_buffer);
+ free(session_data);
return -1;
}
// Finalize headers
if (lws_finalize_http_header(wsi, &p, end)) {
- free(json_string);
+ free(session_data->json_buffer);
+ free(session_data);
return -1;
}
// Write headers
if (lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS) < 0) {
- free(json_string);
+ free(session_data->json_buffer);
+ free(session_data);
return -1;
}
- // Write JSON body
- unsigned char *json_buf = malloc(LWS_PRE + json_len);
- if (!json_buf) {
- free(json_string);
- return -1;
- }
+ session_data->headers_sent = 1;
- memcpy(json_buf + LWS_PRE, json_string, json_len);
- if (lws_write(wsi, json_buf + LWS_PRE, json_len, LWS_WRITE_HTTP) < 0) {
- free(json_string);
- free(json_buf);
- return -1;
- }
+ // Request callback for body transmission
+ lws_callback_on_writable(wsi);
- free(json_buf);
-
- free(json_string);
- log_success("NIP-11 relay information served successfully");
+ log_success("NIP-11 headers sent, body transmission scheduled");
return 0;
}
@@ -3163,7 +3184,49 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
}
case LWS_CALLBACK_HTTP_WRITEABLE:
- // HTTP response continuation if needed
+ // Handle NIP-11 HTTP body transmission with proper buffer management
+ {
+ struct nip11_session_data* session_data = (struct nip11_session_data*)lws_wsi_user(wsi);
+ if (session_data && session_data->headers_sent && !session_data->body_sent) {
+ // Allocate buffer for JSON body transmission
+ unsigned char *json_buf = malloc(LWS_PRE + session_data->json_length);
+ if (!json_buf) {
+ log_error("Failed to allocate buffer for NIP-11 body transmission");
+ // Clean up session data
+ free(session_data->json_buffer);
+ free(session_data);
+ lws_set_wsi_user(wsi, NULL);
+ return -1;
+ }
+
+ // Copy JSON data to buffer
+ memcpy(json_buf + LWS_PRE, session_data->json_buffer, session_data->json_length);
+
+ // Write JSON body
+ int write_result = lws_write(wsi, json_buf + LWS_PRE, session_data->json_length, LWS_WRITE_HTTP);
+
+ // Free the transmission buffer immediately (it's been copied by libwebsockets)
+ free(json_buf);
+
+ if (write_result < 0) {
+ log_error("Failed to write NIP-11 JSON body");
+ // Clean up session data
+ free(session_data->json_buffer);
+ free(session_data);
+ lws_set_wsi_user(wsi, NULL);
+ return -1;
+ }
+
+ // Mark body as sent and clean up session data
+ session_data->body_sent = 1;
+ free(session_data->json_buffer);
+ free(session_data);
+ lws_set_wsi_user(wsi, NULL);
+
+ log_success("NIP-11 relay information served successfully");
+ return 0; // Close connection after successful transmission
+ }
+ }
break;
case LWS_CALLBACK_ESTABLISHED: