v0.7.40 - Removed event_broadcasts table and related code to fix FOREIGN KEY constraint failures preventing event insertion

This commit is contained in:
Your Name
2025-10-25 15:26:31 -04:00
parent 3dc09d55fd
commit edb73d50cf
15 changed files with 1062 additions and 347 deletions

View File

@@ -493,6 +493,24 @@ button:disabled {
border-radius: 0;
}
/* Relay Events Styles */
.status-message {
margin-top: 10px;
padding: 8px;
border-radius: var(--border-radius);
font-size: 14px;
font-family: var(--font-family);
text-align: center;
}
.relay-entry {
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius);
padding: 10px;
margin-bottom: 10px;
background: var(--secondary-color);
}
.config-value-input:focus {
border: 1px solid var(--accent-color);
background: var(--secondary-color);

View File

@@ -16,6 +16,7 @@
<li><button class="nav-item" data-page="subscriptions">Subscriptions</button></li>
<li><button class="nav-item" data-page="configuration">Configuration</button></li>
<li><button class="nav-item" data-page="authorization">Authorization</button></li>
<li><button class="nav-item" data-page="relay-events">Relay Events</button></li>
<li><button class="nav-item" data-page="dm">DM</button></li>
<li><button class="nav-item" data-page="database">Database Query</button></li>
</ul>
@@ -341,6 +342,72 @@
</div>
</div>
<!-- RELAY EVENTS Section -->
<div class="section" id="relayEventsSection" style="display: none;">
<div class="section-header">
<h2>RELAY EVENTS MANAGEMENT</h2>
</div>
<!-- Kind 0: User Metadata -->
<div class="input-group">
<h3>Kind 0: User Metadata</h3>
<div class="form-group">
<label for="kind0-name">Name:</label>
<input type="text" id="kind0-name" placeholder="Relay Name">
</div>
<div class="form-group">
<label for="kind0-about">About:</label>
<textarea id="kind0-about" rows="3" placeholder="Relay Description"></textarea>
</div>
<div class="form-group">
<label for="kind0-picture">Picture URL:</label>
<input type="url" id="kind0-picture" placeholder="https://example.com/logo.png">
</div>
<div class="form-group">
<label for="kind0-banner">Banner URL:</label>
<input type="url" id="kind0-banner" placeholder="https://example.com/banner.png">
</div>
<div class="form-group">
<label for="kind0-nip05">NIP-05:</label>
<input type="text" id="kind0-nip05" placeholder="relay@example.com">
</div>
<div class="form-group">
<label for="kind0-website">Website:</label>
<input type="url" id="kind0-website" placeholder="https://example.com">
</div>
<div class="inline-buttons">
<button type="button" id="submit-kind0-btn">UPDATE METADATA</button>
</div>
<div id="kind0-status" class="status-message"></div>
</div>
<!-- Kind 10050: DM Relay List -->
<div class="input-group">
<h3>Kind 10050: DM Relay List</h3>
<div class="form-group">
<label for="kind10050-relays">Relay URLs (one per line):</label>
<textarea id="kind10050-relays" rows="4" placeholder="wss://relay1.com&#10;wss://relay2.com"></textarea>
</div>
<div class="inline-buttons">
<button type="button" id="submit-kind10050-btn">UPDATE DM RELAYS</button>
</div>
<div id="kind10050-status" class="status-message"></div>
</div>
<!-- Kind 10002: Relay List -->
<div class="input-group">
<h3>Kind 10002: Relay List</h3>
<div id="kind10002-relay-entries">
<!-- Dynamic relay entries will be added here -->
</div>
<div class="inline-buttons">
<button type="button" id="add-relay-entry-btn">ADD RELAY</button>
<button type="button" id="submit-kind10002-btn">UPDATE RELAYS</button>
</div>
<div id="kind10002-status" class="status-message"></div>
</div>
</div>
<!-- SQL QUERY Section -->
<div class="section" id="sqlQuerySection" style="display: none;">
<div class="section-header">

View File

@@ -46,6 +46,8 @@ let pendingSqlQueries = new Map();
let eventRateChart = null;
let previousTotalEvents = 0; // Track previous total for rate calculation
// Relay Events state - now handled by main subscription
// DOM elements
const loginModal = document.getElementById('login-modal');
const loginModalContainer = document.getElementById('login-modal-container');
@@ -1048,9 +1050,9 @@ async function subscribeToConfiguration() {
// Mark as subscribed BEFORE calling subscribeMany to prevent race conditions
isSubscribed = true;
// Subscribe to kind 23457 events (admin response events), kind 4 (NIP-04 DMs), kind 1059 (NIP-17 GiftWrap), and kind 24567 (ephemeral monitoring events)
console.log('🔔 Calling relayPool.subscribeMany...');
// Subscribe to kind 23457 events (admin response events), kind 4 (NIP-04 DMs), kind 1059 (NIP-17 GiftWrap), kind 24567 (ephemeral monitoring events), and relay events (kinds 0, 10050, 10002)
console.log('🔔 Calling relayPool.subscribeMany with all filters...');
const subscription = relayPool.subscribeMany([url], [{
since: Math.floor(Date.now() / 1000) - 5, // Look back 5 seconds to avoid race condition
kinds: [23457],
@@ -1072,8 +1074,13 @@ async function subscribeToConfiguration() {
since: Math.floor(Date.now() / 1000), // Start from current time
kinds: [24567], // Real-time ephemeral monitoring events
authors: [getRelayPubkey()], // Only listen to monitoring events from the relay
"#d": isLoggedIn ? ["event_kinds", "time_stats", "top_pubkeys", "active_subscriptions", "subscription_details", "cpu_metrics"] : ["event_kinds", "time_stats", "top_pubkeys", "active_subscriptions", "cpu_metrics"], // Include subscription_details only when authenticated, cpu_metrics available to all
"#d": isLoggedIn ? ["event_kinds", "time_stats", "top_pubkeys", "subscription_details", "cpu_metrics"] : ["event_kinds", "time_stats", "top_pubkeys", "cpu_metrics"], // Include subscription_details only when authenticated, cpu_metrics available to all
limit: 50
}, {
since: Math.floor(Date.now() / 1000) - (24 * 60 * 60), // Look back 24 hours for relay events
kinds: [0, 10050, 10002], // Relay events: metadata, DM relays, relay list
authors: [getRelayPubkey()], // Only listen to relay's own events
limit: 10
}], {
async onevent(event) {
// Simplified logging - one line per event
@@ -1174,6 +1181,11 @@ async function subscribeToConfiguration() {
// Process monitoring event (logging done above)
processMonitoringEvent(event);
}
// Handle relay events (kinds 0, 10050, 10002)
if ([0, 10050, 10002].includes(event.kind)) {
handleRelayEventReceived(event);
}
},
oneose() {
console.log('EOSE received - End of stored events');
@@ -1240,9 +1252,19 @@ async function processAdminResponse(event) {
console.log('Decrypted admin response:', decryptedContent);
// Parse the decrypted JSON response
const responseData = JSON.parse(decryptedContent);
console.log('Parsed response data:', responseData);
// Try to parse as JSON first, if it fails treat as plain text
let responseData;
try {
responseData = JSON.parse(decryptedContent);
console.log('Parsed response data:', responseData);
} catch (parseError) {
// Not JSON - treat as plain text response
console.log('Response is plain text, not JSON');
responseData = {
plain_text: true,
message: decryptedContent
};
}
// Log the response for testing
if (typeof logTestEvent === 'function') {
@@ -1400,14 +1422,15 @@ async function processMonitoringEvent(event) {
updateStatsFromTopPubkeysMonitoringEvent(monitoringData);
break;
case 'active_subscriptions':
updateStatsFromActiveSubscriptionsMonitoringEvent(monitoringData);
break;
case 'subscription_details':
// Only process subscription details if user is authenticated
if (isLoggedIn) {
updateStatsFromSubscriptionDetailsMonitoringEvent(monitoringData);
// Also update the active subscriptions count from this data
if (monitoringData.data && monitoringData.data.subscriptions) {
updateStatsCell('active-subscriptions', monitoringData.data.subscriptions.length.toString());
}
}
break;
@@ -1432,6 +1455,25 @@ function handleAdminResponseData(responseData) {
console.log('Response data:', responseData);
console.log('Response query_type:', responseData.query_type);
// Handle plain text responses (from create_relay_event and other commands)
if (responseData.plain_text) {
console.log('Handling plain text response');
log(responseData.message, 'INFO');
// Show the message in relay events status if we're on that page
if (currentPage === 'relay-events') {
// Try to determine which kind based on message content
if (responseData.message.includes('Kind: 0')) {
showStatus('kind0-status', responseData.message, 'success');
} else if (responseData.message.includes('Kind: 10050')) {
showStatus('kind10050-status', responseData.message, 'success');
} else if (responseData.message.includes('Kind: 10002')) {
showStatus('kind10002-status', responseData.message, 'success');
}
}
return;
}
// Handle auth query responses - updated to match backend response types
if (responseData.query_type &&
(responseData.query_type.includes('auth_rules') ||
@@ -4022,27 +4064,14 @@ function updateStatsFromTopPubkeysMonitoringEvent(monitoringData) {
}
}
// Update statistics display from active_subscriptions monitoring event
function updateStatsFromActiveSubscriptionsMonitoringEvent(monitoringData) {
try {
if (monitoringData.data_type !== 'active_subscriptions') {
return;
}
// Update active subscriptions cell with real-time data
// The data is nested under monitoringData.data.total_subscriptions
if (monitoringData.data && monitoringData.data.total_subscriptions !== undefined) {
updateStatsCell('active-subscriptions', monitoringData.data.total_subscriptions.toString());
}
} catch (error) {
log(`Error updating active subscriptions from monitoring event: ${error.message}`, 'ERROR');
}
}
// Update statistics display from subscription_details monitoring event
function updateStatsFromSubscriptionDetailsMonitoringEvent(monitoringData) {
try {
// DEBUG: Log every subscription_details event that arrives at the webpage
console.log('subscription_details', JSON.stringify(monitoringData, null, 2));
console.log('subscription_details decoded:', monitoringData);
if (monitoringData.data_type !== 'subscription_details') {
return;
}
@@ -4598,14 +4627,15 @@ function switchPage(pageName) {
});
// Hide all sections
const sections = [
'databaseStatisticsSection',
'subscriptionDetailsSection',
'div_config',
'authRulesSection',
'nip17DMSection',
'sqlQuerySection'
];
const sections = [
'databaseStatisticsSection',
'subscriptionDetailsSection',
'div_config',
'authRulesSection',
'relayEventsSection',
'nip17DMSection',
'sqlQuerySection'
];
sections.forEach(sectionId => {
const section = document.getElementById(sectionId);
@@ -4620,6 +4650,7 @@ function switchPage(pageName) {
'subscriptions': 'subscriptionDetailsSection',
'configuration': 'div_config',
'authorization': 'authRulesSection',
'relay-events': 'relayEventsSection',
'dm': 'nip17DMSection',
'database': 'sqlQuerySection'
};
@@ -5015,13 +5046,15 @@ function displaySqlQueryResults(response) {
// Handle SQL query response (called by event listener)
function handleSqlQueryResponse(response) {
// Check if this is a response to one of our queries
if (response.request_id && pendingSqlQueries.has(response.request_id)) {
const queryInfo = pendingSqlQueries.get(response.request_id);
pendingSqlQueries.delete(response.request_id);
console.log('=== HANDLING SQL QUERY RESPONSE ===');
console.log('Response:', response);
// Display results
displaySqlQueryResults(response);
// Always display SQL query results when received
displaySqlQueryResults(response);
// Clean up any pending queries
if (response.request_id && pendingSqlQueries.has(response.request_id)) {
pendingSqlQueries.delete(response.request_id);
}
}
@@ -5240,6 +5273,346 @@ function initializeToggleButtonsFromConfig(configData) {
log('Monitoring system initialized - subscription-based activation ready', 'INFO');
}
// ================================
// RELAY EVENTS FUNCTIONS
// ================================
// Handle received relay events
function handleRelayEventReceived(event) {
console.log('Handling relay event:', event.kind, event);
switch (event.kind) {
case 0:
populateKind0Form(event);
break;
case 10050:
populateKind10050Form(event);
break;
case 10002:
populateKind10002Form(event);
break;
default:
console.log('Unknown relay event kind:', event.kind);
}
}
// Populate Kind 0 form (User Metadata)
function populateKind0Form(event) {
try {
const metadata = JSON.parse(event.content);
console.log('Populating Kind 0 form with:', metadata);
// Update form fields
const nameField = document.getElementById('kind0-name');
const aboutField = document.getElementById('kind0-about');
const pictureField = document.getElementById('kind0-picture');
const bannerField = document.getElementById('kind0-banner');
const nip05Field = document.getElementById('kind0-nip05');
const websiteField = document.getElementById('kind0-website');
if (nameField) nameField.value = metadata.name || '';
if (aboutField) aboutField.value = metadata.about || '';
if (pictureField) pictureField.value = metadata.picture || '';
if (bannerField) bannerField.value = metadata.banner || '';
if (nip05Field) nip05Field.value = metadata.nip05 || '';
if (websiteField) websiteField.value = metadata.website || '';
showStatus('kind0-status', 'Metadata loaded from relay', 'success');
} catch (error) {
console.error('Error populating Kind 0 form:', error);
showStatus('kind0-status', 'Error loading metadata', 'error');
}
}
// Populate Kind 10050 form (DM Relay List)
function populateKind10050Form(event) {
try {
console.log('Populating Kind 10050 form with tags:', event.tags);
// Extract relay URLs from "relay" tags
const relayUrls = event.tags
.filter(tag => tag[0] === 'relay' && tag[1])
.map(tag => tag[1]);
const relaysField = document.getElementById('kind10050-relays');
if (relaysField) {
relaysField.value = relayUrls.join('\n');
}
showStatus('kind10050-status', 'DM relay list loaded from relay', 'success');
} catch (error) {
console.error('Error populating Kind 10050 form:', error);
showStatus('kind10050-status', 'Error loading DM relay list', 'error');
}
}
// Populate Kind 10002 form (Relay List)
function populateKind10002Form(event) {
try {
console.log('Populating Kind 10002 form with tags:', event.tags);
// Clear existing entries
const container = document.getElementById('kind10002-relay-entries');
if (container) {
container.innerHTML = '';
}
// Extract relay entries from "r" tags
event.tags.forEach(tag => {
if (tag[0] === 'r' && tag[1]) {
const url = tag[1];
const marker = tag[2] || 'read'; // Default to read if no marker
const read = marker.includes('read');
const write = marker.includes('write');
addRelayEntry(url, read, write);
}
});
showStatus('kind10002-status', 'Relay list loaded from relay', 'success');
} catch (error) {
console.error('Error populating Kind 10002 form:', error);
showStatus('kind10002-status', 'Error loading relay list', 'error');
}
}
// Submit Kind 0 event
async function submitKind0Event() {
try {
showStatus('kind0-status', 'Submitting metadata...', 'info');
// Collect form data
const metadata = {
name: document.getElementById('kind0-name').value.trim(),
about: document.getElementById('kind0-about').value.trim(),
picture: document.getElementById('kind0-picture').value.trim(),
banner: document.getElementById('kind0-banner').value.trim(),
nip05: document.getElementById('kind0-nip05').value.trim(),
website: document.getElementById('kind0-website').value.trim()
};
// Remove empty fields
Object.keys(metadata).forEach(key => {
if (!metadata[key]) delete metadata[key];
});
// Validate required fields
if (!metadata.name) {
showStatus('kind0-status', 'Name is required', 'error');
return;
}
await sendCreateRelayEventCommand(0, metadata);
showStatus('kind0-status', 'Metadata updated successfully', 'success');
} catch (error) {
console.error('Error submitting Kind 0 event:', error);
showStatus('kind0-status', 'Error updating metadata: ' + error.message, 'error');
}
}
// Submit Kind 10050 event
async function submitKind10050Event() {
try {
showStatus('kind10050-status', 'Submitting DM relay list...', 'info');
// Parse textarea content
const relaysText = document.getElementById('kind10050-relays').value.trim();
const relays = relaysText.split('\n')
.map(url => url.trim())
.filter(url => url.length > 0)
.filter(url => isValidRelayUrl(url));
if (relays.length === 0) {
showStatus('kind10050-status', 'At least one valid relay URL is required', 'error');
return;
}
await sendCreateRelayEventCommand(10050, { relays });
showStatus('kind10050-status', 'DM relay list updated successfully', 'success');
} catch (error) {
console.error('Error submitting Kind 10050 event:', error);
showStatus('kind10050-status', 'Error updating DM relay list: ' + error.message, 'error');
}
}
// Submit Kind 10002 event
async function submitKind10002Event() {
try {
showStatus('kind10002-status', 'Submitting relay list...', 'info');
// Collect relay entries
const relays = [];
const entries = document.querySelectorAll('.relay-entry');
entries.forEach(entry => {
const url = entry.querySelector('.relay-url').value.trim();
const read = entry.querySelector('.relay-read').checked;
const write = entry.querySelector('.relay-write').checked;
if (url && isValidRelayUrl(url)) {
relays.push({
url: url,
read: read,
write: write
});
}
});
if (relays.length === 0) {
showStatus('kind10002-status', 'At least one valid relay entry is required', 'error');
return;
}
await sendCreateRelayEventCommand(10002, { relays });
showStatus('kind10002-status', 'Relay list updated successfully', 'success');
} catch (error) {
console.error('Error submitting Kind 10002 event:', error);
showStatus('kind10002-status', 'Error updating relay list: ' + error.message, 'error');
}
}
// Send create_relay_event command
async function sendCreateRelayEventCommand(kind, eventData) {
if (!isLoggedIn || !userPubkey) {
throw new Error('Must be logged in to create relay events');
}
if (!relayPool) {
throw new Error('SimplePool connection not available');
}
try {
console.log(`Sending create_relay_event command for kind ${kind}...`);
// Create command array
const command_array = ["create_relay_event", kind, eventData];
// Encrypt the command array
const encrypted_content = await encryptForRelay(JSON.stringify(command_array));
if (!encrypted_content) {
throw new Error('Failed to encrypt command array');
}
// Create kind 23456 admin event
const adminEvent = {
kind: 23456,
pubkey: userPubkey,
created_at: Math.floor(Date.now() / 1000),
tags: [["p", getRelayPubkey()]],
content: encrypted_content
};
// Sign the event
const signedEvent = await window.nostr.signEvent(adminEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
// Publish via SimplePool
const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
// Wait for publish results
const results = await Promise.allSettled(publishPromises);
let successCount = 0;
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
successCount++;
console.log(`✅ Relay event published successfully to relay ${index}`);
} else {
console.error(`❌ Relay event failed on relay ${index}:`, result.reason);
}
});
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected relay event. Details: ${errorDetails}`);
}
console.log(`Relay event command sent successfully for kind ${kind}`);
} catch (error) {
console.error(`Failed to send create_relay_event command for kind ${kind}:`, error);
throw error;
}
}
// Validation helpers
function isValidUrl(url) {
try {
new URL(url);
return true;
} catch {
return false;
}
}
function isValidRelayUrl(url) {
if (!isValidUrl(url)) return false;
return url.startsWith('ws://') || url.startsWith('wss://');
}
// UI helpers
function showStatus(elementId, message, type = 'info') {
const element = document.getElementById(elementId);
if (!element) return;
element.textContent = message;
element.className = 'status-message';
// Add type-specific styling
switch (type) {
case 'success':
element.style.color = 'var(--accent-color)';
break;
case 'error':
element.style.color = '#ff0000';
break;
case 'info':
default:
element.style.color = 'var(--primary-color)';
break;
}
}
function addRelayEntry(url = '', read = true, write = true) {
const container = document.getElementById('kind10002-relay-entries');
if (!container) return;
const entryDiv = document.createElement('div');
entryDiv.className = 'relay-entry';
entryDiv.innerHTML = `
<div class="form-group" style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;">
<input type="url" class="relay-url" placeholder="wss://relay.example.com" value="${url}" style="flex: 1; min-width: 300px; pointer-events: auto; cursor: text;">
<label style="display: flex; align-items: center; gap: 5px; white-space: nowrap;">
<input type="checkbox" class="relay-read" ${read ? 'checked' : ''}>
Read
</label>
<label style="display: flex; align-items: center; gap: 5px; white-space: nowrap;">
<input type="checkbox" class="relay-write" ${write ? 'checked' : ''}>
Write
</label>
<button type="button" onclick="removeRelayEntry(this)" style="padding: 4px 8px; font-size: 12px; white-space: nowrap;">Remove</button>
</div>
`;
container.appendChild(entryDiv);
}
function removeRelayEntry(button) {
const entry = button.closest('.relay-entry');
if (entry) {
entry.remove();
}
}
// Initialize toggle button after DOM is ready
document.addEventListener('DOMContentLoaded', function() {
console.log('=== DOM CONTENT LOADED - INITIALIZING TOGGLE BUTTON ===');
@@ -5249,4 +5622,43 @@ document.addEventListener('DOMContentLoaded', function() {
console.log('=== SETTIMEOUT CALLBACK - CALLING initializeMonitoringToggleButton ===');
initializeMonitoringToggleButton();
}, 500); // Small delay to ensure DOM is fully ready
});
// Initialize relay events functionality
initializeRelayEvents();
});
// Initialize relay events functionality
function initializeRelayEvents() {
console.log('Initializing relay events functionality...');
// Set up event handlers for relay events page
const submitKind0Btn = document.getElementById('submit-kind0-btn');
const submitKind10050Btn = document.getElementById('submit-kind10050-btn');
const submitKind10002Btn = document.getElementById('submit-kind10002-btn');
const addRelayEntryBtn = document.getElementById('add-relay-entry-btn');
if (submitKind0Btn) {
submitKind0Btn.addEventListener('click', submitKind0Event);
}
if (submitKind10050Btn) {
submitKind10050Btn.addEventListener('click', submitKind10050Event);
}
if (submitKind10002Btn) {
submitKind10002Btn.addEventListener('click', submitKind10002Event);
}
if (addRelayEntryBtn) {
addRelayEntryBtn.addEventListener('click', () => addRelayEntry());
}
// Add one empty relay entry by default for Kind 10002
const kind10002Container = document.getElementById('kind10002-relay-entries');
if (kind10002Container && kind10002Container.children.length === 0) {
addRelayEntry(); // Add one empty entry to start
console.log('Added initial empty relay entry for Kind 10002');
}
console.log('Relay events functionality initialized');
}