Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cb9b746d8 | ||
|
|
57a0089664 | ||
|
|
53f7608872 | ||
|
|
838ce5b45a |
@@ -305,6 +305,8 @@ h2 {
|
||||
border-radius: var(--border-radius);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
margin-left: 5px;
|
||||
margin-right:5px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
|
||||
@@ -54,9 +54,8 @@
|
||||
<div class="section flex-section" id="databaseStatisticsSection" style="display: none;">
|
||||
<div class="section-header">
|
||||
<h2>DATABASE STATISTICS</h2>
|
||||
<!-- Monitoring toggle button will be inserted here by JavaScript -->
|
||||
<!-- Temporarily disable auto-refresh button for real-time monitoring -->
|
||||
<!-- <button type="button" id="refresh-stats-btn" class="countdown-btn"></button> -->
|
||||
<!-- Monitoring is now subscription-based - no toggle button needed -->
|
||||
<!-- Subscribe to kind 24567 events to receive real-time monitoring data -->
|
||||
</div>
|
||||
|
||||
<!-- Event Rate Graph Container -->
|
||||
|
||||
141
api/index.js
141
api/index.js
@@ -850,7 +850,7 @@ async function subscribeToConfiguration() {
|
||||
|
||||
console.log(`Generated subscription ID: ${subscriptionId}`);
|
||||
console.log(`User pubkey ${userPubkey}`)
|
||||
// Subscribe to kind 23457 events (admin response events), kind 4 (NIP-04 DMs), kind 1059 (NIP-17 GiftWrap), and kind 34567 (monitoring events)
|
||||
// Subscribe to kind 23457 events (admin response events), kind 4 (NIP-04 DMs), kind 1059 (NIP-17 GiftWrap), and kind 24567 (ephemeral monitoring events)
|
||||
const subscription = relayPool.subscribeMany([url], [{
|
||||
since: Math.floor(Date.now() / 1000) - 5, // Look back 5 seconds to avoid race condition
|
||||
kinds: [23457],
|
||||
@@ -870,7 +870,7 @@ async function subscribeToConfiguration() {
|
||||
limit: 50
|
||||
}, {
|
||||
since: Math.floor(Date.now() / 1000), // Start from current time
|
||||
kinds: [34567], // Real-time monitoring events
|
||||
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"] : ["event_kinds", "time_stats", "top_pubkeys", "active_subscriptions"], // Include subscription_details only when authenticated
|
||||
limit: 50
|
||||
@@ -956,8 +956,8 @@ async function subscribeToConfiguration() {
|
||||
processAdminResponse(event);
|
||||
}
|
||||
|
||||
// Handle monitoring events (kind 34567)
|
||||
if (event.kind === 34567) {
|
||||
// Handle monitoring events (kind 24567 - ephemeral)
|
||||
if (event.kind === 24567) {
|
||||
console.log('=== MONITORING EVENT RECEIVED ===');
|
||||
console.log('Monitoring event:', event);
|
||||
|
||||
@@ -1145,14 +1145,14 @@ function createChartStubElements() {
|
||||
console.log('Chart stub elements created');
|
||||
}
|
||||
|
||||
// Handle monitoring events (kind 34567)
|
||||
// Handle monitoring events (kind 24567 - ephemeral)
|
||||
async function processMonitoringEvent(event) {
|
||||
try {
|
||||
console.log('=== PROCESSING MONITORING EVENT ===');
|
||||
console.log('Monitoring event:', event);
|
||||
|
||||
// Verify this is a kind 34567 monitoring event
|
||||
if (event.kind !== 34567) {
|
||||
// Verify this is a kind 24567 ephemeral monitoring event
|
||||
if (event.kind !== 24567) {
|
||||
console.log('Ignoring non-monitoring event, kind:', event.kind);
|
||||
return;
|
||||
}
|
||||
@@ -2433,29 +2433,11 @@ async function saveAuthRule(event) {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-enable monitoring when admin logs in
|
||||
// Monitoring is now subscription-based - no auto-enable needed
|
||||
// Monitoring automatically activates when someone subscribes to kind 24567 events
|
||||
async function autoEnableMonitoring() {
|
||||
if (!isLoggedIn || !relayPool) {
|
||||
log('Cannot auto-enable monitoring: not logged in or no relay connection', 'WARNING');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
log('Auto-enabling monitoring for admin session...', 'INFO');
|
||||
|
||||
// Send enable_monitoring command
|
||||
const commandArray = ["enable_monitoring"];
|
||||
const requestEvent = await sendAdminCommand(commandArray);
|
||||
|
||||
if (requestEvent) {
|
||||
log('Monitoring auto-enabled for admin session', 'INFO');
|
||||
} else {
|
||||
log('Failed to auto-enable monitoring', 'ERROR');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
log(`Failed to auto-enable monitoring: ${error.message}`, 'ERROR');
|
||||
}
|
||||
log('Monitoring system is subscription-based - no manual enable needed', 'INFO');
|
||||
log('Subscribe to kind 24567 events to receive real-time monitoring data', 'INFO');
|
||||
}
|
||||
|
||||
// Update existing logout and showMainInterface functions to handle auth rules and NIP-17 DMs
|
||||
@@ -4900,106 +4882,33 @@ function getConfigToggleButton(configKey) {
|
||||
return configToggleButtons.get(configKey);
|
||||
}
|
||||
|
||||
// Initialize toggle button for monitoring config
|
||||
// Monitoring is now subscription-based - no toggle button needed
|
||||
// Monitoring automatically activates when someone subscribes to kind 24567 events
|
||||
function initializeMonitoringToggleButton() {
|
||||
console.log('=== INITIALIZING MONITORING TOGGLE BUTTON ===');
|
||||
|
||||
// Check if button already exists to prevent duplicates
|
||||
const existingButton = getConfigToggleButton('kind_34567_reporting_enabled');
|
||||
if (existingButton) {
|
||||
console.log('Monitoring toggle button already exists, skipping creation');
|
||||
return existingButton;
|
||||
}
|
||||
|
||||
// Find the DATABASE STATISTICS section header
|
||||
const sectionHeader = document.querySelector('#databaseStatisticsSection .section-header h2');
|
||||
console.log('Section header found:', sectionHeader);
|
||||
|
||||
if (!sectionHeader) {
|
||||
log('Could not find DATABASE STATISTICS section header for toggle button', 'WARNING');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the toggle button
|
||||
const button = new ConfigToggleButton('kind_34567_reporting_enabled', sectionHeader.parentElement, {
|
||||
dataType: 'boolean',
|
||||
category: 'monitoring'
|
||||
});
|
||||
|
||||
console.log('Monitoring toggle button created:', button);
|
||||
console.log('Button element:', button.button);
|
||||
console.log('Button in DOM:', document.contains(button.button));
|
||||
|
||||
log('Monitoring toggle button initialized', 'INFO');
|
||||
return button;
|
||||
console.log('=== MONITORING IS NOW SUBSCRIPTION-BASED ===');
|
||||
console.log('No toggle button needed - monitoring activates automatically when subscribing to kind 24567');
|
||||
log('Monitoring system is subscription-based - no manual toggle required', 'INFO');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Enhanced config update response handler to update toggle buttons
|
||||
// Monitoring is subscription-based - no toggle button response handling needed
|
||||
const originalHandleConfigUpdateResponse = handleConfigUpdateResponse;
|
||||
handleConfigUpdateResponse = function(responseData) {
|
||||
console.log('=== ENHANCED CONFIG UPDATE RESPONSE HANDLER ===');
|
||||
console.log('=== CONFIG UPDATE RESPONSE HANDLER ===');
|
||||
console.log('Response data:', responseData);
|
||||
|
||||
// Call original handler
|
||||
originalHandleConfigUpdateResponse(responseData);
|
||||
|
||||
// Update toggle buttons if this was a config update response
|
||||
if (responseData.query_type === 'config_update' && responseData.status === 'success' && responseData.processed_configs) {
|
||||
console.log('Processing config update response for toggle buttons');
|
||||
responseData.processed_configs.forEach(config => {
|
||||
console.log('Processing config:', config);
|
||||
const button = getConfigToggleButton(config.key);
|
||||
console.log('Button found:', button);
|
||||
if (button) {
|
||||
const success = config.status === 'updated';
|
||||
const value = String(config.value).toLowerCase();
|
||||
console.log('Calling handleResponse with:', success, value);
|
||||
button.handleResponse(success, value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('Not a config update response or no processed_configs');
|
||||
}
|
||||
|
||||
// Also handle config query responses to initialize toggle buttons
|
||||
if ((responseData.query_type === 'config_query' || responseData.query_type === 'config_all') && responseData.status === 'success' && responseData.data) {
|
||||
console.log('Config query response - initializing toggle buttons');
|
||||
initializeToggleButtonsFromConfig(responseData);
|
||||
}
|
||||
// Monitoring is now subscription-based - no toggle buttons to update
|
||||
console.log('Monitoring system is subscription-based - no toggle buttons to handle');
|
||||
};
|
||||
|
||||
// Initialize toggle button when config is loaded
|
||||
// Monitoring is now subscription-based - no toggle buttons needed
|
||||
function initializeToggleButtonsFromConfig(configData) {
|
||||
console.log('=== INITIALIZING TOGGLE BUTTONS FROM CONFIG ===');
|
||||
console.log('Config data:', configData);
|
||||
|
||||
if (!configData || !configData.data) {
|
||||
console.log('No config data available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find monitoring enabled config
|
||||
const monitoringConfig = configData.data.find(c => c.key === 'kind_34567_reporting_enabled');
|
||||
console.log('Monitoring config found:', monitoringConfig);
|
||||
|
||||
if (monitoringConfig) {
|
||||
const button = getConfigToggleButton('kind_34567_reporting_enabled');
|
||||
console.log('Button instance:', button);
|
||||
|
||||
if (button) {
|
||||
// Convert config value to string for state setting
|
||||
const configValue = String(monitoringConfig.value).toLowerCase();
|
||||
console.log('Setting button state to:', configValue);
|
||||
|
||||
// Set initial state from config
|
||||
button.setState(configValue);
|
||||
log(`Monitoring toggle button set to: ${configValue}`, 'INFO');
|
||||
} else {
|
||||
console.log('Button instance not found in registry - button should have been created on DOM ready');
|
||||
}
|
||||
} else {
|
||||
console.log('Monitoring config not found in config data');
|
||||
}
|
||||
console.log('=== MONITORING IS SUBSCRIPTION-BASED ===');
|
||||
console.log('No toggle buttons needed - monitoring activates automatically when subscribing to kind 24567');
|
||||
log('Monitoring system initialized - subscription-based activation ready', 'INFO');
|
||||
}
|
||||
|
||||
// Initialize toggle button after DOM is ready
|
||||
|
||||
@@ -122,7 +122,7 @@ increment_version() {
|
||||
print_status "New version: $NEW_VERSION"
|
||||
|
||||
# Update version in src/main.h
|
||||
update_version_in_header "$NEW_VERSION" "$MAJOR" "$NEW_MINOR" "$NEW_PATCH"
|
||||
update_version_in_header "$NEW_VERSION" "$MAJOR" "${NEW_MINOR:-$MINOR}" "${NEW_PATCH:-$PATCH}"
|
||||
|
||||
# Export for use in other functions
|
||||
export NEW_VERSION
|
||||
|
||||
319
src/api.c
319
src/api.c
@@ -40,28 +40,14 @@ const char* get_config_value(const char* key);
|
||||
int get_config_bool(const char* key, int default_value);
|
||||
int update_config_in_table(const char* key, const char* value);
|
||||
|
||||
// Monitoring system state
|
||||
static time_t last_report_time = 0;
|
||||
// Monitoring system state (throttling now handled per-function)
|
||||
|
||||
// Forward declaration for monitoring helper function
|
||||
int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_func)(void));
|
||||
|
||||
// Monitoring system helper functions
|
||||
int is_monitoring_enabled(void) {
|
||||
return get_config_bool("kind_34567_reporting_enabled", 0);
|
||||
}
|
||||
|
||||
int get_monitoring_throttle_seconds(void) {
|
||||
return get_config_int("kind_34567_reporting_throttling_sec", 5);
|
||||
}
|
||||
|
||||
int set_monitoring_enabled(int enabled) {
|
||||
const char* value = enabled ? "1" : "0";
|
||||
if (update_config_in_table("kind_34567_reporting_enabled", value) == 0) {
|
||||
DEBUG_INFO("Monitoring enabled state changed");
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
return get_config_int("kind_24567_reporting_throttle_sec", 5);
|
||||
}
|
||||
|
||||
// Query event kind distribution from database
|
||||
@@ -233,51 +219,57 @@ cJSON* query_top_pubkeys(void) {
|
||||
return top_pubkeys;
|
||||
}
|
||||
|
||||
// Query active subscriptions from in-memory manager (NO DATABASE QUERY)
|
||||
// Query active subscriptions summary from database
|
||||
cJSON* query_active_subscriptions(void) {
|
||||
// Access the global subscription manager
|
||||
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
DEBUG_ERROR("Database not available for active subscriptions query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int total_subs = g_subscription_manager.total_subscriptions;
|
||||
// Get configuration limits
|
||||
int max_subs = g_subscription_manager.max_total_subscriptions;
|
||||
int max_per_client = g_subscription_manager.max_subscriptions_per_client;
|
||||
|
||||
// Calculate per-client statistics by iterating through active subscriptions
|
||||
// Query total active subscriptions from database
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql =
|
||||
"SELECT COUNT(*) as total_subs, "
|
||||
"COUNT(DISTINCT client_ip) as client_count "
|
||||
"FROM subscription_events "
|
||||
"WHERE event_type = 'created' AND ended_at IS NULL";
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare active subscriptions query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int total_subs = 0;
|
||||
int client_count = 0;
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
total_subs = sqlite3_column_int(stmt, 0);
|
||||
client_count = sqlite3_column_int(stmt, 1);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// Query max subscriptions per client
|
||||
int most_subs_per_client = 0;
|
||||
const char* max_sql =
|
||||
"SELECT MAX(sub_count) FROM ("
|
||||
" SELECT COUNT(*) as sub_count "
|
||||
" FROM subscription_events "
|
||||
" WHERE event_type = 'created' AND ended_at IS NULL "
|
||||
" GROUP BY client_ip"
|
||||
")";
|
||||
|
||||
// Count subscriptions per WebSocket connection
|
||||
subscription_t* current = g_subscription_manager.active_subscriptions;
|
||||
struct lws* last_wsi = NULL;
|
||||
int current_client_subs = 0;
|
||||
|
||||
while (current) {
|
||||
if (current->wsi != last_wsi) {
|
||||
// New client
|
||||
if (last_wsi != NULL) {
|
||||
client_count++;
|
||||
if (current_client_subs > most_subs_per_client) {
|
||||
most_subs_per_client = current_client_subs;
|
||||
}
|
||||
}
|
||||
last_wsi = current->wsi;
|
||||
current_client_subs = 1;
|
||||
} else {
|
||||
current_client_subs++;
|
||||
if (sqlite3_prepare_v2(g_db, max_sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
most_subs_per_client = sqlite3_column_int(stmt, 0);
|
||||
}
|
||||
current = current->next;
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
// Handle last client
|
||||
if (last_wsi != NULL) {
|
||||
client_count++;
|
||||
if (current_client_subs > most_subs_per_client) {
|
||||
most_subs_per_client = current_client_subs;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
// Calculate statistics
|
||||
double utilization_percentage = max_subs > 0 ? (total_subs * 100.0 / max_subs) : 0.0;
|
||||
double avg_subs_per_client = client_count > 0 ? (total_subs * 1.0 / client_count) : 0.0;
|
||||
@@ -301,10 +293,29 @@ cJSON* query_active_subscriptions(void) {
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
// Query detailed subscription information from in-memory manager (ADMIN ONLY)
|
||||
// Query detailed subscription information from database log (ADMIN ONLY)
|
||||
// Uses subscription_events table instead of in-memory iteration to avoid mutex contention
|
||||
cJSON* query_subscription_details(void) {
|
||||
// Access the global subscription manager
|
||||
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
DEBUG_ERROR("Database not available for subscription details query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Query active subscriptions directly from subscription_events table
|
||||
// Get subscriptions that were created but not yet closed/expired/disconnected
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql =
|
||||
"SELECT subscription_id, client_ip, filter_json, events_sent, "
|
||||
"created_at, (strftime('%s', 'now') - created_at) as duration_seconds "
|
||||
"FROM subscription_events "
|
||||
"WHERE event_type = 'created' AND ended_at IS NULL "
|
||||
"ORDER BY created_at DESC LIMIT 100";
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare subscription details query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
time_t current_time = time(NULL);
|
||||
cJSON* subscriptions_data = cJSON_CreateObject();
|
||||
@@ -314,70 +325,43 @@ cJSON* query_subscription_details(void) {
|
||||
cJSON* data = cJSON_CreateObject();
|
||||
cJSON* subscriptions_array = cJSON_CreateArray();
|
||||
|
||||
// Iterate through all active subscriptions
|
||||
subscription_t* current = g_subscription_manager.active_subscriptions;
|
||||
while (current) {
|
||||
// Iterate through query results
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
cJSON* sub_obj = cJSON_CreateObject();
|
||||
|
||||
// Basic subscription info
|
||||
cJSON_AddStringToObject(sub_obj, "id", current->id);
|
||||
cJSON_AddStringToObject(sub_obj, "client_ip", current->client_ip);
|
||||
cJSON_AddNumberToObject(sub_obj, "created_at", (double)current->created_at);
|
||||
cJSON_AddNumberToObject(sub_obj, "duration_seconds", (double)(current_time - current->created_at));
|
||||
cJSON_AddNumberToObject(sub_obj, "events_sent", current->events_sent);
|
||||
cJSON_AddBoolToObject(sub_obj, "active", current->active);
|
||||
// Extract subscription data from database
|
||||
const char* sub_id = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* client_ip = (const char*)sqlite3_column_text(stmt, 1);
|
||||
const char* filter_json = (const char*)sqlite3_column_text(stmt, 2);
|
||||
long long events_sent = sqlite3_column_int64(stmt, 3);
|
||||
long long created_at = sqlite3_column_int64(stmt, 4);
|
||||
long long duration_seconds = sqlite3_column_int64(stmt, 5);
|
||||
|
||||
// Extract filter details
|
||||
cJSON* filters_array = cJSON_CreateArray();
|
||||
subscription_filter_t* filter = current->filters;
|
||||
// Add basic subscription info
|
||||
cJSON_AddStringToObject(sub_obj, "id", sub_id ? sub_id : "");
|
||||
cJSON_AddStringToObject(sub_obj, "client_ip", client_ip ? client_ip : "");
|
||||
cJSON_AddNumberToObject(sub_obj, "created_at", (double)created_at);
|
||||
cJSON_AddNumberToObject(sub_obj, "duration_seconds", (double)duration_seconds);
|
||||
cJSON_AddNumberToObject(sub_obj, "events_sent", events_sent);
|
||||
cJSON_AddBoolToObject(sub_obj, "active", 1); // All from this view are active
|
||||
|
||||
while (filter) {
|
||||
cJSON* filter_obj = cJSON_CreateObject();
|
||||
|
||||
// Add kinds array if present
|
||||
if (filter->kinds) {
|
||||
cJSON_AddItemToObject(filter_obj, "kinds", cJSON_Duplicate(filter->kinds, 1));
|
||||
// Parse and add filter JSON if available
|
||||
if (filter_json) {
|
||||
cJSON* filters = cJSON_Parse(filter_json);
|
||||
if (filters) {
|
||||
cJSON_AddItemToObject(sub_obj, "filters", filters);
|
||||
} else {
|
||||
// If parsing fails, add empty array
|
||||
cJSON_AddItemToObject(sub_obj, "filters", cJSON_CreateArray());
|
||||
}
|
||||
|
||||
// Add authors array if present
|
||||
if (filter->authors) {
|
||||
cJSON_AddItemToObject(filter_obj, "authors", cJSON_Duplicate(filter->authors, 1));
|
||||
}
|
||||
|
||||
// Add ids array if present
|
||||
if (filter->ids) {
|
||||
cJSON_AddItemToObject(filter_obj, "ids", cJSON_Duplicate(filter->ids, 1));
|
||||
}
|
||||
|
||||
// Add since/until timestamps if set
|
||||
if (filter->since > 0) {
|
||||
cJSON_AddNumberToObject(filter_obj, "since", (double)filter->since);
|
||||
}
|
||||
if (filter->until > 0) {
|
||||
cJSON_AddNumberToObject(filter_obj, "until", (double)filter->until);
|
||||
}
|
||||
|
||||
// Add limit if set
|
||||
if (filter->limit > 0) {
|
||||
cJSON_AddNumberToObject(filter_obj, "limit", filter->limit);
|
||||
}
|
||||
|
||||
// Add tag filters if present
|
||||
if (filter->tag_filters) {
|
||||
cJSON_AddItemToObject(filter_obj, "tag_filters", cJSON_Duplicate(filter->tag_filters, 1));
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(filters_array, filter_obj);
|
||||
filter = filter->next;
|
||||
} else {
|
||||
cJSON_AddItemToObject(sub_obj, "filters", cJSON_CreateArray());
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(sub_obj, "filters", filters_array);
|
||||
cJSON_AddItemToArray(subscriptions_array, sub_obj);
|
||||
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// Add subscriptions array and count to data
|
||||
cJSON_AddItemToObject(data, "subscriptions", subscriptions_array);
|
||||
@@ -461,12 +445,12 @@ int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_f
|
||||
}
|
||||
free(relay_privkey_hex);
|
||||
|
||||
// Create monitoring event (kind 34567)
|
||||
// Create monitoring event (kind 24567 - ephemeral)
|
||||
cJSON* monitoring_event = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(monitoring_event, "id", ""); // Will be set by signing
|
||||
cJSON_AddStringToObject(monitoring_event, "pubkey", relay_pubkey);
|
||||
cJSON_AddNumberToObject(monitoring_event, "created_at", (double)time(NULL));
|
||||
cJSON_AddNumberToObject(monitoring_event, "kind", 34567);
|
||||
cJSON_AddNumberToObject(monitoring_event, "kind", 24567);
|
||||
cJSON_AddStringToObject(monitoring_event, "content", content_json);
|
||||
|
||||
// Create tags array with d tag for identification
|
||||
@@ -482,7 +466,7 @@ int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_f
|
||||
|
||||
// Use the library function to create and sign the event
|
||||
cJSON* signed_event = nostr_create_and_sign_event(
|
||||
34567, // kind
|
||||
24567, // kind (ephemeral)
|
||||
cJSON_GetStringValue(cJSON_GetObjectItem(monitoring_event, "content")), // content
|
||||
tags, // tags
|
||||
relay_privkey, // private key
|
||||
@@ -500,55 +484,36 @@ int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_f
|
||||
cJSON_Delete(monitoring_event);
|
||||
monitoring_event = signed_event;
|
||||
|
||||
// Broadcast the event to active subscriptions
|
||||
// Broadcast the ephemeral event to active subscriptions (no database storage)
|
||||
broadcast_event_to_subscriptions(monitoring_event);
|
||||
|
||||
// Store in database
|
||||
int store_result = store_event(monitoring_event);
|
||||
|
||||
cJSON_Delete(monitoring_event);
|
||||
free(content_json);
|
||||
|
||||
if (store_result != 0) {
|
||||
DEBUG_ERROR("Failed to store monitoring event (%s)", d_tag_value);
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_LOG("Monitoring event broadcast (ephemeral kind 24567, type: %s)", d_tag_value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Monitoring hook called when an event is stored
|
||||
void monitoring_on_event_stored(void) {
|
||||
// Check if monitoring is enabled
|
||||
if (!is_monitoring_enabled()) {
|
||||
// Check throttling first (cheapest check)
|
||||
static time_t last_monitoring_time = 0;
|
||||
time_t current_time = time(NULL);
|
||||
int throttle_seconds = get_monitoring_throttle_seconds();
|
||||
|
||||
if (current_time - last_monitoring_time < throttle_seconds) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check throttling
|
||||
time_t now = time(NULL);
|
||||
int throttle_seconds = get_monitoring_throttle_seconds();
|
||||
|
||||
if (now - last_report_time < throttle_seconds) {
|
||||
return; // Too soon, skip this report
|
||||
|
||||
// Check if anyone is subscribed to monitoring events (kind 24567)
|
||||
// This is the ONLY activation check needed - if someone subscribes, they want monitoring
|
||||
if (!has_subscriptions_for_kind(24567)) {
|
||||
return; // No subscribers = no expensive operations
|
||||
}
|
||||
|
||||
// Generate and broadcast monitoring event
|
||||
if (generate_monitoring_event() == 0) {
|
||||
last_report_time = now;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize monitoring system
|
||||
int init_monitoring_system(void) {
|
||||
last_report_time = 0;
|
||||
DEBUG_INFO("Monitoring system initialized");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Cleanup monitoring system
|
||||
void cleanup_monitoring_system(void) {
|
||||
// No cleanup needed for monitoring system
|
||||
DEBUG_INFO("Monitoring system cleaned up");
|
||||
|
||||
// Generate monitoring events only when someone is listening
|
||||
last_monitoring_time = current_time;
|
||||
generate_monitoring_event();
|
||||
}
|
||||
|
||||
// Forward declaration for known_configs (defined in config.c)
|
||||
@@ -2267,24 +2232,8 @@ int handle_monitoring_command(cJSON* event, const char* command, char* error_mes
|
||||
if (*p >= 'A' && *p <= 'Z') *p = *p + 32;
|
||||
}
|
||||
|
||||
// Handle commands
|
||||
if (strcmp(cmd, "enable_monitoring") == 0) {
|
||||
if (set_monitoring_enabled(1) == 0) {
|
||||
char* response_content = "✅ Monitoring enabled\n\nReal-time monitoring events will now be generated.";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
} else {
|
||||
char* response_content = "❌ Failed to enable monitoring";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
}
|
||||
} else if (strcmp(cmd, "disable_monitoring") == 0) {
|
||||
if (set_monitoring_enabled(0) == 0) {
|
||||
char* response_content = "✅ Monitoring disabled\n\nReal-time monitoring events will no longer be generated.";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
} else {
|
||||
char* response_content = "❌ Failed to disable monitoring";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
}
|
||||
} else if (strcmp(cmd, "set_monitoring_throttle") == 0) {
|
||||
// Handle set_monitoring_throttle command (only remaining monitoring command)
|
||||
if (strcmp(cmd, "set_monitoring_throttle") == 0) {
|
||||
if (arg[0] == '\0') {
|
||||
char* response_content = "❌ Missing throttle value\n\nUsage: set_monitoring_throttle <seconds>";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
@@ -2300,44 +2249,28 @@ int handle_monitoring_command(cJSON* event, const char* command, char* error_mes
|
||||
char throttle_str[16];
|
||||
snprintf(throttle_str, sizeof(throttle_str), "%ld", throttle_seconds);
|
||||
|
||||
if (update_config_in_table("kind_34567_reporting_throttling_sec", throttle_str) == 0) {
|
||||
if (update_config_in_table("kind_24567_reporting_throttle_sec", throttle_str) == 0) {
|
||||
char response_content[256];
|
||||
snprintf(response_content, sizeof(response_content),
|
||||
"✅ Monitoring throttle updated\n\nMinimum interval between monitoring events: %ld seconds", throttle_seconds);
|
||||
"✅ Monitoring throttle updated\n\n"
|
||||
"Minimum interval between monitoring events: %ld seconds\n\n"
|
||||
"ℹ️ Monitoring activates automatically when you subscribe to kind 24567 events.",
|
||||
throttle_seconds);
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
} else {
|
||||
char* response_content = "❌ Failed to update monitoring throttle";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
}
|
||||
} else if (strcmp(cmd, "monitoring_status") == 0) {
|
||||
int enabled = is_monitoring_enabled();
|
||||
int throttle = get_monitoring_throttle_seconds();
|
||||
|
||||
} else {
|
||||
char response_content[512];
|
||||
snprintf(response_content, sizeof(response_content),
|
||||
"📊 Monitoring Status\n"
|
||||
"━━━━━━━━━━━━━━━━━━━━\n"
|
||||
"\n"
|
||||
"Enabled: %s\n"
|
||||
"Throttle: %d seconds\n"
|
||||
"\n"
|
||||
"Commands:\n"
|
||||
"• enable_monitoring\n"
|
||||
"• disable_monitoring\n"
|
||||
"• set_monitoring_throttle <seconds>\n"
|
||||
"• monitoring_status",
|
||||
enabled ? "Yes" : "No", throttle);
|
||||
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
} else {
|
||||
char response_content[256];
|
||||
snprintf(response_content, sizeof(response_content),
|
||||
"❌ Unknown monitoring command: %s\n\n"
|
||||
"Available commands:\n"
|
||||
"• enable_monitoring\n"
|
||||
"• disable_monitoring\n"
|
||||
"• set_monitoring_throttle <seconds>\n"
|
||||
"• monitoring_status", cmd);
|
||||
"Available command:\n"
|
||||
"• set_monitoring_throttle <seconds>\n\n"
|
||||
"ℹ️ Monitoring is now subscription-based:\n"
|
||||
"Subscribe to kind 24567 events to receive real-time monitoring data.\n"
|
||||
"Monitoring automatically activates when subscriptions exist and deactivates when they close.",
|
||||
cmd);
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,11 +60,7 @@ char* execute_sql_query(const char* query, const char* request_id, char* error_m
|
||||
int handle_sql_query_unified(cJSON* event, const char* query, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
// Monitoring system functions
|
||||
int init_monitoring_system(void);
|
||||
void cleanup_monitoring_system(void);
|
||||
void monitoring_on_event_stored(void);
|
||||
int set_monitoring_enabled(int enabled);
|
||||
int is_monitoring_enabled(void);
|
||||
int get_monitoring_throttle_seconds(void);
|
||||
|
||||
#endif // API_H
|
||||
37
src/config.c
37
src/config.c
@@ -3,6 +3,19 @@
|
||||
#include "debug.h"
|
||||
#include "default_config_event.h"
|
||||
#include "dm_admin.h"
|
||||
|
||||
// Undefine VERSION macros before including nostr_core.h to avoid redefinition warnings
|
||||
// This must come AFTER default_config_event.h so that RELAY_VERSION macro expansion works correctly
|
||||
#ifdef VERSION
|
||||
#undef VERSION
|
||||
#endif
|
||||
#ifdef VERSION_MINOR
|
||||
#undef VERSION_MINOR
|
||||
#endif
|
||||
#ifdef VERSION_PATCH
|
||||
#undef VERSION_PATCH
|
||||
#endif
|
||||
|
||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -4099,32 +4112,18 @@ int populate_all_config_values_atomic(const char* admin_pubkey, const char* rela
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Insert monitoring system config entries
|
||||
// Insert monitoring system config entry (ephemeral kind 24567)
|
||||
// Note: Monitoring is automatically activated when clients subscribe to kind 24567
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, "kind_34567_reporting_enabled", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, "false", -1, SQLITE_STATIC); // boolean, default false
|
||||
sqlite3_bind_text(stmt, 3, "boolean", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 4, "Enable real-time monitoring event generation", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 5, "monitoring", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int(stmt, 6, 0); // does not require restart
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR("Failed to insert kind_34567_reporting_enabled: %s", sqlite3_errmsg(g_db));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, "kind_34567_reporting_throttling_sec", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 1, "kind_24567_reporting_throttle_sec", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, "5", -1, SQLITE_STATIC); // integer, default 5 seconds
|
||||
sqlite3_bind_text(stmt, 3, "integer", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 4, "Minimum seconds between monitoring event reports", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 4, "Minimum seconds between monitoring event reports (ephemeral kind 24567)", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 5, "monitoring", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int(stmt, 6, 0); // does not require restart
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR("Failed to insert kind_34567_reporting_throttling_sec: %s", sqlite3_errmsg(g_db));
|
||||
DEBUG_ERROR("Failed to insert kind_24567_reporting_throttle_sec: %s", sqlite3_errmsg(g_db));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
|
||||
@@ -72,7 +72,13 @@ static const struct {
|
||||
|
||||
// Performance Settings
|
||||
{"default_limit", "500"},
|
||||
{"max_limit", "5000"}
|
||||
{"max_limit", "5000"},
|
||||
|
||||
// Proxy Settings
|
||||
// Trust proxy headers (X-Forwarded-For, X-Real-IP) for accurate client IP detection
|
||||
// Safe for informational/debugging use. Only becomes a security concern if you implement
|
||||
// IP-based rate limiting or access control (which would require firewall protection anyway)
|
||||
{"trust_proxy_headers", "true"}
|
||||
};
|
||||
|
||||
// Number of default configuration values
|
||||
|
||||
File diff suppressed because one or more lines are too long
10
src/main.c
10
src/main.c
@@ -149,9 +149,7 @@ int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, c
|
||||
// Forward declaration for database functions
|
||||
int store_event(cJSON* event);
|
||||
|
||||
// Forward declarations for monitoring system
|
||||
void init_monitoring_system(void);
|
||||
void cleanup_monitoring_system(void);
|
||||
// Forward declaration for monitoring system
|
||||
void monitoring_on_event_stored(void);
|
||||
|
||||
// Forward declarations for NIP-11 relay information handling
|
||||
@@ -1989,9 +1987,6 @@ int main(int argc, char* argv[]) {
|
||||
// Initialize NIP-40 expiration configuration
|
||||
init_expiration_config();
|
||||
|
||||
// Initialize monitoring system
|
||||
init_monitoring_system();
|
||||
|
||||
// Update subscription manager configuration
|
||||
update_subscription_manager_config();
|
||||
|
||||
@@ -2023,9 +2018,6 @@ int main(int argc, char* argv[]) {
|
||||
ginxsom_request_validator_cleanup();
|
||||
cleanup_configuration_system();
|
||||
|
||||
// Cleanup monitoring system
|
||||
cleanup_monitoring_system();
|
||||
|
||||
// Cleanup subscription manager mutexes
|
||||
pthread_mutex_destroy(&g_subscription_manager.subscriptions_lock);
|
||||
pthread_mutex_destroy(&g_subscription_manager.ip_tracking_lock);
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
#define MAIN_H
|
||||
|
||||
// Version information (auto-updated by build system)
|
||||
#define VERSION "v0.7.29"
|
||||
#define VERSION "v0.7.33"
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR
|
||||
#define VERSION_PATCH 29
|
||||
#define VERSION_MINOR 7
|
||||
#define VERSION_PATCH 33
|
||||
|
||||
// Relay metadata (authoritative source for NIP-11 information)
|
||||
#define RELAY_NAME "C-Relay"
|
||||
|
||||
@@ -664,6 +664,38 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
return broadcasts;
|
||||
}
|
||||
|
||||
// Check if any active subscription exists for a specific event kind (thread-safe)
|
||||
int has_subscriptions_for_kind(int event_kind) {
|
||||
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
subscription_t* sub = g_subscription_manager.active_subscriptions;
|
||||
while (sub) {
|
||||
if (sub->active && sub->filters) {
|
||||
subscription_filter_t* filter = sub->filters;
|
||||
while (filter) {
|
||||
// Check if this filter includes our event kind
|
||||
if (filter->kinds && cJSON_IsArray(filter->kinds)) {
|
||||
cJSON* kind_item = NULL;
|
||||
cJSON_ArrayForEach(kind_item, filter->kinds) {
|
||||
if (cJSON_IsNumber(kind_item)) {
|
||||
int filter_kind = (int)cJSON_GetNumberValue(kind_item);
|
||||
if (filter_kind == event_kind) {
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
return 1; // Found matching subscription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
filter = filter->next;
|
||||
}
|
||||
}
|
||||
sub = sub->next;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
return 0; // No matching subscriptions
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -118,4 +118,7 @@ void log_subscription_disconnected(const char* client_ip);
|
||||
void log_event_broadcast(const char* event_id, const char* sub_id, const char* client_ip);
|
||||
void update_subscription_events_sent(const char* sub_id, int events_sent);
|
||||
|
||||
// Subscription query functions
|
||||
int has_subscriptions_for_kind(int event_kind);
|
||||
|
||||
#endif // SUBSCRIPTIONS_H
|
||||
@@ -247,7 +247,57 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
|
||||
// Get real client IP address
|
||||
char client_ip[CLIENT_IP_MAX_LENGTH];
|
||||
lws_get_peer_simple(wsi, client_ip, sizeof(client_ip));
|
||||
memset(client_ip, 0, sizeof(client_ip));
|
||||
|
||||
// Check if we should trust proxy headers
|
||||
int trust_proxy = get_config_bool("trust_proxy_headers", 0);
|
||||
|
||||
if (trust_proxy) {
|
||||
// Try to get IP from X-Forwarded-For header first
|
||||
char x_forwarded_for[CLIENT_IP_MAX_LENGTH];
|
||||
int header_len = lws_hdr_copy(wsi, x_forwarded_for, sizeof(x_forwarded_for) - 1, WSI_TOKEN_X_FORWARDED_FOR);
|
||||
|
||||
if (header_len > 0) {
|
||||
x_forwarded_for[header_len] = '\0';
|
||||
// X-Forwarded-For can contain multiple IPs (client, proxy1, proxy2, ...)
|
||||
// We want the first (leftmost) IP which is the original client
|
||||
char* comma = strchr(x_forwarded_for, ',');
|
||||
if (comma) {
|
||||
*comma = '\0'; // Truncate at first comma
|
||||
}
|
||||
// Trim leading/trailing whitespace
|
||||
char* ip_start = x_forwarded_for;
|
||||
while (*ip_start == ' ' || *ip_start == '\t') ip_start++;
|
||||
size_t ip_len = strlen(ip_start);
|
||||
while (ip_len > 0 && (ip_start[ip_len-1] == ' ' || ip_start[ip_len-1] == '\t')) {
|
||||
ip_start[--ip_len] = '\0';
|
||||
}
|
||||
if (ip_len > 0 && ip_len < CLIENT_IP_MAX_LENGTH) {
|
||||
strncpy(client_ip, ip_start, CLIENT_IP_MAX_LENGTH - 1);
|
||||
client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0';
|
||||
DEBUG_TRACE("Using X-Forwarded-For IP: %s", client_ip);
|
||||
}
|
||||
}
|
||||
|
||||
// If X-Forwarded-For didn't work, try X-Real-IP
|
||||
if (client_ip[0] == '\0') {
|
||||
char x_real_ip[CLIENT_IP_MAX_LENGTH];
|
||||
header_len = lws_hdr_copy(wsi, x_real_ip, sizeof(x_real_ip) - 1, WSI_TOKEN_HTTP_X_REAL_IP);
|
||||
|
||||
if (header_len > 0) {
|
||||
x_real_ip[header_len] = '\0';
|
||||
strncpy(client_ip, x_real_ip, CLIENT_IP_MAX_LENGTH - 1);
|
||||
client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0';
|
||||
DEBUG_TRACE("Using X-Real-IP: %s", client_ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to direct connection IP if proxy headers not available or not trusted
|
||||
if (client_ip[0] == '\0') {
|
||||
lws_get_peer_simple(wsi, client_ip, sizeof(client_ip));
|
||||
DEBUG_TRACE("Using direct connection IP: %s", client_ip);
|
||||
}
|
||||
|
||||
// Ensure client_ip is null-terminated and copy safely
|
||||
client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0';
|
||||
@@ -628,16 +678,24 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_TRACE("Storing regular event in database");
|
||||
// Regular event - store in database and broadcast
|
||||
if (store_event(event) != 0) {
|
||||
DEBUG_ERROR("Failed to store event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
DEBUG_LOG("Event stored and broadcast (kind %d)", event_kind);
|
||||
// Broadcast event to matching persistent subscriptions
|
||||
// Check if this is an ephemeral event (kinds 20000-29999)
|
||||
// Per NIP-01: ephemeral events are broadcast but never stored
|
||||
if (event_kind >= 20000 && event_kind < 30000) {
|
||||
DEBUG_TRACE("Ephemeral event (kind %d) - broadcasting without storage", event_kind);
|
||||
// Broadcast directly to subscriptions without database storage
|
||||
broadcast_event_to_subscriptions(event);
|
||||
} else {
|
||||
DEBUG_TRACE("Storing regular event in database");
|
||||
// Regular event - store in database and broadcast
|
||||
if (store_event(event) != 0) {
|
||||
DEBUG_ERROR("Failed to store event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
DEBUG_LOG("Event stored and broadcast (kind %d)", event_kind);
|
||||
// Broadcast event to matching persistent subscriptions
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
35
tests/ephemeral_test.sh
Executable file
35
tests/ephemeral_test.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Simplified Ephemeral Event Test
|
||||
# Tests that ephemeral events are broadcast to active subscriptions
|
||||
|
||||
echo "=== Generating Ephemeral Event (kind 20000) ==="
|
||||
event=$(nak event --kind 20000 --content "test ephemeral event")
|
||||
echo "$event"
|
||||
echo ""
|
||||
|
||||
echo "=== Testing Ephemeral Event Broadcast ==="
|
||||
subscription='["REQ","test_sub",{"kinds":[20000],"limit":10}]'
|
||||
echo "Subscription Filter:"
|
||||
echo "$subscription"
|
||||
echo ""
|
||||
|
||||
event_msg='["EVENT",'"$event"']'
|
||||
echo "Event Message:"
|
||||
echo "$event_msg"
|
||||
echo ""
|
||||
|
||||
echo "=== Relay Responses ==="
|
||||
(
|
||||
# Send subscription
|
||||
printf "%s\n" "$subscription"
|
||||
# Wait for subscription to establish
|
||||
sleep 1
|
||||
# Send ephemeral event on same connection
|
||||
printf "%s\n" "$event_msg"
|
||||
# Wait for responses
|
||||
sleep 2
|
||||
) | timeout 5 websocat ws://127.0.0.1:8888
|
||||
|
||||
echo ""
|
||||
echo "Test complete!"
|
||||
Reference in New Issue
Block a user