185 lines
6.7 KiB
C
185 lines
6.7 KiB
C
/*
|
|
* relay_fetch.c — post-login bootstrap relay fetch for sovereign_browser
|
|
*
|
|
* Uses synchronous_query_relays_with_progress() from nostr_core_lib to
|
|
* query the user's kind 0/3/10002 events from the bootstrap relays, then
|
|
* stores them in the SQLite database via db_store_event().
|
|
*
|
|
* This runs in a background thread (relay_fetch_thread) so the browser
|
|
* is usable immediately after login. SQLite is opened with
|
|
* SQLITE_OPEN_FULLMUTEX so concurrent access from the main thread is safe.
|
|
*/
|
|
|
|
#include "relay_fetch.h"
|
|
#include "db.h"
|
|
#include "settings.h"
|
|
#include "bookmarks.h"
|
|
#include "settings_sync.h"
|
|
#include "tab_manager.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <glib.h>
|
|
|
|
#include "nostr_core/nostr_core.h"
|
|
#include "../nostr_core_lib/cjson/cJSON.h"
|
|
|
|
/* Maximum number of relays to parse from the bootstrap_relays setting. */
|
|
#define MAX_RELAYS 32
|
|
|
|
/* Relay query timeout in seconds. */
|
|
#define RELAY_TIMEOUT_SECONDS 15
|
|
|
|
/* Forward declaration — defined after relay_fetch_bootstrap. */
|
|
static gboolean avatar_refresh_idle(gpointer data);
|
|
|
|
/* ── Public API ────────────────────────────────────────────────────── */
|
|
|
|
int relay_fetch_bootstrap(const char *pubkey_hex,
|
|
const char **relay_urls,
|
|
int relay_count) {
|
|
if (pubkey_hex == NULL || pubkey_hex[0] == '\0') {
|
|
g_printerr("[relay] No pubkey, skipping bootstrap fetch\n");
|
|
return -1;
|
|
}
|
|
if (relay_urls == NULL || relay_count <= 0) {
|
|
g_printerr("[relay] No bootstrap relays configured\n");
|
|
return -1;
|
|
}
|
|
|
|
g_print("[relay] Fetching kind 0/3/10002/30003/30078 for %s from %d relay(s)...\n",
|
|
pubkey_hex, relay_count);
|
|
|
|
/* Build the filter: {"authors": [pubkey], "kinds": [0, 3, 10002, 30003, 30078]} */
|
|
cJSON *filter = cJSON_CreateObject();
|
|
cJSON *authors = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey_hex));
|
|
cJSON_AddItemToObject(filter, "authors", authors);
|
|
|
|
cJSON *kinds = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(0));
|
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(3));
|
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10002));
|
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(30003)); /* bookmark sets */
|
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(30078)); /* NIP-78 app data */
|
|
cJSON_AddItemToObject(filter, "kinds", kinds);
|
|
|
|
/* Query the relays. RELAY_QUERY_ALL_RESULTS collects all unique events
|
|
* from all relays. No NIP-42 auth needed for public events. */
|
|
int result_count = 0;
|
|
cJSON **results = synchronous_query_relays_with_progress(
|
|
relay_urls, relay_count, filter,
|
|
RELAY_QUERY_ALL_RESULTS, &result_count,
|
|
RELAY_TIMEOUT_SECONDS,
|
|
NULL, NULL, /* no progress callback */
|
|
0, NULL /* no NIP-42 auth */
|
|
);
|
|
|
|
cJSON_Delete(filter);
|
|
|
|
if (results == NULL || result_count <= 0) {
|
|
g_print("[relay] No events found (relays may be unreachable or "
|
|
"the user has no kind 0/3/10002 events)\n");
|
|
if (results) free(results);
|
|
return 0;
|
|
}
|
|
|
|
/* Store each event in the database. Kind 30003 (bookmark sets) are
|
|
* also decrypted and loaded into the in-memory bookmarks list.
|
|
* Kind 30078 (NIP-78 app data) is stored and merged into local
|
|
* settings if it's our sovereign_browser settings event. */
|
|
int stored = 0;
|
|
for (int i = 0; i < result_count; i++) {
|
|
if (results[i] == NULL) continue;
|
|
|
|
long kind = 0;
|
|
cJSON *k = cJSON_GetObjectItemCaseSensitive(results[i], "kind");
|
|
if (cJSON_IsNumber(k)) kind = (long)k->valuedouble;
|
|
|
|
if (kind == 30003) {
|
|
/* bookmarks_store_and_load_event stores in SQLite + decrypts. */
|
|
if (bookmarks_store_and_load_event(results[i]) == 0) {
|
|
stored++;
|
|
}
|
|
} else if (kind == 30078) {
|
|
/* Store in SQLite first, then try to merge into local settings.
|
|
* settings_sync_merge_from_nostr checks the d-tag and only
|
|
* merges if it's our "sovereign_browser" event. */
|
|
if (db_store_event(results[i]) == 0) {
|
|
stored++;
|
|
}
|
|
settings_sync_merge_from_nostr(results[i]);
|
|
} else {
|
|
if (db_store_event(results[i]) == 0) {
|
|
stored++;
|
|
} else {
|
|
g_printerr("[relay] Failed to store event %d/%d\n", i + 1,
|
|
result_count);
|
|
}
|
|
}
|
|
cJSON_Delete(results[i]);
|
|
}
|
|
free(results);
|
|
|
|
g_print("[relay] Fetched %d event(s), stored %d in database\n",
|
|
result_count, stored);
|
|
|
|
/* Refresh the user's avatar now that the kind 0 profile may be
|
|
* available in the database. This must run on the main thread
|
|
* because it touches GTK widgets. */
|
|
g_idle_add(avatar_refresh_idle, g_strdup(pubkey_hex));
|
|
|
|
return stored;
|
|
}
|
|
|
|
/* Idle callback wrapper to refresh the avatar on the main thread. */
|
|
static gboolean avatar_refresh_idle(gpointer data) {
|
|
char *pubkey = (char *)data;
|
|
tab_manager_set_avatar(pubkey);
|
|
g_free(pubkey);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* ── Background thread ─────────────────────────────────────────────── */
|
|
|
|
gpointer relay_fetch_thread(gpointer data) {
|
|
char *pubkey_hex = (char *)data;
|
|
if (pubkey_hex == NULL || pubkey_hex[0] == '\0') {
|
|
g_printerr("[relay] Thread started with no pubkey\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Parse bootstrap relays from settings. */
|
|
const browser_settings_t *s = settings_get();
|
|
char *relay_buf = g_strdup(s->bootstrap_relays);
|
|
const char *relay_urls[MAX_RELAYS];
|
|
int relay_count = 0;
|
|
|
|
/* Tokenize the buffer in place. */
|
|
char *saveptr = NULL;
|
|
char *line = strtok_r(relay_buf, "\n\r", &saveptr);
|
|
while (line != NULL && relay_count < MAX_RELAYS) {
|
|
while (*line == ' ' || *line == '\t') line++;
|
|
if (line[0] != '\0' &&
|
|
(strncmp(line, "wss://", 6) == 0 ||
|
|
strncmp(line, "ws://", 5) == 0)) {
|
|
relay_urls[relay_count++] = line;
|
|
}
|
|
line = strtok_r(NULL, "\n\r", &saveptr);
|
|
}
|
|
|
|
if (relay_count == 0) {
|
|
g_print("[relay] No valid bootstrap relay URLs in settings\n");
|
|
g_free(relay_buf);
|
|
g_free(pubkey_hex);
|
|
return NULL;
|
|
}
|
|
|
|
g_print("[relay] Background fetch started (%d relays)\n", relay_count);
|
|
relay_fetch_bootstrap(pubkey_hex, relay_urls, relay_count);
|
|
|
|
g_free(relay_buf);
|
|
g_free(pubkey_hex);
|
|
return NULL;
|
|
}
|