upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-27 09:31:45 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-27 14:24:52 +0000
commit436b707b2bdecb995bbdb374a029714c9f4c5159 (patch)
treef45ec5c6f166070e078ca7415f9756e33ee91ebf /src
parent76a5e7b46dbe90ebf5e31904cb510e6cab242cf4 (diff)
feat: show and allow changing signer relays in nostrconnect flow
display signer relays below QR code and nostrconnect URL with an option to change them via the existing multiselect UI before connecting
Diffstat (limited to 'src')
-rw-r--r--src/lib/login/fresh.rs109
1 files changed, 95 insertions, 14 deletions
diff --git a/src/lib/login/fresh.rs b/src/lib/login/fresh.rs
index e81b74a..0b5922b 100644
--- a/src/lib/login/fresh.rs
+++ b/src/lib/login/fresh.rs
@@ -23,7 +23,8 @@ use crate::client::MockConnect;
23use crate::{ 23use crate::{
24 cli_interactor::{ 24 cli_interactor::{
25 Interactor, InteractorPrompt, Printer, PromptChoiceParms, PromptConfirmParms, 25 Interactor, InteractorPrompt, Printer, PromptChoiceParms, PromptConfirmParms,
26 PromptInputParms, PromptPasswordParms, 26 PromptInputParms, PromptPasswordParms, multi_select_with_custom_value,
27 show_multi_input_prompt_success,
27 }, 28 },
28 client::{Connect, nip05_query, save_event_in_global_cache, send_events}, 29 client::{Connect, nip05_query, save_event_in_global_cache, send_events},
29 git::{Repo, RepoActions, remove_git_config_item, save_git_config_item}, 30 git::{Repo, RepoActions, remove_git_config_item, save_git_config_item},
@@ -273,7 +274,46 @@ pub async fn get_fresh_nip46_signer(
273 .dont_report(), 274 .dont_report(),
274 )?; 275 )?;
275 let url = match signer_choice { 276 let url = match signer_choice {
276 0 | 1 => nostr_connect_url, 277 0 | 1 => {
278 // Loop so the user can change relays and see a refreshed QR/URL.
279 let mut current_url = nostr_connect_url;
280 loop {
281 // Display QR or URL with the current relay list.
282 display_nostr_connect(signer_choice, &current_url)?;
283
284 // Offer the option to change relays or proceed.
285 let action = Interactor::default().choice(
286 PromptChoiceParms::default()
287 .with_prompt(format!(
288 "signer relays: {}",
289 current_url
290 .relays()
291 .iter()
292 .map(std::string::ToString::to_string)
293 .collect::<Vec<_>>()
294 .join(", ")
295 ))
296 .with_default(0)
297 .with_choices(vec![
298 "waiting for signer app to connect...".to_string(),
299 "change signer relays".to_string(),
300 ])
301 .dont_report(),
302 )?;
303
304 if action == 0 {
305 break current_url;
306 }
307
308 // User wants to change relays — run the multiselect and rebuild URL.
309 let selected = select_signer_relays(&current_url)?;
310 if !selected.is_empty() {
311 let new_relays: Vec<RelayUrl> =
312 selected.iter().flat_map(|s| RelayUrl::parse(s)).collect();
313 current_url = NostrConnectURI::client(app_key.public_key(), new_relays, "ngit");
314 }
315 }
316 }
277 2 => { 317 2 => {
278 let mut error = None; 318 let mut error = None;
279 loop { 319 loop {
@@ -316,26 +356,16 @@ pub async fn get_fresh_nip46_signer(
316 { 356 {
317 let printer_clone = Arc::clone(&printer); 357 let printer_clone = Arc::clone(&printer);
318 let mut printer_locked = printer_clone.lock().await; 358 let mut printer_locked = printer_clone.lock().await;
359 // For choices 0 and 1 the content was already printed by display_nostr_connect
360 // inside the relay-selection loop above; only the "waiting" hint is added here.
319 match signer_choice { 361 match signer_choice {
320 0 => { 362 0 => {
321 printer_locked
322 .println("login to nostr with remote signer via nostr connect".to_string());
323 printer_locked.println("scan QR code in signer app (eg Amber):".to_string());
324 printer_locked.printlns(generate_qr(&url.to_string())?);
325 printer_locked.println( 363 printer_locked.println(
326 "scan QR code in signer app or use ctrl + c to go back to login menu..." 364 "scan QR code in signer app or use ctrl + c to go back to login menu..."
327 .to_string(), 365 .to_string(),
328 ); 366 );
329 } 367 }
330 1 => { 368 1 => {
331 printer_locked
332 .println("login to nostr with remote signer via nostr connect".to_string());
333 printer_locked.println("".to_string());
334 printer_locked.println_with_custom_formatting(
335 format!("{}", Style::new().bold().apply_to(url.to_string()),),
336 url.to_string(),
337 );
338 printer_locked.println("".to_string());
339 printer_locked.println( 369 printer_locked.println(
340 "paste this url into signer app or use ctrl + c to go back to login menu..." 370 "paste this url into signer app or use ctrl + c to go back to login menu..."
341 .to_string(), 371 .to_string(),
@@ -396,6 +426,57 @@ pub fn generate_nostr_connect_app(
396 Ok((app_key, nostr_connect_url)) 426 Ok((app_key, nostr_connect_url))
397} 427}
398 428
429/// Print the QR code or nostrconnect URL to stderr.
430///
431/// `choice` must be 0 (QR) or 1 (URL). Output goes directly to stderr so it
432/// is visible before the relay-selection choice prompt that follows.
433fn display_nostr_connect(choice: usize, url: &NostrConnectURI) -> Result<()> {
434 eprintln!("login to nostr with remote signer via nostr connect");
435 if choice == 0 {
436 eprintln!("scan QR code in signer app (eg Amber):");
437 for line in generate_qr(&url.to_string())? {
438 eprintln!("{line}");
439 }
440 } else {
441 eprintln!();
442 eprintln!("{}", Style::new().bold().apply_to(url.to_string()));
443 eprintln!();
444 }
445 Ok(())
446}
447
448/// Present the multiselect UI for choosing signer relays.
449///
450/// Returns the selected relay list as strings. An empty return means the user
451/// submitted without selecting anything (caller should keep the existing URL).
452fn select_signer_relays(nostr_connect_url: &NostrConnectURI) -> Result<Vec<String>> {
453 let current_relays: Vec<String> = nostr_connect_url
454 .relays()
455 .iter()
456 .map(std::string::ToString::to_string)
457 .collect();
458
459 let defaults = vec![true; current_relays.len()];
460 let selected = multi_select_with_custom_value(
461 "signer relays",
462 "signer relay",
463 current_relays,
464 defaults,
465 |s| {
466 let url = if s.starts_with("ws://") || s.starts_with("wss://") {
467 s.to_string()
468 } else {
469 format!("wss://{s}")
470 };
471 RelayUrl::parse(&url)
472 .map(|r| r.to_string())
473 .context(format!("invalid relay URL: {s}"))
474 },
475 )?;
476 show_multi_input_prompt_success("signer relays", &selected);
477 Ok(selected)
478}
479
399pub async fn fetch_nip46_uri_from_nip05(nip05: &str) -> Result<NostrConnectURI> { 480pub async fn fetch_nip46_uri_from_nip05(nip05: &str) -> Result<NostrConnectURI> {
400 let term = console::Term::stderr(); 481 let term = console::Term::stderr();
401 term.write_line("contacting login service provider...")?; 482 term.write_line("contacting login service provider...")?;