diff --git a/README.md b/README.md index a2f2387..ba16e02 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Do NOT modify the formatting, add emojis, or change the text. Keep the simple fo - [x] NIP-01: Basic protocol flow implementation - [x] NIP-09: Event deletion -- [ ] NIP-11: Relay information document +- [x] NIP-11: Relay information document - [ ] NIP-12: Generic tag queries - [ ] NIP-13: Proof of Work - [x] NIP-15: End of Stored Events Notice diff --git a/c-relay-x86_64 b/c-relay-x86_64 deleted file mode 100755 index 2ec9ed3..0000000 Binary files a/c-relay-x86_64 and /dev/null differ diff --git a/otp b/otp deleted file mode 160000 index 3d99009..0000000 --- a/otp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3d990091eb7d9e80cb23b55e38a7bf7d58d80eee diff --git a/relay.log b/relay.log index 013b08b..7f8e33b 100644 --- a/relay.log +++ b/relay.log @@ -1,131 +1,12 @@ === C Nostr Relay Server === [SUCCESS] Database connection established +[SUCCESS] Relay information initialized with default values [INFO] Starting relay server... [INFO] Starting libwebsockets-based Nostr relay server... [SUCCESS] WebSocket relay started on ws://127.0.0.1:8888 -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Handling EVENT message with full NIP-01 validation -[SUCCESS] Event stored in database -[SUCCESS] Event validated and stored successfully -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Handling EVENT message with full NIP-01 validation -[SUCCESS] Event stored in database -[SUCCESS] Event validated and stored successfully -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Handling EVENT message with full NIP-01 validation -[SUCCESS] Event stored in database -[SUCCESS] Event validated and stored successfully -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[WARNING] Subscription 'exists_1757082297' not found for removal -[INFO] Closed subscription: exists_1757082297 -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[WARNING] Subscription 'exists_1757082298' not found for removal -[INFO] Closed subscription: exists_1757082298 -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Handling EVENT message with full NIP-01 validation -[INFO] Event not found for deletion: [INFO] ... -[INFO] Event not found for deletion: [INFO] ... -[SUCCESS] Event stored in database -[INFO] Deletion request processed: 0 events deleted -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[WARNING] Subscription 'exists_1757082301' not found for removal -[INFO] Closed subscription: exists_1757082301 -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[WARNING] Subscription 'exists_1757082301' not found for removal -[INFO] Closed subscription: exists_1757082301 -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[WARNING] Subscription 'exists_1757082301' not found for removal -[INFO] Closed subscription: exists_1757082301 -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Handling EVENT message with full NIP-01 validation -[SUCCESS] Event stored in database -[INFO] Deletion request processed: 0 events deleted -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[INFO] Received WebSocket message -[WARNING] Subscription 'exists_1757082305' not found for removal -[INFO] Closed subscription: exists_1757082305 -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Handling EVENT message with full NIP-01 validation -[INFO] Event not found for deletion: ✗ Cou... -[SUCCESS] Event stored in database -[INFO] Deletion request processed: 0 events deleted -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Handling REQ message for persistent subscription -[INFO] Added subscription 'exists_1757082309' (total: 1) -[INFO] Executing SQL: SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE 1=1 ORDER BY created_at DESC LIMIT 500 -[INFO] Query returned 25 rows -[INFO] Total events sent: 25 -[INFO] Received WebSocket message -[INFO] Removed subscription 'exists_1757082309' (total: 0) -[INFO] Closed subscription: exists_1757082309 -[INFO] WebSocket connection closed -[WARNING] Subscription 'z[ìÞ.Y' not found for removal -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Handling EVENT message with full NIP-01 validation -[INFO] WebSocket connection closed -[INFO] WebSocket connection established -[INFO] Received WebSocket message -[INFO] Handling REQ message for persistent subscription -[INFO] Added subscription 'kind5_1757082309' (total: 1) -[INFO] Executing SQL: SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE 1=1 AND kind IN (5) ORDER BY created_at DESC LIMIT 500 -[INFO] Query returned 3 rows -[INFO] Total events sent: 3 -[INFO] Received WebSocket message -[INFO] Removed subscription 'kind5_1757082309' (total: 0) -[INFO] Closed subscription: kind5_1757082309 -[INFO] WebSocket connection closed -[WARNING] Subscription 'ùfìÞ.Y' not found for removal +[INFO] HTTP request received +[INFO] Handling NIP-11 relay information request +[SUCCESS] NIP-11 relay information served successfully +[INFO] HTTP request received +[INFO] Handling NIP-11 relay information request +[WARNING] HTTP request without proper Accept header for NIP-11 diff --git a/relay.pid b/relay.pid index 36c0aa0..2236e83 100644 --- a/relay.pid +++ b/relay.pid @@ -1 +1 @@ -682319 +714947 diff --git a/src/main.c b/src/main.c index 01af6de..e4922a3 100644 --- a/src/main.c +++ b/src/main.c @@ -28,6 +28,13 @@ #define SUBSCRIPTION_ID_MAX_LENGTH 64 #define CLIENT_IP_MAX_LENGTH 64 +// NIP-11 relay information configuration +#define RELAY_NAME_MAX_LENGTH 128 +#define RELAY_DESCRIPTION_MAX_LENGTH 1024 +#define RELAY_URL_MAX_LENGTH 256 +#define RELAY_CONTACT_MAX_LENGTH 128 +#define RELAY_PUBKEY_MAX_LENGTH 65 // 64 hex chars + null terminator + // Color constants for logging #define RED "\033[31m" #define GREEN "\033[32m" @@ -41,6 +48,32 @@ static sqlite3* g_db = NULL; static int g_server_running = 1; static struct lws_context *ws_context = NULL; +// NIP-11 relay information structure +struct relay_info { + char name[RELAY_NAME_MAX_LENGTH]; + char description[RELAY_DESCRIPTION_MAX_LENGTH]; + char banner[RELAY_URL_MAX_LENGTH]; + char icon[RELAY_URL_MAX_LENGTH]; + char pubkey[RELAY_PUBKEY_MAX_LENGTH]; + char contact[RELAY_CONTACT_MAX_LENGTH]; + char software[RELAY_URL_MAX_LENGTH]; + char version[64]; + char privacy_policy[RELAY_URL_MAX_LENGTH]; + char terms_of_service[RELAY_URL_MAX_LENGTH]; + cJSON* supported_nips; // Array of supported NIP numbers + cJSON* limitation; // Server limitations object + cJSON* retention; // Event retention policies array + cJSON* relay_countries; // Array of country codes + cJSON* language_tags; // Array of language tags + cJSON* tags; // Array of content tags + char posting_policy[RELAY_URL_MAX_LENGTH]; + cJSON* fees; // Payment fee structure + char payments_url[RELAY_URL_MAX_LENGTH]; +}; + +// Global relay information instance +static struct relay_info g_relay_info = {0}; + ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// @@ -151,6 +184,12 @@ 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 NIP-11 relay information handling +void init_relay_info(); +void cleanup_relay_info(); +cJSON* generate_relay_info_json(); +int handle_nip11_http_request(struct lws* wsi, const char* accept_header); + ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// @@ -922,8 +961,11 @@ int handle_deletion_request(cJSON* event, char* error_message, size_t error_size } const char* requester_pubkey = cJSON_GetStringValue(pubkey_obj); + // Extract deletion event ID and reason (for potential logging) const char* deletion_event_id = cJSON_GetStringValue(event_id_obj); const char* reason = content_obj ? cJSON_GetStringValue(content_obj) : ""; + (void)deletion_event_id; // Mark as intentionally unused for now + (void)reason; // Mark as intentionally unused for now long deletion_timestamp = (long)cJSON_GetNumberValue(created_at_obj); if (!cJSON_IsArray(tags_obj)) { @@ -1011,7 +1053,7 @@ int handle_deletion_request(cJSON* event, char* error_message, size_t error_size snprintf(debug_msg, sizeof(debug_msg), "Deletion request processed: %d events deleted", deleted_count); log_info(debug_msg); - snprintf(error_message, error_size, ""); // Success + error_message[0] = '\0'; // Success - empty error message return 0; } @@ -1176,6 +1218,352 @@ int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, c return 0; } +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +// NIP-11 RELAY INFORMATION DOCUMENT +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// + +// Initialize relay information with default values +void init_relay_info() { + // Set default relay information + strncpy(g_relay_info.name, "C Nostr Relay", sizeof(g_relay_info.name) - 1); + strncpy(g_relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_relay_info.description) - 1); + strncpy(g_relay_info.software, "https://github.com/teknari/c-relay", sizeof(g_relay_info.software) - 1); + strncpy(g_relay_info.version, "0.1.0", sizeof(g_relay_info.version) - 1); + + // Initialize supported NIPs array + g_relay_info.supported_nips = cJSON_CreateArray(); + if (g_relay_info.supported_nips) { + cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol + cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion + cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information + cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE + cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results + } + + // Initialize server limitations + g_relay_info.limitation = cJSON_CreateObject(); + if (g_relay_info.limitation) { + cJSON_AddNumberToObject(g_relay_info.limitation, "max_message_length", 16384); + cJSON_AddNumberToObject(g_relay_info.limitation, "max_subscriptions", MAX_SUBSCRIPTIONS_PER_CLIENT); + cJSON_AddNumberToObject(g_relay_info.limitation, "max_limit", 5000); + cJSON_AddNumberToObject(g_relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH); + cJSON_AddNumberToObject(g_relay_info.limitation, "max_event_tags", 100); + cJSON_AddNumberToObject(g_relay_info.limitation, "max_content_length", 8196); + cJSON_AddNumberToObject(g_relay_info.limitation, "min_pow_difficulty", 0); + cJSON_AddBoolToObject(g_relay_info.limitation, "auth_required", cJSON_False); + cJSON_AddBoolToObject(g_relay_info.limitation, "payment_required", cJSON_False); + cJSON_AddBoolToObject(g_relay_info.limitation, "restricted_writes", cJSON_False); + cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_lower_limit", 0); + cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_upper_limit", 2147483647); + cJSON_AddNumberToObject(g_relay_info.limitation, "default_limit", 500); + } + + // Initialize empty retention policies (can be configured later) + g_relay_info.retention = cJSON_CreateArray(); + + // Initialize language tags - set to global for now + g_relay_info.language_tags = cJSON_CreateArray(); + if (g_relay_info.language_tags) { + cJSON_AddItemToArray(g_relay_info.language_tags, cJSON_CreateString("*")); + } + + // Initialize relay countries - set to global for now + g_relay_info.relay_countries = cJSON_CreateArray(); + if (g_relay_info.relay_countries) { + cJSON_AddItemToArray(g_relay_info.relay_countries, cJSON_CreateString("*")); + } + + // Initialize content tags as empty array + g_relay_info.tags = cJSON_CreateArray(); + + // Initialize fees as empty object (no payment required by default) + g_relay_info.fees = cJSON_CreateObject(); + + log_success("Relay information initialized with default values"); +} + +// Clean up relay information JSON objects +void cleanup_relay_info() { + if (g_relay_info.supported_nips) { + cJSON_Delete(g_relay_info.supported_nips); + g_relay_info.supported_nips = NULL; + } + if (g_relay_info.limitation) { + cJSON_Delete(g_relay_info.limitation); + g_relay_info.limitation = NULL; + } + if (g_relay_info.retention) { + cJSON_Delete(g_relay_info.retention); + g_relay_info.retention = NULL; + } + if (g_relay_info.language_tags) { + cJSON_Delete(g_relay_info.language_tags); + g_relay_info.language_tags = NULL; + } + if (g_relay_info.relay_countries) { + cJSON_Delete(g_relay_info.relay_countries); + g_relay_info.relay_countries = NULL; + } + if (g_relay_info.tags) { + cJSON_Delete(g_relay_info.tags); + g_relay_info.tags = NULL; + } + if (g_relay_info.fees) { + cJSON_Delete(g_relay_info.fees); + g_relay_info.fees = NULL; + } +} + +// Generate NIP-11 compliant JSON document +cJSON* generate_relay_info_json() { + cJSON* info = cJSON_CreateObject(); + if (!info) { + log_error("Failed to create relay info JSON object"); + return NULL; + } + + // Add basic relay information + if (strlen(g_relay_info.name) > 0) { + cJSON_AddStringToObject(info, "name", g_relay_info.name); + } + if (strlen(g_relay_info.description) > 0) { + cJSON_AddStringToObject(info, "description", g_relay_info.description); + } + if (strlen(g_relay_info.banner) > 0) { + cJSON_AddStringToObject(info, "banner", g_relay_info.banner); + } + if (strlen(g_relay_info.icon) > 0) { + cJSON_AddStringToObject(info, "icon", g_relay_info.icon); + } + if (strlen(g_relay_info.pubkey) > 0) { + cJSON_AddStringToObject(info, "pubkey", g_relay_info.pubkey); + } + if (strlen(g_relay_info.contact) > 0) { + cJSON_AddStringToObject(info, "contact", g_relay_info.contact); + } + + // Add supported NIPs + if (g_relay_info.supported_nips) { + cJSON_AddItemToObject(info, "supported_nips", cJSON_Duplicate(g_relay_info.supported_nips, 1)); + } + + // Add software information + if (strlen(g_relay_info.software) > 0) { + cJSON_AddStringToObject(info, "software", g_relay_info.software); + } + if (strlen(g_relay_info.version) > 0) { + cJSON_AddStringToObject(info, "version", g_relay_info.version); + } + + // Add policies + if (strlen(g_relay_info.privacy_policy) > 0) { + cJSON_AddStringToObject(info, "privacy_policy", g_relay_info.privacy_policy); + } + if (strlen(g_relay_info.terms_of_service) > 0) { + cJSON_AddStringToObject(info, "terms_of_service", g_relay_info.terms_of_service); + } + if (strlen(g_relay_info.posting_policy) > 0) { + cJSON_AddStringToObject(info, "posting_policy", g_relay_info.posting_policy); + } + + // Add server limitations + if (g_relay_info.limitation) { + cJSON_AddItemToObject(info, "limitation", cJSON_Duplicate(g_relay_info.limitation, 1)); + } + + // Add retention policies if configured + if (g_relay_info.retention && cJSON_GetArraySize(g_relay_info.retention) > 0) { + cJSON_AddItemToObject(info, "retention", cJSON_Duplicate(g_relay_info.retention, 1)); + } + + // Add geographical and language information + if (g_relay_info.relay_countries) { + cJSON_AddItemToObject(info, "relay_countries", cJSON_Duplicate(g_relay_info.relay_countries, 1)); + } + if (g_relay_info.language_tags) { + cJSON_AddItemToObject(info, "language_tags", cJSON_Duplicate(g_relay_info.language_tags, 1)); + } + if (g_relay_info.tags && cJSON_GetArraySize(g_relay_info.tags) > 0) { + cJSON_AddItemToObject(info, "tags", cJSON_Duplicate(g_relay_info.tags, 1)); + } + + // Add payment information if configured + if (strlen(g_relay_info.payments_url) > 0) { + cJSON_AddStringToObject(info, "payments_url", g_relay_info.payments_url); + } + if (g_relay_info.fees && cJSON_GetObjectItem(g_relay_info.fees, "admission")) { + cJSON_AddItemToObject(info, "fees", cJSON_Duplicate(g_relay_info.fees, 1)); + } + + return info; +} + +// Handle NIP-11 HTTP request +int handle_nip11_http_request(struct lws* wsi, const char* accept_header) { + log_info("Handling NIP-11 relay information request"); + + // Check if client accepts application/nostr+json + int accepts_nostr_json = 0; + if (accept_header) { + if (strstr(accept_header, "application/nostr+json") != NULL) { + accepts_nostr_json = 1; + } + } + + if (!accepts_nostr_json) { + log_warning("HTTP request without proper Accept header for NIP-11"); + // Return 406 Not Acceptable + unsigned char buf[LWS_PRE + 256]; + unsigned char *p = &buf[LWS_PRE]; + unsigned char *start = p; + unsigned char *end = &buf[sizeof(buf) - 1]; + + if (lws_add_http_header_status(wsi, HTTP_STATUS_NOT_ACCEPTABLE, &p, end)) { + return -1; + } + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char*)"text/plain", 10, &p, end)) { + return -1; + } + if (lws_add_http_header_content_length(wsi, 0, &p, end)) { + return -1; + } + if (lws_finalize_http_header(wsi, &p, end)) { + return -1; + } + lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); + return -1; // Close connection + } + + // Generate relay information JSON + cJSON* info_json = generate_relay_info_json(); + if (!info_json) { + log_error("Failed to generate relay info JSON"); + unsigned char buf[LWS_PRE + 256]; + unsigned char *p = &buf[LWS_PRE]; + unsigned char *start = p; + unsigned char *end = &buf[sizeof(buf) - 1]; + + if (lws_add_http_header_status(wsi, HTTP_STATUS_INTERNAL_SERVER_ERROR, &p, end)) { + return -1; + } + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char*)"text/plain", 10, &p, end)) { + return -1; + } + if (lws_add_http_header_content_length(wsi, 0, &p, end)) { + return -1; + } + if (lws_finalize_http_header(wsi, &p, end)) { + return -1; + } + lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); + return -1; + } + + char* json_string = cJSON_Print(info_json); + cJSON_Delete(info_json); + + if (!json_string) { + log_error("Failed to serialize relay info JSON"); + unsigned char buf[LWS_PRE + 256]; + unsigned char *p = &buf[LWS_PRE]; + unsigned char *start = p; + unsigned char *end = &buf[sizeof(buf) - 1]; + + if (lws_add_http_header_status(wsi, HTTP_STATUS_INTERNAL_SERVER_ERROR, &p, end)) { + return -1; + } + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char*)"text/plain", 10, &p, end)) { + return -1; + } + if (lws_add_http_header_content_length(wsi, 0, &p, end)) { + return -1; + } + if (lws_finalize_http_header(wsi, &p, end)) { + return -1; + } + lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); + return -1; + } + + size_t json_len = strlen(json_string); + + // Prepare HTTP response with CORS headers + unsigned char buf[LWS_PRE + 1024]; + unsigned char *p = &buf[LWS_PRE]; + unsigned char *start = p; + unsigned char *end = &buf[sizeof(buf) - 1]; + + // Add status + if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) { + free(json_string); + 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); + return -1; + } + + // Add content length + if (lws_add_http_header_content_length(wsi, json_len, &p, end)) { + free(json_string); + 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); + 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); + 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); + return -1; + } + + // Finalize headers + if (lws_finalize_http_header(wsi, &p, end)) { + free(json_string); + return -1; + } + + // Write headers + if (lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS) < 0) { + free(json_string); + return -1; + } + + // Write JSON body + unsigned char *json_buf = malloc(LWS_PRE + json_len); + if (!json_buf) { + free(json_string); + return -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; + } + + free(json_buf); + + free(json_string); + log_success("NIP-11 relay information served successfully"); + return 0; +} + ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// // DATABASE FUNCTIONS @@ -1843,14 +2231,14 @@ int handle_event_message(cJSON* event, char* error_message, size_t error_size) { } } else if (event_type == EVENT_TYPE_EPHEMERAL) { // Ephemeral events should not be stored - snprintf(error_message, error_size, ""); // Success but no storage + error_message[0] = '\0'; // Success but no storage - empty error message return 0; // Accept but don't store } } // Step 5: Store event in database if (store_event(event) == 0) { - snprintf(error_message, error_size, ""); // Success + error_message[0] = '\0'; // Success - empty error message log_success("Event validated and stored successfully"); return 0; } @@ -1873,6 +2261,43 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso struct per_session_data *pss = (struct per_session_data *)user; switch (reason) { + case LWS_CALLBACK_HTTP: + // Handle NIP-11 relay information requests (HTTP GET to root path) + { + char *requested_uri = (char *)in; + log_info("HTTP request received"); + + // Check if this is a GET request to the root path + if (strcmp(requested_uri, "/") == 0) { + // Get Accept header + char accept_header[256] = {0}; + int header_len = lws_hdr_copy(wsi, accept_header, sizeof(accept_header) - 1, WSI_TOKEN_HTTP_ACCEPT); + + if (header_len > 0) { + accept_header[header_len] = '\0'; + + // Handle NIP-11 request + if (handle_nip11_http_request(wsi, accept_header) == 0) { + return 0; // Successfully handled + } + } else { + log_warning("HTTP request without Accept header"); + } + + // Return 404 for other requests + lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); + return -1; + } + + // Return 404 for non-root paths + lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); + return -1; + } + + case LWS_CALLBACK_HTTP_WRITEABLE: + // HTTP response continuation if needed + break; + case LWS_CALLBACK_ESTABLISHED: log_info("WebSocket connection established"); memset(pss, 0, sizeof(*pss)); @@ -2175,12 +2600,16 @@ int main(int argc, char* argv[]) { return 1; } + // Initialize NIP-11 relay information + init_relay_info(); + log_info("Starting relay server..."); // Start WebSocket Nostr relay server int result = start_websocket_relay(); // Cleanup + cleanup_relay_info(); nostr_cleanup(); close_database();