3 Commits

6 changed files with 171 additions and 220 deletions

View File

@@ -11,8 +11,8 @@
*/
#define NT_VERSION_MAJOR 0
#define NT_VERSION_MINOR 0
#define NT_VERSION_PATCH 21
#define NT_VERSION "v0.0.21"
#define NT_VERSION_PATCH 23
#define NT_VERSION "v0.0.23"
#include <ncurses.h>
#include <stdio.h>

View File

@@ -45,21 +45,6 @@ static int menu_prompt(const char *breadcrumb,
return 0;
}
static void menu_noticef(const char *fmt, ...) {
char msg[1024];
va_list ap;
if (!fmt) {
return;
}
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
tuin_notice(msg);
}
static int menu_load_test_mnemonic(char *out, int out_size) {
FILE *fp;
@@ -199,15 +184,11 @@ void menu_login(void) {
static const TuiMenuItem LOGIN_ITEMS[] = {
{NT_HK("P", "rivate key"), 'p'},
{NT_HK("M", "nemonic"), 'm'},
{"N_" NT_HK("s", "igner"), 's'},
{"Signer - " NT_HK("U", "RL"), 'u'},
{"Signer - " NT_HK("Q", "ube"), 'q'},
{"Signer - " NT_HK("L", "aunch new"), 'l'},
{NT_HK("N", "ew account"), 'n'},
{NT_HK("Q", "uit"), 'q'},
};
static const TuiMenuItem SIGNER_TRANSPORT_ITEMS[] = {
{"URL signer (FIPS/web address)", 'u'},
{"Signer qrexec transport", 's'},
{"Launch n_signer (stub)", 'l'},
{"Back", 'b'},
{"E_" NT_HK("x", "it"), 'x'},
};
TuiMenuState login_state = {0};
@@ -220,12 +201,12 @@ void menu_login(void) {
unsigned char pub[32];
int login_idx;
TuiFrame frame = nt_frame("> Login");
TuiMenu menu = {LOGIN_ITEMS, 5};
TuiMenu menu = {LOGIN_ITEMS, 7};
TuiStatus status = nt_status();
login_idx = tuin_menu_run(&frame, &menu, &status, &login_state);
if (login_idx < 0 || login_idx == 4) {
if (login_idx < 0 || login_idx == 6) {
nt_log("Hasta luego.");
signer_shutdown();
exit(0);
@@ -243,166 +224,15 @@ void menu_login(void) {
continue;
}
} else if (login_idx == 2) {
snprintf(input, sizeof(input), "%s", "s");
snprintf(input, sizeof(input), "%s", "u");
} else if (login_idx == 3) {
snprintf(input, sizeof(input), "%s", "S");
} else if (login_idx == 4) {
snprintf(input, sizeof(input), "%s", "l");
} else if (login_idx == 5) {
snprintf(input, sizeof(input), "%s", "n");
}
if (strcmp(input, "s") == 0 || strcmp(input, "S") == 0) {
char **names = NULL;
int name_count = 0;
if (nsigner_list(&names, &name_count) == 0 && name_count > 0) {
char pubkey_hex[65] = {0};
int selected = 0;
nt_nsigner_selector_t selector = nsigner_selector_default();
char idxbuf[32] = {0};
int nostr_index = 0;
int choose_transport = 0;
int idx;
int local_choice;
TuiMenuState local_state = {0};
TuiMenuItem *local_items = NULL;
char (*labels)[256] = NULL;
local_items = (TuiMenuItem *)calloc((size_t)(name_count + 2), sizeof(TuiMenuItem));
labels = (char(*)[256])calloc((size_t)(name_count + 2), sizeof(*labels));
if (!local_items || !labels) {
for (idx = 0; idx < name_count; idx++) {
free(names[idx]);
}
free(names);
free(local_items);
free(labels);
menu_noticef("Out of memory while building local n_signer menu.");
continue;
}
for (idx = 0; idx < name_count; idx++) {
snprintf(labels[idx], sizeof(labels[idx]), "@%s", names[idx]);
local_items[idx].label = labels[idx];
local_items[idx].shortcut = 0;
}
snprintf(labels[name_count], sizeof(labels[name_count]), "Transport options");
local_items[name_count].label = labels[name_count];
local_items[name_count].shortcut = 't';
snprintf(labels[name_count + 1], sizeof(labels[name_count + 1]), "Back");
local_items[name_count + 1].label = labels[name_count + 1];
local_items[name_count + 1].shortcut = 'b';
{
TuiFrame local_frame = nt_frame("> Login > Signer Local");
TuiMenu local_menu = {local_items, name_count + 2};
TuiStatus local_status = nt_status();
local_choice = tuin_menu_run(&local_frame, &local_menu, &local_status, &local_state);
}
if (local_choice < 0 || local_choice == name_count + 1) {
for (idx = 0; idx < name_count; idx++) {
free(names[idx]);
}
free(names);
free(local_items);
free(labels);
continue;
}
if (local_choice == name_count) {
choose_transport = 1;
} else {
selected = local_choice;
(void)menu_prompt("> Login > Signer Local", "Seed phrase index (0)", "", idxbuf, sizeof(idxbuf));
}
if (idxbuf[0] != '\0') {
nostr_index = atoi(idxbuf);
if (nostr_index < 0) {
nostr_index = 0;
}
}
if (!choose_transport) {
selector.has_nostr_index = 1;
selector.nostr_index = nostr_index;
selector.role[0] = '\0';
menu_render_body("> Login > Signer Local");
nt_log("Waiting for n_signer approval...");
if (nsigner_get_public_key(names[selected], &selector, pubkey_hex) != 0) {
for (idx = 0; idx < name_count; idx++) {
free(names[idx]);
}
free(names);
free(local_items);
free(labels);
tuin_notice("Failed to get public key from n_signer (denied or error).");
continue;
}
signer_init_nsigner(names[selected], &selector);
snprintf(g_state.npub_hex, sizeof(g_state.npub_hex), "%s", pubkey_hex);
{
unsigned char pub_bytes[32];
nostr_hex_to_bytes(pubkey_hex, pub_bytes, 32);
nostr_key_to_bech32(pub_bytes, "npub", g_state.npub_bech32);
}
g_state.nsec_hex[0] = '\0';
g_state.nsec_bech32[0] = '\0';
g_state.seed_phrase[0] = '\0';
g_state.logged_in = 1;
nt_log("Signed in via n_signer (@%s) npub: %s", names[selected], g_state.npub_bech32);
for (idx = 0; idx < name_count; idx++) {
free(names[idx]);
}
free(names);
free(local_items);
free(labels);
if (state_load_user_info_async() != 0) {
nt_log("Failed to start background user-info load.");
} else {
nt_log("Loading profile/relay data in background...");
}
return;
}
for (idx = 0; idx < name_count; idx++) {
free(names[idx]);
}
free(names);
free(local_items);
free(labels);
}
{
int transport_idx;
TuiMenuState transport_state = {0};
TuiFrame transport_frame = nt_frame("> Login > N_signer > Transport");
TuiMenu transport_menu = {SIGNER_TRANSPORT_ITEMS, 4};
TuiStatus transport_status = nt_status();
transport_idx = tuin_menu_run(&transport_frame, &transport_menu, &transport_status, &transport_state);
if (transport_idx == 0) {
snprintf(input, sizeof(input), "%s", "u");
} else if (transport_idx == 1) {
snprintf(input, sizeof(input), "%s", "S");
} else if (transport_idx == 2) {
tuin_notice("Launch n_signer is not implemented yet.");
continue;
} else {
continue;
}
}
}
if (strcmp(input, "u") == 0 || strcmp(input, "U") == 0) {
char pubkey_hex[65] = {0};
char endpoint_url[256];
@@ -416,7 +246,7 @@ void menu_login(void) {
snprintf(endpoint_url, sizeof(endpoint_url), "%s", (saved_endpoint && saved_endpoint[0] != '\0') ? saved_endpoint : "");
(void)menu_prompt("> Login > URL Signer",
"Signer URL (http://<npub>.fips:8080)",
(saved_endpoint && saved_endpoint[0] != '\0') ? saved_endpoint : "",
"",
endpoint_url,
sizeof(endpoint_url));
@@ -484,10 +314,8 @@ void menu_login(void) {
nt_log("Signed in via n_signer URL (%s) npub: %s", endpoint_url, g_state.npub_bech32);
if (state_load_user_info_async() != 0) {
nt_log("Failed to start background user-info load.");
} else {
nt_log("Loading profile/relay data in background...");
if (state_load_user_info() != 0) {
nt_log("Failed to load profile/relay data after login.");
}
return;
@@ -563,15 +391,18 @@ void menu_login(void) {
nt_log("Signed in via qrexec n_signer (%s:%s) npub: %s", target_qube, service_name, g_state.npub_bech32);
if (state_load_user_info_async() != 0) {
nt_log("Failed to start background user-info load.");
} else {
nt_log("Loading profile/relay data in background...");
if (state_load_user_info() != 0) {
nt_log("Failed to load profile/relay data after login.");
}
return;
}
if (strcmp(input, "l") == 0 || strcmp(input, "L") == 0) {
tuin_notice("Launch n_signer is not implemented yet.");
continue;
}
if (strcmp(input, "n") == 0) {
char mnemonic[256];
char idxbuf[32];

View File

@@ -7,6 +7,7 @@
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -17,21 +18,19 @@ static int profile_publish_kind0(const char *content_json) {
void menu_profile(void) {
static const char *fields[] = {"name", "about", "picture", "banner", "website", "lud16", "nip05"};
static const TuiMenuItem PROFILE_ITEMS[] = {
{NT_HK("M", "odify account"), 'm'},
{NT_HK("P", "ost changes and exit."), 'p'},
{"E" NT_HK("x", "it without saving"), 'x'},
};
TuiMenuState menu_state = {0};
char edit_buf[512];
if (g_state.logged_in && g_state.npub_hex[0] != '\0' &&
(!g_state.kind0_json || strcmp(g_state.kind0_json, "{}") == 0)) {
(void)state_load_user_info();
}
while (1) {
TuiFrame frame = nt_frame("> Main Menu > Profile");
TuiMenu menu = {PROFILE_ITEMS, 3};
TuiStatus status = nt_status();
cJSON *meta;
int i;
int idx;
int idx = -1;
tuin_render_header(&frame);
nt_print_reset();
@@ -79,7 +78,29 @@ void menu_profile(void) {
nt_print("%-18s %s", fields[i], (cJSON_IsString(v) && v->valuestring) ? v->valuestring : "");
}
idx = tuin_menu_run(&frame, &menu, &status, &menu_state);
nt_print("");
nt_print("[m] Modify account [p] Post changes and exit [x] Exit without saving");
{
int key = tuin_get_key();
if (key == TUI_KEY_RESIZE) {
cJSON_Delete(meta);
continue;
}
if (tuin_is_escape_key(key)) {
idx = 2;
} else if (key >= 0 && key <= 255) {
char ch = (char)tolower((unsigned char)key);
if (ch == 'm') {
idx = 0;
} else if (ch == 'p') {
idx = 1;
} else if (ch == 'x') {
idx = 2;
}
}
}
if (idx < 0 || idx == 2) {
cJSON_Delete(meta);
break;

View File

@@ -727,6 +727,9 @@ int nt_query_sync_verbose(const char *filter_json,
char **events = NULL;
int events_n = 0;
int events_cap = 0;
char **seen_ids = NULL;
int seen_n = 0;
int seen_cap = 0;
int i;
if (!events_out || !event_count || !filter_json || !relay_urls || relay_count <= 0) {
@@ -743,6 +746,73 @@ int nt_query_sync_verbose(const char *filter_json,
return -1;
}
if (verbose && phase_label) {
nt_log("%s: querying %d relays with per-relay diagnostics",
phase_label,
relay_count);
for (i = 0; i < relay_count; i++) {
nt_log("%s: relay %d/%d target: %s",
phase_label,
i + 1,
relay_count,
relay_urls[i] ? relay_urls[i] : "(null)");
}
for (i = 0; i < relay_count; i++) {
const char *reason = "unknown";
int before_count = events_n;
int relay_ok = nt_query_one_relay(relay_urls[i],
filter,
timeout_ms,
verbose,
phase_label,
&events,
&events_n,
&events_cap,
&seen_ids,
&seen_n,
&seen_cap,
&reason);
int added = events_n - before_count;
if (relay_ok < 0) {
nt_log("%s: relay %d/%d %s -> error (%s)",
phase_label,
i + 1,
relay_count,
relay_urls[i] ? relay_urls[i] : "(null)",
reason ? reason : "unknown");
nt_free_string_array(events, events_n);
nt_free_string_array(seen_ids, seen_n);
cJSON_Delete(filter);
return -1;
}
nt_log("%s: relay %d/%d %s -> %s (reason=%s, new_events=%d, total=%d)",
phase_label,
i + 1,
relay_count,
relay_urls[i] ? relay_urls[i] : "(null)",
relay_ok ? "connected" : "no_data_or_error",
reason ? reason : "unknown",
added,
events_n);
}
nt_free_string_array(seen_ids, seen_n);
cJSON_Delete(filter);
if (events_n <= 0) {
return -1;
}
nt_log("%s: collected %d unique events total", phase_label, events_n);
*events_out = events;
*event_count = events_n;
return 0;
}
pool = nostr_relay_pool_create(NULL);
if (!pool) {
cJSON_Delete(filter);
@@ -750,12 +820,6 @@ int nt_query_sync_verbose(const char *filter_json,
return -1;
}
if (verbose && phase_label) {
nt_log("%s: querying %d relays via nostr_core relay pool",
phase_label,
relay_count);
}
pool_events = nostr_relay_pool_query_sync(pool,
relay_urls,
relay_count,
@@ -790,12 +854,6 @@ int nt_query_sync_verbose(const char *filter_json,
}
free(pool_events);
if (verbose && phase_label) {
nt_log("%s: relay pool collected %d unique events",
phase_label,
events_n);
}
nostr_relay_pool_destroy(pool);
*events_out = events;
*event_count = events_n;

View File

@@ -17,16 +17,46 @@ static int g_body_row = 0;
static char g_app_name[128] = "NOSTR TERMINAL";
static char g_app_version[64] = "";
static char g_user[256] = "";
static char g_composed_title[512] = "NOSTR TERMINAL";
static char g_status_line[512] = "";
static NtLogEntry g_log_entries[NT_LOG_MAX];
static int g_log_head = 0;
static int g_log_count = 0;
static pthread_mutex_t g_log_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned long g_log_version = 0;
static unsigned long g_rendered_status_version = 0;
static int g_idle_callback_registered = 0;
static WINDOW *nt_body_window(void) {
return (WINDOW *)tuin_body_window();
}
static void nt_tui_idle_callback(void) {
unsigned long log_version_snapshot;
unsigned long rendered_snapshot;
TuiStatus status;
pthread_mutex_lock(&g_log_mutex);
log_version_snapshot = g_log_version;
rendered_snapshot = g_rendered_status_version;
pthread_mutex_unlock(&g_log_mutex);
if (log_version_snapshot == rendered_snapshot) {
return;
}
status = nt_status();
tuin_render_footer(&status);
doupdate();
}
static void nt_register_idle_callback_once(void) {
if (!g_idle_callback_registered) {
tuin_set_idle_callback(nt_tui_idle_callback);
g_idle_callback_registered = 1;
}
}
void nt_print(const char *fmt, ...) {
WINDOW *body = nt_body_window();
char line[8192];
@@ -89,17 +119,20 @@ int nt_run_external_editor(int (*spawn)(void *user), void *user) {
return result;
}
static void nt_refresh_composed_title(void) {
if (g_user[0] != '\0') {
snprintf(g_composed_title, sizeof(g_composed_title), "%s - %s - %s", g_app_name, g_app_version, g_user);
} else {
snprintf(g_composed_title, sizeof(g_composed_title), "%s - %s", g_app_name, g_app_version);
}
}
TuiFrame nt_frame(const char *breadcrumb) {
static char composed_title[512];
TuiFrame frame;
if (g_user[0] != '\0') {
snprintf(composed_title, sizeof(composed_title), "%s - %s - %s", g_app_name, g_app_version, g_user);
} else {
snprintf(composed_title, sizeof(composed_title), "%s - %s", g_app_name, g_app_version);
}
nt_refresh_composed_title();
frame.app_name = composed_title;
frame.app_name = g_composed_title;
frame.app_version = "";
frame.breadcrumb = breadcrumb;
return frame;
@@ -114,6 +147,7 @@ TuiStatus nt_status(void) {
snprintf(g_status_line, sizeof(g_status_line), "%s", g_log_entries[idx].text);
}
status.text = g_status_line;
g_rendered_status_version = g_log_version;
pthread_mutex_unlock(&g_log_mutex);
return status;
@@ -122,6 +156,8 @@ TuiStatus nt_status(void) {
void nt_set_app_info(const char *name, const char *version) {
snprintf(g_app_name, sizeof(g_app_name), "%s", (name && name[0] != '\0') ? name : "NOSTR TERMINAL");
snprintf(g_app_version, sizeof(g_app_version), "%s", (version && version[0] != '\0') ? version : "");
nt_refresh_composed_title();
nt_register_idle_callback_once();
}
void nt_set_window_title(const char *title) {
@@ -134,6 +170,7 @@ void nt_set_window_title(const char *title) {
void nt_set_user(const char *user) {
snprintf(g_user, sizeof(g_user), "%s", (user && user[0] != '\0') ? user : "");
nt_refresh_composed_title();
}
void nt_log(const char *fmt, ...) {
@@ -170,6 +207,7 @@ void nt_log(const char *fmt, ...) {
if (g_log_count < NT_LOG_MAX) {
g_log_count++;
}
g_log_version++;
pthread_mutex_unlock(&g_log_mutex);
}
@@ -207,6 +245,7 @@ void nt_log_clear(void) {
g_log_head = 0;
g_log_count = 0;
g_status_line[0] = '\0';
g_log_version++;
pthread_mutex_unlock(&g_log_mutex);
}

View File

@@ -483,6 +483,8 @@ int state_load_user_info(void) {
snprintf(g_state.user_name, sizeof(g_state.user_name), "%s", g_state.npub_bech32);
}
nt_set_user(g_state.user_name);
return 0;
}