Updated synchronous relay queries to handle nip42.
This commit is contained in:
parent
9a63550863
commit
0d910ca181
|
@ -8,7 +8,7 @@ node_modules/
|
||||||
nostr-tools/
|
nostr-tools/
|
||||||
tiny-AES-c/
|
tiny-AES-c/
|
||||||
blossom/
|
blossom/
|
||||||
|
ndk/
|
||||||
|
|
||||||
Trash/debug_tests/
|
Trash/debug_tests/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
|
@ -0,0 +1,755 @@
|
||||||
|
# Relay Pool API Reference
|
||||||
|
|
||||||
|
This document describes the public API for the Nostr Relay Pool implementation in [`core_relay_pool.c`](nostr_core/core_relay_pool.c).
|
||||||
|
|
||||||
|
## Function Summary
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| [`nostr_relay_pool_create()`](nostr_core/core_relay_pool.c:219) | Create and initialize a new relay pool |
|
||||||
|
| [`nostr_relay_pool_destroy()`](nostr_core/core_relay_pool.c:304) | Destroy pool and cleanup all resources |
|
||||||
|
| [`nostr_relay_pool_add_relay()`](nostr_core/core_relay_pool.c:229) | Add a relay URL to the pool |
|
||||||
|
| [`nostr_relay_pool_remove_relay()`](nostr_core/core_relay_pool.c:273) | Remove a relay URL from the pool |
|
||||||
|
| [`nostr_relay_pool_subscribe()`](nostr_core/core_relay_pool.c:399) | Create async subscription with callbacks |
|
||||||
|
| [`nostr_pool_subscription_close()`](nostr_core/core_relay_pool.c:491) | Close subscription and free resources |
|
||||||
|
| [`nostr_relay_pool_run()`](nostr_core/core_relay_pool.c:1192) | Run event loop for specified timeout |
|
||||||
|
| [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) | Single iteration poll and dispatch |
|
||||||
|
| [`nostr_relay_pool_query_sync()`](nostr_core/core_relay_pool.c:695) | Synchronous query returning event array |
|
||||||
|
| [`nostr_relay_pool_get_event()`](nostr_core/core_relay_pool.c:825) | Get single most recent event |
|
||||||
|
| [`nostr_relay_pool_publish()`](nostr_core/core_relay_pool.c:866) | Publish event and wait for OK responses |
|
||||||
|
| [`nostr_relay_pool_get_relay_status()`](nostr_core/core_relay_pool.c:944) | Get connection status for a relay |
|
||||||
|
| [`nostr_relay_pool_list_relays()`](nostr_core/core_relay_pool.c:960) | List all relays and their statuses |
|
||||||
|
| [`nostr_relay_pool_get_relay_stats()`](nostr_core/core_relay_pool.c:992) | Get detailed statistics for a relay |
|
||||||
|
| [`nostr_relay_pool_reset_relay_stats()`](nostr_core/core_relay_pool.c:1008) | Reset statistics for a relay |
|
||||||
|
| [`nostr_relay_pool_get_relay_query_latency()`](nostr_core/core_relay_pool.c:1045) | Get average query latency for a relay |
|
||||||
|
|
||||||
|
## Pool Lifecycle
|
||||||
|
|
||||||
|
### Create Pool
|
||||||
|
**Function:** [`nostr_relay_pool_create()`](nostr_core/core_relay_pool.c:219)
|
||||||
|
```c
|
||||||
|
nostr_relay_pool_t* nostr_relay_pool_create(void);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
#include "nostr_core.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Create a new relay pool
|
||||||
|
nostr_relay_pool_t* pool = nostr_relay_pool_create();
|
||||||
|
if (!pool) {
|
||||||
|
fprintf(stderr, "Failed to create relay pool\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the pool...
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
nostr_relay_pool_destroy(pool);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Destroy Pool
|
||||||
|
**Function:** [`nostr_relay_pool_destroy()`](nostr_core/core_relay_pool.c:304)
|
||||||
|
```c
|
||||||
|
void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
// Properly cleanup a relay pool
|
||||||
|
void cleanup_pool(nostr_relay_pool_t* pool) {
|
||||||
|
if (pool) {
|
||||||
|
// This will close all active subscriptions and relay connections
|
||||||
|
nostr_relay_pool_destroy(pool);
|
||||||
|
pool = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relay Management
|
||||||
|
|
||||||
|
### Add Relay
|
||||||
|
**Function:** [`nostr_relay_pool_add_relay()`](nostr_core/core_relay_pool.c:229)
|
||||||
|
```c
|
||||||
|
int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
int setup_relays(nostr_relay_pool_t* pool) {
|
||||||
|
const char* relays[] = {
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://nos.lol",
|
||||||
|
"wss://relay.nostr.band"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
int result = nostr_relay_pool_add_relay(pool, relays[i]);
|
||||||
|
if (result != NOSTR_SUCCESS) {
|
||||||
|
fprintf(stderr, "Failed to add relay %s: %d\n", relays[i], result);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
printf("Added relay: %s\n", relays[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove Relay
|
||||||
|
**Function:** [`nostr_relay_pool_remove_relay()`](nostr_core/core_relay_pool.c:273)
|
||||||
|
```c
|
||||||
|
int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
int remove_slow_relay(nostr_relay_pool_t* pool) {
|
||||||
|
const char* slow_relay = "wss://slow-relay.example.com";
|
||||||
|
|
||||||
|
int result = nostr_relay_pool_remove_relay(pool, slow_relay);
|
||||||
|
if (result == NOSTR_SUCCESS) {
|
||||||
|
printf("Successfully removed relay: %s\n", slow_relay);
|
||||||
|
} else {
|
||||||
|
printf("Failed to remove relay %s (may not exist)\n", slow_relay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subscriptions (Asynchronous)
|
||||||
|
|
||||||
|
### Subscribe to Events
|
||||||
|
**Function:** [`nostr_relay_pool_subscribe()`](nostr_core/core_relay_pool.c:399)
|
||||||
|
```c
|
||||||
|
nostr_pool_subscription_t* nostr_relay_pool_subscribe(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char** relay_urls,
|
||||||
|
int relay_count,
|
||||||
|
cJSON* filter,
|
||||||
|
void (*on_event)(cJSON* event, const char* relay_url, void* user_data),
|
||||||
|
void (*on_eose)(void* user_data),
|
||||||
|
void* user_data);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
#include "cjson/cJSON.h"
|
||||||
|
|
||||||
|
// Event callback - called for each received event
|
||||||
|
void handle_event(cJSON* event, const char* relay_url, void* user_data) {
|
||||||
|
cJSON* content = cJSON_GetObjectItem(event, "content");
|
||||||
|
cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey");
|
||||||
|
|
||||||
|
if (content && pubkey) {
|
||||||
|
printf("Event from %s: %s (by %s)\n",
|
||||||
|
relay_url,
|
||||||
|
cJSON_GetStringValue(content),
|
||||||
|
cJSON_GetStringValue(pubkey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EOSE callback - called when all relays finish sending stored events
|
||||||
|
void handle_eose(void* user_data) {
|
||||||
|
printf("All relays finished sending stored events\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int subscribe_to_notes(nostr_relay_pool_t* pool) {
|
||||||
|
// Create filter for kind 1 (text notes) from last hour
|
||||||
|
cJSON* filter = cJSON_CreateObject();
|
||||||
|
cJSON* kinds = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1));
|
||||||
|
cJSON_AddItemToObject(filter, "kinds", kinds);
|
||||||
|
|
||||||
|
time_t since = time(NULL) - 3600; // Last hour
|
||||||
|
cJSON_AddItemToObject(filter, "since", cJSON_CreateNumber(since));
|
||||||
|
cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(50));
|
||||||
|
|
||||||
|
// Subscribe to specific relays
|
||||||
|
const char* relay_urls[] = {
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://nos.lol"
|
||||||
|
};
|
||||||
|
|
||||||
|
nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe(
|
||||||
|
pool,
|
||||||
|
relay_urls,
|
||||||
|
2,
|
||||||
|
filter,
|
||||||
|
handle_event,
|
||||||
|
handle_eose,
|
||||||
|
NULL // user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
cJSON_Delete(filter); // Pool makes its own copy
|
||||||
|
|
||||||
|
if (!sub) {
|
||||||
|
fprintf(stderr, "Failed to create subscription\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drive the event loop to receive events
|
||||||
|
printf("Listening for events for 30 seconds...\n");
|
||||||
|
nostr_relay_pool_run(pool, 30000); // 30 seconds
|
||||||
|
|
||||||
|
// Close subscription
|
||||||
|
nostr_pool_subscription_close(sub);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Close Subscription
|
||||||
|
**Function:** [`nostr_pool_subscription_close()`](nostr_core/core_relay_pool.c:491)
|
||||||
|
```c
|
||||||
|
int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
// Subscription management with cleanup
|
||||||
|
typedef struct {
|
||||||
|
nostr_pool_subscription_t* subscription;
|
||||||
|
int event_count;
|
||||||
|
int should_stop;
|
||||||
|
} subscription_context_t;
|
||||||
|
|
||||||
|
void event_counter(cJSON* event, const char* relay_url, void* user_data) {
|
||||||
|
subscription_context_t* ctx = (subscription_context_t*)user_data;
|
||||||
|
ctx->event_count++;
|
||||||
|
|
||||||
|
printf("Received event #%d from %s\n", ctx->event_count, relay_url);
|
||||||
|
|
||||||
|
// Stop after 10 events
|
||||||
|
if (ctx->event_count >= 10) {
|
||||||
|
ctx->should_stop = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int limited_subscription(nostr_relay_pool_t* pool) {
|
||||||
|
subscription_context_t ctx = {0};
|
||||||
|
|
||||||
|
// Create filter
|
||||||
|
cJSON* filter = cJSON_CreateObject();
|
||||||
|
cJSON* kinds = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1));
|
||||||
|
cJSON_AddItemToObject(filter, "kinds", kinds);
|
||||||
|
|
||||||
|
const char* relay_urls[] = {"wss://relay.damus.io"};
|
||||||
|
|
||||||
|
ctx.subscription = nostr_relay_pool_subscribe(
|
||||||
|
pool, relay_urls, 1, filter, event_counter, NULL, &ctx);
|
||||||
|
|
||||||
|
cJSON_Delete(filter);
|
||||||
|
|
||||||
|
if (!ctx.subscription) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll until we should stop
|
||||||
|
while (!ctx.should_stop) {
|
||||||
|
int events = nostr_relay_pool_poll(pool, 100);
|
||||||
|
if (events < 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
int result = nostr_pool_subscription_close(ctx.subscription);
|
||||||
|
printf("Subscription closed with result: %d\n", result);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Event Loop
|
||||||
|
|
||||||
|
### Run Timed Loop
|
||||||
|
**Function:** [`nostr_relay_pool_run()`](nostr_core/core_relay_pool.c:1192)
|
||||||
|
```c
|
||||||
|
int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
int run_event_loop(nostr_relay_pool_t* pool) {
|
||||||
|
printf("Starting event loop for 60 seconds...\n");
|
||||||
|
|
||||||
|
// Run for 60 seconds, processing all incoming events
|
||||||
|
int total_events = nostr_relay_pool_run(pool, 60000);
|
||||||
|
|
||||||
|
if (total_events < 0) {
|
||||||
|
fprintf(stderr, "Event loop error\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Processed %d events total\n", total_events);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Single Poll Iteration
|
||||||
|
**Function:** [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232)
|
||||||
|
```c
|
||||||
|
int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
// Integration with custom main loop
|
||||||
|
int custom_main_loop(nostr_relay_pool_t* pool) {
|
||||||
|
int running = 1;
|
||||||
|
int total_events = 0;
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
// Poll for Nostr events (non-blocking with 50ms timeout)
|
||||||
|
int events = nostr_relay_pool_poll(pool, 50);
|
||||||
|
if (events > 0) {
|
||||||
|
total_events += events;
|
||||||
|
printf("Processed %d events this iteration\n", events);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do other work in your application
|
||||||
|
// handle_ui_events();
|
||||||
|
// process_background_tasks();
|
||||||
|
|
||||||
|
// Check exit condition
|
||||||
|
// running = !should_exit();
|
||||||
|
|
||||||
|
// Simple exit after 100 events for demo
|
||||||
|
if (total_events >= 100) {
|
||||||
|
running = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Main loop finished, processed %d total events\n", total_events);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Synchronous Operations
|
||||||
|
|
||||||
|
### Query Multiple Events
|
||||||
|
**Function:** [`nostr_relay_pool_query_sync()`](nostr_core/core_relay_pool.c:695)
|
||||||
|
```c
|
||||||
|
cJSON** nostr_relay_pool_query_sync(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char** relay_urls,
|
||||||
|
int relay_count,
|
||||||
|
cJSON* filter,
|
||||||
|
int* event_count,
|
||||||
|
int timeout_ms);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
int query_recent_notes(nostr_relay_pool_t* pool) {
|
||||||
|
// Create filter for recent text notes
|
||||||
|
cJSON* filter = cJSON_CreateObject();
|
||||||
|
cJSON* kinds = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1));
|
||||||
|
cJSON_AddItemToObject(filter, "kinds", kinds);
|
||||||
|
cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(20));
|
||||||
|
|
||||||
|
const char* relay_urls[] = {
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://nos.lol"
|
||||||
|
};
|
||||||
|
|
||||||
|
int event_count = 0;
|
||||||
|
cJSON** events = nostr_relay_pool_query_sync(
|
||||||
|
pool, relay_urls, 2, filter, &event_count, 10000); // 10 second timeout
|
||||||
|
|
||||||
|
cJSON_Delete(filter);
|
||||||
|
|
||||||
|
if (!events) {
|
||||||
|
printf("No events received or query failed\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Received %d events:\n", event_count);
|
||||||
|
for (int i = 0; i < event_count; i++) {
|
||||||
|
cJSON* content = cJSON_GetObjectItem(events[i], "content");
|
||||||
|
if (content) {
|
||||||
|
printf(" %d: %s\n", i + 1, cJSON_GetStringValue(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free each event
|
||||||
|
cJSON_Delete(events[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the events array
|
||||||
|
free(events);
|
||||||
|
return event_count;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Single Most Recent Event
|
||||||
|
**Function:** [`nostr_relay_pool_get_event()`](nostr_core/core_relay_pool.c:825)
|
||||||
|
```c
|
||||||
|
cJSON* nostr_relay_pool_get_event(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char** relay_urls,
|
||||||
|
int relay_count,
|
||||||
|
cJSON* filter,
|
||||||
|
int timeout_ms);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
int get_latest_note_from_pubkey(nostr_relay_pool_t* pool, const char* pubkey_hex) {
|
||||||
|
// Create filter for specific author's notes
|
||||||
|
cJSON* filter = cJSON_CreateObject();
|
||||||
|
cJSON* kinds = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1));
|
||||||
|
cJSON_AddItemToObject(filter, "kinds", kinds);
|
||||||
|
|
||||||
|
cJSON* authors = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey_hex));
|
||||||
|
cJSON_AddItemToObject(filter, "authors", authors);
|
||||||
|
|
||||||
|
cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(1));
|
||||||
|
|
||||||
|
const char* relay_urls[] = {"wss://relay.damus.io"};
|
||||||
|
|
||||||
|
cJSON* event = nostr_relay_pool_get_event(
|
||||||
|
pool, relay_urls, 1, filter, 5000); // 5 second timeout
|
||||||
|
|
||||||
|
cJSON_Delete(filter);
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
printf("No recent event found for pubkey %s\n", pubkey_hex);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* content = cJSON_GetObjectItem(event, "content");
|
||||||
|
cJSON* created_at = cJSON_GetObjectItem(event, "created_at");
|
||||||
|
|
||||||
|
if (content && created_at) {
|
||||||
|
printf("Latest note: %s (created at %ld)\n",
|
||||||
|
cJSON_GetStringValue(content),
|
||||||
|
(long)cJSON_GetNumberValue(created_at));
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(event);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publish Event
|
||||||
|
**Function:** [`nostr_relay_pool_publish()`](nostr_core/core_relay_pool.c:866)
|
||||||
|
```c
|
||||||
|
int nostr_relay_pool_publish(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char** relay_urls,
|
||||||
|
int relay_count,
|
||||||
|
cJSON* event);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
int publish_text_note(nostr_relay_pool_t* pool, const char* content) {
|
||||||
|
// Create a basic text note event (this is simplified - real implementation
|
||||||
|
// would need proper signing with private key)
|
||||||
|
cJSON* event = cJSON_CreateObject();
|
||||||
|
cJSON_AddItemToObject(event, "kind", cJSON_CreateNumber(1));
|
||||||
|
cJSON_AddItemToObject(event, "content", cJSON_CreateString(content));
|
||||||
|
cJSON_AddItemToObject(event, "created_at", cJSON_CreateNumber(time(NULL)));
|
||||||
|
|
||||||
|
// In real usage, you'd add pubkey, id, sig fields here
|
||||||
|
cJSON_AddItemToObject(event, "pubkey", cJSON_CreateString("your_pubkey_hex"));
|
||||||
|
cJSON_AddItemToObject(event, "id", cJSON_CreateString("event_id_hash"));
|
||||||
|
cJSON_AddItemToObject(event, "sig", cJSON_CreateString("event_signature"));
|
||||||
|
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
|
||||||
|
|
||||||
|
const char* relay_urls[] = {
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://nos.lol",
|
||||||
|
"wss://relay.nostr.band"
|
||||||
|
};
|
||||||
|
|
||||||
|
printf("Publishing note: %s\n", content);
|
||||||
|
|
||||||
|
int success_count = nostr_relay_pool_publish(
|
||||||
|
pool, relay_urls, 3, event);
|
||||||
|
|
||||||
|
cJSON_Delete(event);
|
||||||
|
|
||||||
|
printf("Successfully published to %d out of 3 relays\n", success_count);
|
||||||
|
|
||||||
|
if (success_count == 0) {
|
||||||
|
fprintf(stderr, "Failed to publish to any relay\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return success_count;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Status and Statistics
|
||||||
|
|
||||||
|
### Get Relay Status
|
||||||
|
**Function:** [`nostr_relay_pool_get_relay_status()`](nostr_core/core_relay_pool.c:944)
|
||||||
|
```c
|
||||||
|
nostr_pool_relay_status_t nostr_relay_pool_get_relay_status(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char* relay_url);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
void check_relay_status(nostr_relay_pool_t* pool, const char* relay_url) {
|
||||||
|
nostr_pool_relay_status_t status = nostr_relay_pool_get_relay_status(pool, relay_url);
|
||||||
|
|
||||||
|
const char* status_str;
|
||||||
|
switch (status) {
|
||||||
|
case NOSTR_POOL_RELAY_DISCONNECTED:
|
||||||
|
status_str = "DISCONNECTED";
|
||||||
|
break;
|
||||||
|
case NOSTR_POOL_RELAY_CONNECTING:
|
||||||
|
status_str = "CONNECTING";
|
||||||
|
break;
|
||||||
|
case NOSTR_POOL_RELAY_CONNECTED:
|
||||||
|
status_str = "CONNECTED";
|
||||||
|
break;
|
||||||
|
case NOSTR_POOL_RELAY_ERROR:
|
||||||
|
status_str = "ERROR";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
status_str = "UNKNOWN";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Relay %s status: %s\n", relay_url, status_str);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### List All Relays
|
||||||
|
**Function:** [`nostr_relay_pool_list_relays()`](nostr_core/core_relay_pool.c:960)
|
||||||
|
```c
|
||||||
|
int nostr_relay_pool_list_relays(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
char*** relay_urls,
|
||||||
|
nostr_pool_relay_status_t** statuses);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
void print_all_relays(nostr_relay_pool_t* pool) {
|
||||||
|
char** relay_urls = NULL;
|
||||||
|
nostr_pool_relay_status_t* statuses = NULL;
|
||||||
|
|
||||||
|
int count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses);
|
||||||
|
|
||||||
|
if (count < 0) {
|
||||||
|
printf("Failed to list relays\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
printf("No relays configured\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Configured relays (%d):\n", count);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
const char* status_str = (statuses[i] == NOSTR_POOL_RELAY_CONNECTED) ?
|
||||||
|
"CONNECTED" : "DISCONNECTED";
|
||||||
|
printf(" %s - %s\n", relay_urls[i], status_str);
|
||||||
|
|
||||||
|
// Free the duplicated URL string
|
||||||
|
free(relay_urls[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the arrays
|
||||||
|
free(relay_urls);
|
||||||
|
free(statuses);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Relay Statistics
|
||||||
|
**Function:** [`nostr_relay_pool_get_relay_stats()`](nostr_core/core_relay_pool.c:992)
|
||||||
|
```c
|
||||||
|
const nostr_relay_stats_t* nostr_relay_pool_get_relay_stats(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char* relay_url);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
void print_relay_stats(nostr_relay_pool_t* pool, const char* relay_url) {
|
||||||
|
const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(pool, relay_url);
|
||||||
|
|
||||||
|
if (!stats) {
|
||||||
|
printf("No stats available for relay %s\n", relay_url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Statistics for %s:\n", relay_url);
|
||||||
|
printf(" Connection attempts: %d\n", stats->connection_attempts);
|
||||||
|
printf(" Connection failures: %d\n", stats->connection_failures);
|
||||||
|
printf(" Events received: %d\n", stats->events_received);
|
||||||
|
printf(" Events published: %d\n", stats->events_published);
|
||||||
|
printf(" Events published OK: %d\n", stats->events_published_ok);
|
||||||
|
printf(" Events published failed: %d\n", stats->events_published_failed);
|
||||||
|
printf(" Query latency avg: %.2f ms\n", stats->query_latency_avg);
|
||||||
|
printf(" Query samples: %d\n", stats->query_samples);
|
||||||
|
printf(" Publish latency avg: %.2f ms\n", stats->publish_latency_avg);
|
||||||
|
printf(" Publish samples: %d\n", stats->publish_samples);
|
||||||
|
|
||||||
|
if (stats->last_event_time > 0) {
|
||||||
|
printf(" Last event: %ld seconds ago\n",
|
||||||
|
time(NULL) - stats->last_event_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reset Relay Statistics
|
||||||
|
**Function:** [`nostr_relay_pool_reset_relay_stats()`](nostr_core/core_relay_pool.c:1008)
|
||||||
|
```c
|
||||||
|
int nostr_relay_pool_reset_relay_stats(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char* relay_url);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
void reset_stats_for_relay(nostr_relay_pool_t* pool, const char* relay_url) {
|
||||||
|
int result = nostr_relay_pool_reset_relay_stats(pool, relay_url);
|
||||||
|
|
||||||
|
if (result == NOSTR_SUCCESS) {
|
||||||
|
printf("Successfully reset statistics for %s\n", relay_url);
|
||||||
|
} else {
|
||||||
|
printf("Failed to reset statistics for %s\n", relay_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Query Latency
|
||||||
|
**Function:** [`nostr_relay_pool_get_relay_query_latency()`](nostr_core/core_relay_pool.c:1045)
|
||||||
|
```c
|
||||||
|
double nostr_relay_pool_get_relay_query_latency(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char* relay_url);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```c
|
||||||
|
void check_relay_performance(nostr_relay_pool_t* pool) {
|
||||||
|
const char* relays[] = {
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://nos.lol",
|
||||||
|
"wss://relay.nostr.band"
|
||||||
|
};
|
||||||
|
|
||||||
|
printf("Relay performance comparison:\n");
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
double latency = nostr_relay_pool_get_relay_query_latency(pool, relays[i]);
|
||||||
|
|
||||||
|
if (latency >= 0) {
|
||||||
|
printf(" %s: %.2f ms average query latency\n", relays[i], latency);
|
||||||
|
} else {
|
||||||
|
printf(" %s: No latency data available\n", relays[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example Application
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "nostr_core.h"
|
||||||
|
#include "cjson/cJSON.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// Global context for the example
|
||||||
|
typedef struct {
|
||||||
|
int event_count;
|
||||||
|
int max_events;
|
||||||
|
} app_context_t;
|
||||||
|
|
||||||
|
void on_text_note(cJSON* event, const char* relay_url, void* user_data) {
|
||||||
|
app_context_t* ctx = (app_context_t*)user_data;
|
||||||
|
|
||||||
|
cJSON* content = cJSON_GetObjectItem(event, "content");
|
||||||
|
if (content && cJSON_IsString(content)) {
|
||||||
|
printf("[%s] Note #%d: %s\n",
|
||||||
|
relay_url, ++ctx->event_count, cJSON_GetStringValue(content));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_subscription_complete(void* user_data) {
|
||||||
|
printf("All relays finished sending stored events\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Initialize pool
|
||||||
|
nostr_relay_pool_t* pool = nostr_relay_pool_create();
|
||||||
|
if (!pool) {
|
||||||
|
fprintf(stderr, "Failed to create relay pool\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add relays
|
||||||
|
const char* relays[] = {
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://nos.lol"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
if (nostr_relay_pool_add_relay(pool, relays[i]) != NOSTR_SUCCESS) {
|
||||||
|
fprintf(stderr, "Failed to add relay: %s\n", relays[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create filter for recent text notes
|
||||||
|
cJSON* filter = cJSON_CreateObject();
|
||||||
|
cJSON* kinds = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1));
|
||||||
|
cJSON_AddItemToObject(filter, "kinds", kinds);
|
||||||
|
cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(10));
|
||||||
|
|
||||||
|
// Set up context
|
||||||
|
app_context_t ctx = {0, 10};
|
||||||
|
|
||||||
|
// Subscribe
|
||||||
|
nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe(
|
||||||
|
pool, relays, 2, filter, on_text_note, on_subscription_complete, &ctx);
|
||||||
|
|
||||||
|
cJSON_Delete(filter);
|
||||||
|
|
||||||
|
if (!sub) {
|
||||||
|
fprintf(stderr, "Failed to create subscription\n");
|
||||||
|
nostr_relay_pool_destroy(pool);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run event loop for 30 seconds
|
||||||
|
printf("Listening for events...\n");
|
||||||
|
nostr_relay_pool_run(pool, 30000);
|
||||||
|
|
||||||
|
// Print final stats
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
print_relay_stats(pool, relays[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
nostr_pool_subscription_close(sub);
|
||||||
|
nostr_relay_pool_destroy(pool);
|
||||||
|
|
||||||
|
printf("Application finished. Received %d events total.\n", ctx.event_count);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- All functions are **not thread-safe**. Use from a single thread or add external synchronization.
|
||||||
|
- **Memory ownership**: The pool duplicates filters and URLs internally. Caller owns returned events and must free them.
|
||||||
|
- **Event deduplication** is applied pool-wide using a circular buffer of 1000 event IDs.
|
||||||
|
- **Ping functionality** is currently disabled in this build.
|
||||||
|
- **Reconnection** happens on-demand when sending, but active subscriptions are not automatically re-sent after reconnect.
|
||||||
|
- **Polling model**: You must drive the event loop via [`nostr_relay_pool_run()`](nostr_core/core_relay_pool.c:1192) or [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) to receive events.
|
16
debug.log
16
debug.log
|
@ -51,3 +51,19 @@ Final event: {
|
||||||
"id": "0034dad20205876be164b768ba5efad8aaa23bdf16b273544785cae9f13a76f7",
|
"id": "0034dad20205876be164b768ba5efad8aaa23bdf16b273544785cae9f13a76f7",
|
||||||
"sig": "e1ef97bf916f9e1fd94e231e62833e6ff000be29a92e16fc3656142e51abbf4ee8795ab4100f448e6712c1a76bb128016c24306b165077188fa8e5b82c72f23f"
|
"sig": "e1ef97bf916f9e1fd94e231e62833e6ff000be29a92e16fc3656142e51abbf4ee8795ab4100f448e6712c1a76bb128016c24306b165077188fa8e5b82c72f23f"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
=== NOSTR WebSocket Debug Log Started ===
|
||||||
|
[11:57:56.083] SEND relay.laantungir.net:443: ["REQ", "sync_1_1759247875", {
|
||||||
|
"kinds": [1],
|
||||||
|
"limit": 1
|
||||||
|
}]
|
||||||
|
[11:57:56.742] SEND nostr.mom:443: ["REQ", "sync_2_1759247875", {
|
||||||
|
"kinds": [1],
|
||||||
|
"limit": 1
|
||||||
|
}]
|
||||||
|
[11:57:56.742] RECV relay.laantungir.net:443: ["EVENT","sync_1_1759247875",{"content":"🟠 New Bitcoin Block Mined!\n\nBlock Height: 917,088\nBlock Hash: 000000000000000000011050a3967e7d3936867a5afcae9babbdc5925c5b66d5\nTimestamp: 2025-09-30T15:46:37.000Z\nTransactions: 3,161\nBlock Size: 1.52 MB\nBlock Weight: 3,998,317 WU\nDifficulty: 1.42e+14\n\n#Bitcoin #Blockchain #Block917088","created_at":1759247235,"id":"20e0e3acf17ecde6e9ed49bd5b1c739eff1095d6e6c3c2775556b5c978359721","kind":1,"pubkey":"e568a76a4f8836be296d405eb41034260d55e2361e4b2ef88350a4003bbd5f9b","sig":"10817ffa6dafc128c8d2c16e9127d101e3a8730ae205be933d747294afd3766150e54073a6817fae3bdc5cff57436c6ab58ccb8c327e7013220b27ab2680d722","tags":[["t","bitcoin"],["t","blockchain"],["t","block"],["alt","New Bitcoin block mined - Block 917088"],["published_at","1759247197"],["client","info_bot","ws://127.0.0.1:7777"],["r","https://blockstream.info/block/000000000000000000011050a3967e7d3936867a5afcae9babbdc5925c5b66d5"],["new_block","true"],["block_height","917088"],["block_hash","000000000000000000011050a3967e7d3936867a5afcae9babbdc5925c5b66d5"],["block_time","1759247197"],["tx_count","3161"],["block_size","1590325"],["block_weight","3998317"],["difficulty","142342602928674.9"],["previous_block","00000000000000000000c50b4fcf7b8fac91e60893037a99c3dbab84f3aee313"],["mempool_tx_count","3087"],["mempool_size","1195411"],["memory_usage_pct","2.4"]]}]
|
||||||
|
[11:57:56.802] RECV relay.laantungir.net:443: ["EOSE","sync_1_1759247875"]
|
||||||
|
[11:57:56.802] SEND relay.laantungir.net:443: ["CLOSE", "sync_1_1759247875"]
|
||||||
|
[11:57:56.895] RECV nostr.mom:443: ["EVENT","sync_2_1759247875",{"content":"\"No matter how economically or politically complex our human societies appear, they nevertheless remain accountable to the regularities and vagaries of the natural world.\"\n\n-Roy Sebag\n\n#TheQuoteShelf \n\n\n\nhttps://blossom.primal.net/6968f243f29a1f125f049ea798274b13efe44144724bf507cd39246fdd5e4ff6.webp","created_at":1759247875,"id":"0f241242cc645089e4c50a3cec0cf82ec11d7867cde7e257ad7651f836088bf6","kind":1,"pubkey":"356875ffd729b06eeb4c1d7a70a1f750045d067774d21c0faffe4af2bf96a2e8","sig":"d1665367d1ae571b81a624e2ca101f3267af66923389d6ef4f8f4adb452e7632a87f104f9f328363459aaf7537cae583952f169ce5d80a73b8371fe0d1d9ea60","tags":[["t","TheQuoteShelf"],["imeta","url https://blossom.primal.net/6968f243f29a1f125f049ea798274b13efe44144724bf507cd39246fdd5e4ff6.webp","m image/webp","ox 6968f243f29a1f125f049ea798274b13efe44144724bf507cd39246fdd5e4ff6","dim 979x551"]]}]
|
||||||
|
[11:57:56.905] RECV nostr.mom:443: ["EOSE","sync_2_1759247875"]
|
||||||
|
[11:57:56.905] SEND nostr.mom:443: ["CLOSE", "sync_2_1759247875"]
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
#include "nostr_common.h"
|
#include "nostr_core.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -26,6 +26,9 @@
|
||||||
// cJSON for JSON handling
|
// cJSON for JSON handling
|
||||||
#include "../cjson/cJSON.h"
|
#include "../cjson/cJSON.h"
|
||||||
|
|
||||||
|
// NIP-42 Authentication
|
||||||
|
#include "nip042.h"
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// TYPE DEFINITIONS FOR SYNCHRONOUS RELAY QUERIES
|
// TYPE DEFINITIONS FOR SYNCHRONOUS RELAY QUERIES
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
@ -51,6 +54,12 @@ typedef struct {
|
||||||
cJSON** events; // Array of events from this relay
|
cJSON** events; // Array of events from this relay
|
||||||
int events_capacity; // Allocated capacity
|
int events_capacity; // Allocated capacity
|
||||||
char subscription_id[32]; // Unique subscription ID
|
char subscription_id[32]; // Unique subscription ID
|
||||||
|
|
||||||
|
// NIP-42 Authentication fields
|
||||||
|
nostr_auth_state_t auth_state; // Current authentication state
|
||||||
|
char auth_challenge[NOSTR_NIP42_MAX_CHALLENGE_LENGTH]; // Stored challenge
|
||||||
|
time_t auth_challenge_time; // When challenge was received
|
||||||
|
int nip42_enabled; // Whether NIP-42 is enabled for this relay
|
||||||
} relay_connection_t;
|
} relay_connection_t;
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,7 +74,9 @@ cJSON** synchronous_query_relays_with_progress(
|
||||||
int* result_count,
|
int* result_count,
|
||||||
int relay_timeout_seconds,
|
int relay_timeout_seconds,
|
||||||
relay_progress_callback_t callback,
|
relay_progress_callback_t callback,
|
||||||
void* user_data) {
|
void* user_data,
|
||||||
|
int nip42_enabled,
|
||||||
|
const unsigned char* private_key) {
|
||||||
|
|
||||||
if (!relay_urls || relay_count <= 0 || !filter || !result_count) {
|
if (!relay_urls || relay_count <= 0 || !filter || !result_count) {
|
||||||
if (result_count) *result_count = 0;
|
if (result_count) *result_count = 0;
|
||||||
|
@ -95,11 +106,17 @@ cJSON** synchronous_query_relays_with_progress(
|
||||||
relays[i].last_activity = start_time;
|
relays[i].last_activity = start_time;
|
||||||
relays[i].events_capacity = 10;
|
relays[i].events_capacity = 10;
|
||||||
relays[i].events = malloc(relays[i].events_capacity * sizeof(cJSON*));
|
relays[i].events = malloc(relays[i].events_capacity * sizeof(cJSON*));
|
||||||
|
|
||||||
|
// Initialize NIP-42 authentication fields
|
||||||
|
relays[i].auth_state = NOSTR_AUTH_STATE_NONE;
|
||||||
|
memset(relays[i].auth_challenge, 0, sizeof(relays[i].auth_challenge));
|
||||||
|
relays[i].auth_challenge_time = 0;
|
||||||
|
relays[i].nip42_enabled = nip42_enabled;
|
||||||
|
|
||||||
// Generate unique subscription ID
|
// Generate unique subscription ID
|
||||||
snprintf(relays[i].subscription_id, sizeof(relays[i].subscription_id),
|
snprintf(relays[i].subscription_id, sizeof(relays[i].subscription_id),
|
||||||
"sync_%d_%ld", i, start_time);
|
"sync_%d_%ld", i, start_time);
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(relays[i].url, "connecting", NULL, 0, relay_count, 0, user_data);
|
callback(relays[i].url, "connecting", NULL, 0, relay_count, 0, user_data);
|
||||||
}
|
}
|
||||||
|
@ -191,19 +208,50 @@ cJSON** synchronous_query_relays_with_progress(
|
||||||
cJSON* parsed = NULL;
|
cJSON* parsed = NULL;
|
||||||
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
|
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
|
||||||
|
|
||||||
if (msg_type && strcmp(msg_type, "EVENT") == 0) {
|
if (msg_type && strcmp(msg_type, "AUTH") == 0) {
|
||||||
|
// Handle AUTH challenge message: ["AUTH", <challenge-string>]
|
||||||
|
if (relay->nip42_enabled && private_key && cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 2) {
|
||||||
|
cJSON* challenge_json = cJSON_GetArrayItem(parsed, 1);
|
||||||
|
if (cJSON_IsString(challenge_json)) {
|
||||||
|
const char* challenge = cJSON_GetStringValue(challenge_json);
|
||||||
|
|
||||||
|
// Store challenge and attempt authentication
|
||||||
|
strncpy(relay->auth_challenge, challenge, sizeof(relay->auth_challenge) - 1);
|
||||||
|
relay->auth_challenge[sizeof(relay->auth_challenge) - 1] = '\0';
|
||||||
|
relay->auth_challenge_time = time(NULL);
|
||||||
|
relay->auth_state = NOSTR_AUTH_STATE_CHALLENGE_RECEIVED;
|
||||||
|
|
||||||
|
// Create and send authentication event
|
||||||
|
cJSON* auth_event = nostr_nip42_create_auth_event(challenge, relay->url, private_key, 0);
|
||||||
|
if (auth_event) {
|
||||||
|
char* auth_message = nostr_nip42_create_auth_message(auth_event);
|
||||||
|
if (auth_message) {
|
||||||
|
if (nostr_ws_send_text(relay->client, auth_message) >= 0) {
|
||||||
|
relay->auth_state = NOSTR_AUTH_STATE_AUTHENTICATING;
|
||||||
|
if (callback) {
|
||||||
|
callback(relay->url, "authenticating", NULL, 0, relay_count, completed_relays, user_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(auth_message);
|
||||||
|
}
|
||||||
|
cJSON_Delete(auth_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (msg_type && strcmp(msg_type, "EVENT") == 0) {
|
||||||
// Handle EVENT message
|
// Handle EVENT message
|
||||||
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
|
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
|
||||||
cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1);
|
cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1);
|
||||||
cJSON* event = cJSON_GetArrayItem(parsed, 2);
|
cJSON* event = cJSON_GetArrayItem(parsed, 2);
|
||||||
|
|
||||||
if (cJSON_IsString(sub_id_json) && event &&
|
if (cJSON_IsString(sub_id_json) && event &&
|
||||||
strcmp(cJSON_GetStringValue(sub_id_json), relay->subscription_id) == 0) {
|
strcmp(cJSON_GetStringValue(sub_id_json), relay->subscription_id) == 0) {
|
||||||
|
|
||||||
cJSON* event_id_json = cJSON_GetObjectItem(event, "id");
|
cJSON* event_id_json = cJSON_GetObjectItem(event, "id");
|
||||||
if (event_id_json && cJSON_IsString(event_id_json)) {
|
if (event_id_json && cJSON_IsString(event_id_json)) {
|
||||||
const char* event_id = cJSON_GetStringValue(event_id_json);
|
const char* event_id = cJSON_GetStringValue(event_id_json);
|
||||||
|
|
||||||
// Check for duplicate
|
// Check for duplicate
|
||||||
int is_duplicate = 0;
|
int is_duplicate = 0;
|
||||||
for (int j = 0; j < seen_count; j++) {
|
for (int j = 0; j < seen_count; j++) {
|
||||||
|
@ -212,31 +260,31 @@ cJSON** synchronous_query_relays_with_progress(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_duplicate && seen_count < 1000) {
|
if (!is_duplicate && seen_count < 1000) {
|
||||||
// New event - add to seen list
|
// New event - add to seen list
|
||||||
strncpy(seen_event_ids[seen_count], event_id, 64);
|
strncpy(seen_event_ids[seen_count], event_id, 64);
|
||||||
seen_event_ids[seen_count][64] = '\0';
|
seen_event_ids[seen_count][64] = '\0';
|
||||||
seen_count++;
|
seen_count++;
|
||||||
total_unique_events++;
|
total_unique_events++;
|
||||||
|
|
||||||
// Store event in relay's array
|
// Store event in relay's array
|
||||||
if (relay->events_received >= relay->events_capacity) {
|
if (relay->events_received >= relay->events_capacity) {
|
||||||
relay->events_capacity *= 2;
|
relay->events_capacity *= 2;
|
||||||
relay->events = realloc(relay->events,
|
relay->events = realloc(relay->events,
|
||||||
relay->events_capacity * sizeof(cJSON*));
|
relay->events_capacity * sizeof(cJSON*));
|
||||||
}
|
}
|
||||||
|
|
||||||
relay->events[relay->events_received] = cJSON_Duplicate(event, 1);
|
relay->events[relay->events_received] = cJSON_Duplicate(event, 1);
|
||||||
relay->events_received++;
|
relay->events_received++;
|
||||||
relay->state = RELAY_STATE_ACTIVE;
|
relay->state = RELAY_STATE_ACTIVE;
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(relay->url, "event_found", event_id,
|
callback(relay->url, "event_found", event_id,
|
||||||
relay->events_received, relay_count,
|
relay->events_received, relay_count,
|
||||||
completed_relays, user_data);
|
completed_relays, user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For FIRST_RESULT mode, return immediately
|
// For FIRST_RESULT mode, return immediately
|
||||||
if (mode == RELAY_QUERY_FIRST_RESULT) {
|
if (mode == RELAY_QUERY_FIRST_RESULT) {
|
||||||
result_array = malloc(sizeof(cJSON*));
|
result_array = malloc(sizeof(cJSON*));
|
||||||
|
@ -244,7 +292,7 @@ cJSON** synchronous_query_relays_with_progress(
|
||||||
result_array[0] = cJSON_Duplicate(event, 1);
|
result_array[0] = cJSON_Duplicate(event, 1);
|
||||||
*result_count = 1;
|
*result_count = 1;
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(NULL, "first_result", event_id,
|
callback(NULL, "first_result", event_id,
|
||||||
1, relay_count, completed_relays, user_data);
|
1, relay_count, completed_relays, user_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,7 +302,7 @@ cJSON** synchronous_query_relays_with_progress(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (msg_type && strcmp(msg_type, "EOSE") == 0) {
|
} else if (msg_type && strcmp(msg_type, "EOSE") == 0) {
|
||||||
// Handle End of Stored Events
|
// Handle End of Stored Events
|
||||||
cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1);
|
cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1);
|
||||||
|
@ -400,7 +448,9 @@ publish_result_t* synchronous_publish_event_with_progress(
|
||||||
int* success_count,
|
int* success_count,
|
||||||
int relay_timeout_seconds,
|
int relay_timeout_seconds,
|
||||||
publish_progress_callback_t callback,
|
publish_progress_callback_t callback,
|
||||||
void* user_data) {
|
void* user_data,
|
||||||
|
int nip42_enabled,
|
||||||
|
const unsigned char* private_key) {
|
||||||
|
|
||||||
if (!relay_urls || relay_count <= 0 || !event || !success_count) {
|
if (!relay_urls || relay_count <= 0 || !event || !success_count) {
|
||||||
if (success_count) *success_count = 0;
|
if (success_count) *success_count = 0;
|
||||||
|
@ -443,7 +493,13 @@ publish_result_t* synchronous_publish_event_with_progress(
|
||||||
relays[i].state = RELAY_STATE_CONNECTING;
|
relays[i].state = RELAY_STATE_CONNECTING;
|
||||||
relays[i].last_activity = start_time;
|
relays[i].last_activity = start_time;
|
||||||
results[i] = PUBLISH_ERROR; // Default to error
|
results[i] = PUBLISH_ERROR; // Default to error
|
||||||
|
|
||||||
|
// Initialize NIP-42 authentication fields
|
||||||
|
relays[i].auth_state = NOSTR_AUTH_STATE_NONE;
|
||||||
|
memset(relays[i].auth_challenge, 0, sizeof(relays[i].auth_challenge));
|
||||||
|
relays[i].auth_challenge_time = 0;
|
||||||
|
relays[i].nip42_enabled = nip42_enabled;
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(relays[i].url, "connecting", NULL, 0, relay_count, 0, user_data);
|
callback(relays[i].url, "connecting", NULL, 0, relay_count, 0, user_data);
|
||||||
}
|
}
|
||||||
|
@ -535,34 +591,65 @@ publish_result_t* synchronous_publish_event_with_progress(
|
||||||
char* msg_type = NULL;
|
char* msg_type = NULL;
|
||||||
cJSON* parsed = NULL;
|
cJSON* parsed = NULL;
|
||||||
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
|
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
|
||||||
|
|
||||||
if (msg_type && strcmp(msg_type, "OK") == 0) {
|
if (msg_type && strcmp(msg_type, "AUTH") == 0) {
|
||||||
|
// Handle AUTH challenge message: ["AUTH", <challenge-string>]
|
||||||
|
if (relay->nip42_enabled && private_key && cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 2) {
|
||||||
|
cJSON* challenge_json = cJSON_GetArrayItem(parsed, 1);
|
||||||
|
if (cJSON_IsString(challenge_json)) {
|
||||||
|
const char* challenge = cJSON_GetStringValue(challenge_json);
|
||||||
|
|
||||||
|
// Store challenge and attempt authentication
|
||||||
|
strncpy(relay->auth_challenge, challenge, sizeof(relay->auth_challenge) - 1);
|
||||||
|
relay->auth_challenge[sizeof(relay->auth_challenge) - 1] = '\0';
|
||||||
|
relay->auth_challenge_time = time(NULL);
|
||||||
|
relay->auth_state = NOSTR_AUTH_STATE_CHALLENGE_RECEIVED;
|
||||||
|
|
||||||
|
// Create and send authentication event
|
||||||
|
cJSON* auth_event = nostr_nip42_create_auth_event(challenge, relay->url, private_key, 0);
|
||||||
|
if (auth_event) {
|
||||||
|
char* auth_message = nostr_nip42_create_auth_message(auth_event);
|
||||||
|
if (auth_message) {
|
||||||
|
if (nostr_ws_send_text(relay->client, auth_message) >= 0) {
|
||||||
|
relay->auth_state = NOSTR_AUTH_STATE_AUTHENTICATING;
|
||||||
|
if (callback) {
|
||||||
|
callback(relay->url, "authenticating", NULL, 0, relay_count, completed_relays, user_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(auth_message);
|
||||||
|
}
|
||||||
|
cJSON_Delete(auth_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (msg_type && strcmp(msg_type, "OK") == 0) {
|
||||||
// Handle OK message: ["OK", <event_id>, <true|false>, <message>]
|
// Handle OK message: ["OK", <event_id>, <true|false>, <message>]
|
||||||
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
|
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
|
||||||
cJSON* ok_event_id = cJSON_GetArrayItem(parsed, 1);
|
cJSON* ok_event_id = cJSON_GetArrayItem(parsed, 1);
|
||||||
cJSON* accepted = cJSON_GetArrayItem(parsed, 2);
|
cJSON* accepted = cJSON_GetArrayItem(parsed, 2);
|
||||||
cJSON* message = cJSON_GetArrayItem(parsed, 3);
|
cJSON* message = cJSON_GetArrayItem(parsed, 3);
|
||||||
|
|
||||||
// Verify this OK is for our event
|
// Verify this OK is for our event
|
||||||
if (ok_event_id && cJSON_IsString(ok_event_id) && event_id &&
|
if (ok_event_id && cJSON_IsString(ok_event_id) && event_id &&
|
||||||
strcmp(cJSON_GetStringValue(ok_event_id), event_id) == 0) {
|
strcmp(cJSON_GetStringValue(ok_event_id), event_id) == 0) {
|
||||||
|
|
||||||
relay->state = RELAY_STATE_EOSE_RECEIVED; // Reuse for "completed"
|
relay->state = RELAY_STATE_EOSE_RECEIVED; // Reuse for "completed"
|
||||||
active_relays--;
|
active_relays--;
|
||||||
completed_relays++;
|
completed_relays++;
|
||||||
|
|
||||||
const char* ok_message = "";
|
const char* ok_message = "";
|
||||||
if (message && cJSON_IsString(message)) {
|
if (message && cJSON_IsString(message)) {
|
||||||
ok_message = cJSON_GetStringValue(message);
|
ok_message = cJSON_GetStringValue(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accepted && cJSON_IsBool(accepted) && cJSON_IsTrue(accepted)) {
|
if (accepted && cJSON_IsBool(accepted) && cJSON_IsTrue(accepted)) {
|
||||||
// Event accepted
|
// Event accepted
|
||||||
results[i] = PUBLISH_SUCCESS;
|
results[i] = PUBLISH_SUCCESS;
|
||||||
(*success_count)++;
|
(*success_count)++;
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(relay->url, "accepted", ok_message,
|
callback(relay->url, "accepted", ok_message,
|
||||||
*success_count, relay_count, completed_relays, user_data);
|
*success_count, relay_count, completed_relays, user_data);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -570,11 +657,11 @@ publish_result_t* synchronous_publish_event_with_progress(
|
||||||
results[i] = PUBLISH_REJECTED;
|
results[i] = PUBLISH_REJECTED;
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(relay->url, "rejected", ok_message,
|
callback(relay->url, "rejected", ok_message,
|
||||||
*success_count, relay_count, completed_relays, user_data);
|
*success_count, relay_count, completed_relays, user_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close connection
|
// Close connection
|
||||||
nostr_ws_close(relay->client);
|
nostr_ws_close(relay->client);
|
||||||
relay->client = NULL;
|
relay->client = NULL;
|
||||||
|
|
|
@ -81,66 +81,11 @@
|
||||||
// Forward declaration for cJSON (to avoid requiring cJSON.h in header)
|
// Forward declaration for cJSON (to avoid requiring cJSON.h in header)
|
||||||
struct cJSON;
|
struct cJSON;
|
||||||
|
|
||||||
// Relay query modes
|
|
||||||
typedef enum {
|
|
||||||
RELAY_QUERY_FIRST_RESULT, // Return as soon as first event is received
|
|
||||||
RELAY_QUERY_MOST_RECENT, // Return the most recent event from all relays
|
|
||||||
RELAY_QUERY_ALL_RESULTS // Return all unique events from all relays
|
|
||||||
} relay_query_mode_t;
|
|
||||||
|
|
||||||
// Publish result types
|
|
||||||
typedef enum {
|
|
||||||
PUBLISH_SUCCESS, // Event was accepted by relay
|
|
||||||
PUBLISH_REJECTED, // Event was rejected by relay
|
|
||||||
PUBLISH_TIMEOUT, // No response within timeout
|
|
||||||
PUBLISH_ERROR // Connection or other error
|
|
||||||
} publish_result_t;
|
|
||||||
|
|
||||||
// Progress callback function types
|
|
||||||
typedef void (*relay_progress_callback_t)(
|
|
||||||
const char* relay_url,
|
|
||||||
const char* status,
|
|
||||||
const char* event_id,
|
|
||||||
int events_received,
|
|
||||||
int total_relays,
|
|
||||||
int completed_relays,
|
|
||||||
void* user_data);
|
|
||||||
|
|
||||||
typedef void (*publish_progress_callback_t)(
|
|
||||||
const char* relay_url,
|
|
||||||
const char* status,
|
|
||||||
const char* message,
|
|
||||||
int success_count,
|
|
||||||
int total_relays,
|
|
||||||
int completed_relays,
|
|
||||||
void* user_data);
|
|
||||||
|
|
||||||
// Function declarations
|
// Function declarations
|
||||||
const char* nostr_strerror(int error_code);
|
const char* nostr_strerror(int error_code);
|
||||||
|
|
||||||
// Library initialization functions
|
// Library initialization functions
|
||||||
int nostr_init(void);
|
int nostr_init(void);
|
||||||
void nostr_cleanup(void);
|
void nostr_cleanup(void);
|
||||||
|
|
||||||
// Relay query functions
|
|
||||||
struct cJSON** synchronous_query_relays_with_progress(
|
|
||||||
const char** relay_urls,
|
|
||||||
int relay_count,
|
|
||||||
struct cJSON* filter,
|
|
||||||
relay_query_mode_t mode,
|
|
||||||
int* result_count,
|
|
||||||
int relay_timeout_seconds,
|
|
||||||
relay_progress_callback_t callback,
|
|
||||||
void* user_data);
|
|
||||||
|
|
||||||
// Relay publish functions
|
|
||||||
publish_result_t* synchronous_publish_event_with_progress(
|
|
||||||
const char** relay_urls,
|
|
||||||
int relay_count,
|
|
||||||
struct cJSON* event,
|
|
||||||
int* success_count,
|
|
||||||
int relay_timeout_seconds,
|
|
||||||
publish_progress_callback_t callback,
|
|
||||||
void* user_data);
|
|
||||||
|
|
||||||
#endif // NOSTR_COMMON_H
|
#endif // NOSTR_COMMON_H
|
||||||
|
|
|
@ -82,6 +82,15 @@
|
||||||
* - nostr_auth_rule_add() -> Add authentication rule
|
* - nostr_auth_rule_add() -> Add authentication rule
|
||||||
* - nostr_auth_rule_remove() -> Remove authentication rule
|
* - nostr_auth_rule_remove() -> Remove authentication rule
|
||||||
*
|
*
|
||||||
|
* RELAY OPERATIONS:
|
||||||
|
* - synchronous_query_relays_with_progress() -> One-off query from multiple relays
|
||||||
|
* - synchronous_publish_event_with_progress() -> One-off publish to multiple relays
|
||||||
|
* *
|
||||||
|
* RELAY POOL OPERATIONS:
|
||||||
|
* - nostr_relay_pool_create() -> Create relay pool for persistent connections
|
||||||
|
* - nostr_relay_pool_subscribe() -> Subscribe to events with callbacks
|
||||||
|
* - nostr_relay_pool_run() -> Run event loop for receiving events
|
||||||
|
|
||||||
* SYSTEM FUNCTIONS:
|
* SYSTEM FUNCTIONS:
|
||||||
* - nostr_crypto_init() -> Initialize crypto subsystem
|
* - nostr_crypto_init() -> Initialize crypto subsystem
|
||||||
* - nostr_crypto_cleanup() -> Cleanup crypto subsystem
|
* - nostr_crypto_cleanup() -> Cleanup crypto subsystem
|
||||||
|
@ -142,6 +151,155 @@ extern "C" {
|
||||||
// Authentication and request validation system
|
// Authentication and request validation system
|
||||||
#include "request_validator.h" // Request validation and authentication rules
|
#include "request_validator.h" // Request validation and authentication rules
|
||||||
|
|
||||||
|
// Relay pool types and functions
|
||||||
|
typedef enum {
|
||||||
|
NOSTR_POOL_RELAY_DISCONNECTED = 0,
|
||||||
|
NOSTR_POOL_RELAY_CONNECTING = 1,
|
||||||
|
NOSTR_POOL_RELAY_CONNECTED = 2,
|
||||||
|
NOSTR_POOL_RELAY_ERROR = -1
|
||||||
|
} nostr_pool_relay_status_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int connection_attempts;
|
||||||
|
int connection_failures;
|
||||||
|
int events_received;
|
||||||
|
int events_published;
|
||||||
|
int events_published_ok;
|
||||||
|
int events_published_failed;
|
||||||
|
time_t last_event_time;
|
||||||
|
time_t connection_uptime_start;
|
||||||
|
double ping_latency_avg;
|
||||||
|
double ping_latency_min;
|
||||||
|
double ping_latency_max;
|
||||||
|
double ping_latency_current;
|
||||||
|
int ping_samples;
|
||||||
|
double query_latency_avg;
|
||||||
|
double query_latency_min;
|
||||||
|
double query_latency_max;
|
||||||
|
int query_samples;
|
||||||
|
double publish_latency_avg;
|
||||||
|
int publish_samples;
|
||||||
|
} nostr_relay_stats_t;
|
||||||
|
|
||||||
|
typedef struct nostr_relay_pool nostr_relay_pool_t;
|
||||||
|
typedef struct nostr_pool_subscription nostr_pool_subscription_t;
|
||||||
|
|
||||||
|
// Relay pool management functions
|
||||||
|
nostr_relay_pool_t* nostr_relay_pool_create(void);
|
||||||
|
int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url);
|
||||||
|
int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url);
|
||||||
|
void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);
|
||||||
|
|
||||||
|
// Subscription management
|
||||||
|
nostr_pool_subscription_t* nostr_relay_pool_subscribe(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char** relay_urls,
|
||||||
|
int relay_count,
|
||||||
|
cJSON* filter,
|
||||||
|
void (*on_event)(cJSON* event, const char* relay_url, void* user_data),
|
||||||
|
void (*on_eose)(void* user_data),
|
||||||
|
void* user_data);
|
||||||
|
int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);
|
||||||
|
|
||||||
|
// Event loop functions
|
||||||
|
int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms);
|
||||||
|
int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms);
|
||||||
|
|
||||||
|
// Synchronous query/publish functions
|
||||||
|
cJSON** nostr_relay_pool_query_sync(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char** relay_urls,
|
||||||
|
int relay_count,
|
||||||
|
cJSON* filter,
|
||||||
|
int* event_count,
|
||||||
|
int timeout_ms);
|
||||||
|
cJSON* nostr_relay_pool_get_event(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char** relay_urls,
|
||||||
|
int relay_count,
|
||||||
|
cJSON* filter,
|
||||||
|
int timeout_ms);
|
||||||
|
int nostr_relay_pool_publish(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char** relay_urls,
|
||||||
|
int relay_count,
|
||||||
|
cJSON* event);
|
||||||
|
|
||||||
|
// Status and statistics functions
|
||||||
|
nostr_pool_relay_status_t nostr_relay_pool_get_relay_status(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char* relay_url);
|
||||||
|
int nostr_relay_pool_list_relays(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
char*** relay_urls,
|
||||||
|
nostr_pool_relay_status_t** statuses);
|
||||||
|
const nostr_relay_stats_t* nostr_relay_pool_get_relay_stats(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char* relay_url);
|
||||||
|
int nostr_relay_pool_reset_relay_stats(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char* relay_url);
|
||||||
|
double nostr_relay_pool_get_relay_query_latency(
|
||||||
|
nostr_relay_pool_t* pool,
|
||||||
|
const char* relay_url);
|
||||||
|
|
||||||
|
// Synchronous relay operations (one-off queries/publishes)
|
||||||
|
typedef enum {
|
||||||
|
RELAY_QUERY_FIRST_RESULT, // Return as soon as first event is received
|
||||||
|
RELAY_QUERY_MOST_RECENT, // Return the most recent event from all relays
|
||||||
|
RELAY_QUERY_ALL_RESULTS // Return all unique events from all relays
|
||||||
|
} relay_query_mode_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PUBLISH_SUCCESS, // Event was accepted by relay
|
||||||
|
PUBLISH_REJECTED, // Event was rejected by relay
|
||||||
|
PUBLISH_TIMEOUT, // No response within timeout
|
||||||
|
PUBLISH_ERROR // Connection or other error
|
||||||
|
} publish_result_t;
|
||||||
|
|
||||||
|
typedef void (*relay_progress_callback_t)(
|
||||||
|
const char* relay_url,
|
||||||
|
const char* status,
|
||||||
|
const char* event_id,
|
||||||
|
int events_received,
|
||||||
|
int total_relays,
|
||||||
|
int completed_relays,
|
||||||
|
void* user_data);
|
||||||
|
|
||||||
|
typedef void (*publish_progress_callback_t)(
|
||||||
|
const char* relay_url,
|
||||||
|
const char* status,
|
||||||
|
const char* message,
|
||||||
|
int success_count,
|
||||||
|
int total_relays,
|
||||||
|
int completed_relays,
|
||||||
|
void* user_data);
|
||||||
|
|
||||||
|
// Synchronous relay query functions
|
||||||
|
struct cJSON** synchronous_query_relays_with_progress(
|
||||||
|
const char** relay_urls,
|
||||||
|
int relay_count,
|
||||||
|
struct cJSON* filter,
|
||||||
|
relay_query_mode_t mode,
|
||||||
|
int* result_count,
|
||||||
|
int relay_timeout_seconds,
|
||||||
|
relay_progress_callback_t callback,
|
||||||
|
void* user_data,
|
||||||
|
int nip42_enabled,
|
||||||
|
const unsigned char* private_key);
|
||||||
|
|
||||||
|
// Synchronous relay publish functions
|
||||||
|
publish_result_t* synchronous_publish_event_with_progress(
|
||||||
|
const char** relay_urls,
|
||||||
|
int relay_count,
|
||||||
|
struct cJSON* event,
|
||||||
|
int* success_count,
|
||||||
|
int relay_timeout_seconds,
|
||||||
|
publish_progress_callback_t callback,
|
||||||
|
void* user_data,
|
||||||
|
int nip42_enabled,
|
||||||
|
const unsigned char* private_key);
|
||||||
|
|
||||||
// Relay communication functions are defined in nostr_common.h
|
// Relay communication functions are defined in nostr_common.h
|
||||||
// WebSocket functions are defined in nostr_common.h
|
// WebSocket functions are defined in nostr_common.h
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
@ -241,8 +242,10 @@ void nostr_request_validator_cleanup(void) {
|
||||||
// CONVENIENCE FUNCTIONS
|
// CONVENIENCE FUNCTIONS
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
int nostr_auth_check_upload(const char* pubkey, const char* auth_header,
|
int nostr_auth_check_upload(const char* pubkey, const char* auth_header,
|
||||||
const char* hash, const char* mime_type, long file_size) {
|
const char* hash, const char* mime_type, long file_size) {
|
||||||
|
(void)pubkey; // Parameter not used in this convenience function
|
||||||
|
|
||||||
nostr_request_t request = {
|
nostr_request_t request = {
|
||||||
.operation = "upload",
|
.operation = "upload",
|
||||||
.auth_header = auth_header,
|
.auth_header = auth_header,
|
||||||
|
@ -265,6 +268,8 @@ int nostr_auth_check_upload(const char* pubkey, const char* auth_header,
|
||||||
}
|
}
|
||||||
|
|
||||||
int nostr_auth_check_delete(const char* pubkey, const char* auth_header, const char* hash) {
|
int nostr_auth_check_delete(const char* pubkey, const char* auth_header, const char* hash) {
|
||||||
|
(void)pubkey; // Parameter not used in this convenience function
|
||||||
|
|
||||||
nostr_request_t request = {
|
nostr_request_t request = {
|
||||||
.operation = "delete",
|
.operation = "delete",
|
||||||
.auth_header = auth_header,
|
.auth_header = auth_header,
|
||||||
|
@ -287,6 +292,8 @@ int nostr_auth_check_delete(const char* pubkey, const char* auth_header, const c
|
||||||
}
|
}
|
||||||
|
|
||||||
int nostr_auth_check_publish(const char* pubkey, struct cJSON* event) {
|
int nostr_auth_check_publish(const char* pubkey, struct cJSON* event) {
|
||||||
|
(void)pubkey; // Parameter not used in this convenience function
|
||||||
|
|
||||||
if (!event) {
|
if (!event) {
|
||||||
return NOSTR_ERROR_INVALID_INPUT;
|
return NOSTR_ERROR_INVALID_INPUT;
|
||||||
}
|
}
|
||||||
|
@ -459,6 +466,8 @@ static int validate_nostr_event(struct cJSON* event, const char* expected_hash,
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
static int sqlite_auth_init(const char* db_path, const char* app_name) {
|
static int sqlite_auth_init(const char* db_path, const char* app_name) {
|
||||||
|
(void)app_name; // Parameter not used in this implementation
|
||||||
|
|
||||||
if (g_auth_db) {
|
if (g_auth_db) {
|
||||||
return NOSTR_SUCCESS; // Already initialized
|
return NOSTR_SUCCESS; // Already initialized
|
||||||
}
|
}
|
||||||
|
@ -765,13 +774,15 @@ static int sqlite_auth_rule_update(const nostr_auth_rule_t* rule) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sqlite_auth_rule_list(const char* operation, nostr_auth_rule_t** rules, int* count) {
|
static int sqlite_auth_rule_list(const char* operation, nostr_auth_rule_t** rules, int* count) {
|
||||||
|
(void)operation; // Parameter not used in this implementation
|
||||||
|
|
||||||
if (!g_auth_db || !rules || !count) {
|
if (!g_auth_db || !rules || !count) {
|
||||||
return NOSTR_ERROR_INVALID_INPUT;
|
return NOSTR_ERROR_INVALID_INPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
*rules = NULL;
|
*rules = NULL;
|
||||||
*count = 0;
|
*count = 0;
|
||||||
|
|
||||||
// For now, return empty list - would implement full rule listing
|
// For now, return empty list - would implement full rule listing
|
||||||
return NOSTR_SUCCESS;
|
return NOSTR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -1130,19 +1141,21 @@ static int evaluate_auth_rules(const char* pubkey, const char* operation, const
|
||||||
static void generate_auth_cache_key(const char* pubkey, const char* operation, const char* hash,
|
static void generate_auth_cache_key(const char* pubkey, const char* operation, const char* hash,
|
||||||
const char* mime_type, long file_size, char* cache_key, size_t key_size) {
|
const char* mime_type, long file_size, char* cache_key, size_t key_size) {
|
||||||
char temp_buffer[1024];
|
char temp_buffer[1024];
|
||||||
snprintf(temp_buffer, sizeof(temp_buffer), "%s|%s|%s|%s|%ld",
|
int written = snprintf(temp_buffer, sizeof(temp_buffer), "%s|%s|%s|%s|%ld",
|
||||||
pubkey ? pubkey : "", operation ? operation : "",
|
pubkey ? pubkey : "", operation ? operation : "",
|
||||||
hash ? hash : "", mime_type ? mime_type : "", file_size);
|
hash ? hash : "", mime_type ? mime_type : "", file_size);
|
||||||
|
|
||||||
// Generate SHA-256 hash of the key components for consistent cache keys
|
// Generate SHA-256 hash of the key components for consistent cache keys
|
||||||
unsigned char hash_bytes[32];
|
unsigned char hash_bytes[32];
|
||||||
if (nostr_sha256((unsigned char*)temp_buffer, strlen(temp_buffer), hash_bytes) == NOSTR_SUCCESS) {
|
size_t hash_len = (written >= 0 && (size_t)written < sizeof(temp_buffer)) ? (size_t)written : sizeof(temp_buffer) - 1;
|
||||||
|
if (nostr_sha256((unsigned char*)temp_buffer, hash_len, hash_bytes) == NOSTR_SUCCESS) {
|
||||||
nostr_bytes_to_hex(hash_bytes, 32, cache_key);
|
nostr_bytes_to_hex(hash_bytes, 32, cache_key);
|
||||||
cache_key[64] = '\0'; // Ensure null termination
|
cache_key[64] = '\0'; // Ensure null termination
|
||||||
} else {
|
} else {
|
||||||
// Fallback if hashing fails
|
// Fallback if hashing fails - safely copy up to key_size - 1 characters
|
||||||
strncpy(cache_key, temp_buffer, key_size - 1);
|
size_t copy_len = (written >= 0 && (size_t)written < key_size - 1) ? (size_t)written : key_size - 1;
|
||||||
cache_key[key_size - 1] = '\0';
|
memcpy(cache_key, temp_buffer, copy_len);
|
||||||
|
cache_key[copy_len] = '\0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1179,6 +1192,8 @@ int nostr_auth_rule_list(const char* operation, nostr_auth_rule_t** rules, int*
|
||||||
}
|
}
|
||||||
|
|
||||||
void nostr_auth_rules_free(nostr_auth_rule_t* rules, int count) {
|
void nostr_auth_rules_free(nostr_auth_rule_t* rules, int count) {
|
||||||
|
(void)count; // Parameter not used in this implementation
|
||||||
|
|
||||||
if (rules) {
|
if (rules) {
|
||||||
free(rules);
|
free(rules);
|
||||||
}
|
}
|
||||||
|
@ -1200,6 +1215,8 @@ int nostr_auth_cache_stats(int* hit_count, int* miss_count, int* entries) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int nostr_auth_register_db_backend(const nostr_auth_db_interface_t* backend) {
|
int nostr_auth_register_db_backend(const nostr_auth_db_interface_t* backend) {
|
||||||
|
(void)backend; // Parameter not used in this implementation
|
||||||
|
|
||||||
// For now, only SQLite backend is supported
|
// For now, only SQLite backend is supported
|
||||||
return NOSTR_ERROR_AUTH_RULES_BACKEND_NOT_FOUND;
|
return NOSTR_ERROR_AUTH_RULES_BACKEND_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"git.ignoreLimitWarning": true
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue