755 lines
22 KiB
Markdown
755 lines
22 KiB
Markdown
# 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. |