diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-27 13:49:28 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-27 14:25:34 +0000 |
| commit | bccac9db18b93a08e5087578231ca9ac0df1903c (patch) | |
| tree | a5c3acb53939885a554095876950676276b09c1c /src/lib/login/fresh.rs | |
| parent | 7b393f2e249e3bda141994d0c67aa9812d87302a (diff) | |
fix: connect to relay before user acknowledges nostrconnect QR/URL
Previously NostrConnect was only instantiated after the user selected
'waiting for signer app to connect...', so any connection made while
the prompt was visible was missed. Now listening starts immediately
after the QR/URL is displayed, with a spinner that auto-resolves on
connection rather than requiring a manual prompt dismissal.
Diffstat (limited to 'src/lib/login/fresh.rs')
| -rw-r--r-- | src/lib/login/fresh.rs | 154 |
1 files changed, 117 insertions, 37 deletions
diff --git a/src/lib/login/fresh.rs b/src/lib/login/fresh.rs index 8d3a806..057b41a 100644 --- a/src/lib/login/fresh.rs +++ b/src/lib/login/fresh.rs | |||
| @@ -3,6 +3,7 @@ use std::{str::FromStr, sync::Arc, time::Duration}; | |||
| 3 | use anyhow::{Context, Result, bail}; | 3 | use anyhow::{Context, Result, bail}; |
| 4 | use console::Style; | 4 | use console::Style; |
| 5 | use dialoguer::theme::{ColorfulTheme, Theme}; | 5 | use dialoguer::theme::{ColorfulTheme, Theme}; |
| 6 | use indicatif::{ProgressBar, ProgressStyle}; | ||
| 6 | use nostr::nips::nip46::NostrConnectURI; | 7 | use nostr::nips::nip46::NostrConnectURI; |
| 7 | use nostr_connect::client::NostrConnect; | 8 | use nostr_connect::client::NostrConnect; |
| 8 | use nostr_sdk::{EventBuilder, Keys, Metadata, NostrSigner, PublicKey, RelayUrl, ToBech32}; | 9 | use nostr_sdk::{EventBuilder, Keys, Metadata, NostrSigner, PublicKey, RelayUrl, ToBech32}; |
| @@ -337,7 +338,72 @@ pub async fn get_fresh_nip46_signer( | |||
| 337 | // Display QR or URL with the current relay list. | 338 | // Display QR or URL with the current relay list. |
| 338 | display_nostr_connect(signer_choice, ¤t_url)?; | 339 | display_nostr_connect(signer_choice, ¤t_url)?; |
| 339 | 340 | ||
| 340 | // Offer the option to change relays or proceed. | 341 | // Start listening for the signer immediately after displaying |
| 342 | // the QR/URL — don't wait for the user to press anything. | ||
| 343 | let nostr_connect = Arc::new(NostrConnect::new( | ||
| 344 | current_url.clone(), | ||
| 345 | app_key.clone(), | ||
| 346 | Duration::from_secs(10 * 60), | ||
| 347 | None, | ||
| 348 | )?); | ||
| 349 | let signer_arc: Arc<dyn NostrSigner> = nostr_connect.clone(); | ||
| 350 | let pubkey_handle = tokio::spawn(async move { signer_arc.get_public_key().await }); | ||
| 351 | |||
| 352 | // Show a spinner while waiting; Ctrl+C lets the user change | ||
| 353 | // relays or go back instead of waiting indefinitely. | ||
| 354 | eprintln!(); | ||
| 355 | // TODO: remove the dim delay note once the rust-nostr | ||
| 356 | // NostrConnect handshake delay bug is fixed. | ||
| 357 | let spinner_msg = format!( | ||
| 358 | "{} {}", | ||
| 359 | console::style("waiting for signer app to connect...").bold(), | ||
| 360 | console::style("(may take 10s+ to connect once added)").color256(247), | ||
| 361 | ); | ||
| 362 | let spinner = ProgressBar::new_spinner() | ||
| 363 | .with_style( | ||
| 364 | ProgressStyle::with_template("{spinner} {msg}") | ||
| 365 | .unwrap() | ||
| 366 | .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "), | ||
| 367 | ) | ||
| 368 | .with_message(spinner_msg); | ||
| 369 | spinner.enable_steady_tick(Duration::from_millis(100)); | ||
| 370 | |||
| 371 | let res = tokio::select! { | ||
| 372 | result = pubkey_handle => { | ||
| 373 | spinner.finish_and_clear(); | ||
| 374 | match result { | ||
| 375 | Ok(Ok(pk)) => Some(pk), | ||
| 376 | _ => None, | ||
| 377 | } | ||
| 378 | }, | ||
| 379 | _ = signal::ctrl_c() => { | ||
| 380 | spinner.finish_and_clear(); | ||
| 381 | None | ||
| 382 | } | ||
| 383 | }; | ||
| 384 | |||
| 385 | if let Some(public_key) = res { | ||
| 386 | // Connection succeeded — retrieve the canonical bunker URI | ||
| 387 | // and return. | ||
| 388 | let bunker_url = nostr_connect | ||
| 389 | .bunker_uri() | ||
| 390 | .await | ||
| 391 | .context("failed to get bunker URI from NostrConnect client")?; | ||
| 392 | let signer_info = SignerInfo::Bunker { | ||
| 393 | bunker_uri: bunker_url.to_string(), | ||
| 394 | bunker_app_key: app_key.secret_key().to_secret_hex(), | ||
| 395 | npub: Some(public_key.to_bech32()?), | ||
| 396 | }; | ||
| 397 | return Ok(Some(( | ||
| 398 | nostr_connect as Arc<dyn NostrSigner>, | ||
| 399 | public_key, | ||
| 400 | signer_info, | ||
| 401 | SignerInfoSource::GitGlobal, | ||
| 402 | ))); | ||
| 403 | } | ||
| 404 | |||
| 405 | // Ctrl+C was pressed — offer the user a chance to change | ||
| 406 | // relays or go back to the top-level login menu. | ||
| 341 | let action = Interactor::default().choice( | 407 | let action = Interactor::default().choice( |
| 342 | PromptChoiceParms::default() | 408 | PromptChoiceParms::default() |
| 343 | .with_prompt(format!( | 409 | .with_prompt(format!( |
| @@ -351,22 +417,29 @@ pub async fn get_fresh_nip46_signer( | |||
| 351 | )) | 417 | )) |
| 352 | .with_default(0) | 418 | .with_default(0) |
| 353 | .with_choices(vec![ | 419 | .with_choices(vec![ |
| 354 | "waiting for signer app to connect...".to_string(), | 420 | "try again".to_string(), |
| 355 | "change signer relays".to_string(), | 421 | "change signer relays".to_string(), |
| 422 | "back".to_string(), | ||
| 356 | ]) | 423 | ]) |
| 357 | .dont_report(), | 424 | .dont_report(), |
| 358 | )?; | 425 | )?; |
| 359 | 426 | ||
| 360 | if action == 0 { | 427 | match action { |
| 361 | break current_url; | 428 | 0 => { |
| 362 | } | 429 | // Redisplay QR/URL and start listening again. |
| 363 | 430 | continue; | |
| 364 | // User wants to change relays — run the multiselect and rebuild URL. | 431 | } |
| 365 | let selected = select_signer_relays(¤t_url)?; | 432 | 1 => { |
| 366 | if !selected.is_empty() { | 433 | // Change relays and rebuild URL. |
| 367 | let new_relays: Vec<RelayUrl> = | 434 | let selected = select_signer_relays(¤t_url)?; |
| 368 | selected.iter().flat_map(|s| RelayUrl::parse(s)).collect(); | 435 | if !selected.is_empty() { |
| 369 | current_url = NostrConnectURI::client(app_key.public_key(), new_relays, "ngit"); | 436 | let new_relays: Vec<RelayUrl> = |
| 437 | selected.iter().flat_map(|s| RelayUrl::parse(s)).collect(); | ||
| 438 | current_url = | ||
| 439 | NostrConnectURI::client(app_key.public_key(), new_relays, "ngit"); | ||
| 440 | } | ||
| 441 | } | ||
| 442 | _ => return Ok(None), | ||
| 370 | } | 443 | } |
| 371 | } | 444 | } |
| 372 | } | 445 | } |
| @@ -412,28 +485,9 @@ pub async fn get_fresh_nip46_signer( | |||
| 412 | { | 485 | { |
| 413 | let printer_clone = Arc::clone(&printer); | 486 | let printer_clone = Arc::clone(&printer); |
| 414 | let mut printer_locked = printer_clone.lock().await; | 487 | let mut printer_locked = printer_clone.lock().await; |
| 415 | // For choices 0 and 1 the content was already printed by display_nostr_connect | 488 | printer_locked.println( |
| 416 | // inside the relay-selection loop above; only the "waiting" hint is added here. | 489 | "add / approve in your signer or use ctrl + c to go back to login menu...".to_string(), |
| 417 | match signer_choice { | 490 | ); |
| 418 | 0 => { | ||
| 419 | printer_locked.println( | ||
| 420 | "scan QR code in signer app or use ctrl + c to go back to login menu..." | ||
| 421 | .to_string(), | ||
| 422 | ); | ||
| 423 | } | ||
| 424 | 1 => { | ||
| 425 | printer_locked.println( | ||
| 426 | "paste this url into signer app or use ctrl + c to go back to login menu..." | ||
| 427 | .to_string(), | ||
| 428 | ); | ||
| 429 | } | ||
| 430 | _ => { | ||
| 431 | printer_locked.println( | ||
| 432 | "add / approve in your signer or use ctrl + c to go back to login menu..." | ||
| 433 | .to_string(), | ||
| 434 | ); | ||
| 435 | } | ||
| 436 | } | ||
| 437 | } | 491 | } |
| 438 | 492 | ||
| 439 | let (signer, user_public_key, bunker_url) = | 493 | let (signer, user_public_key, bunker_url) = |
| @@ -487,17 +541,43 @@ pub fn generate_nostr_connect_app( | |||
| 487 | /// `choice` must be 0 (QR) or 1 (URL). Output goes directly to stderr so it | 541 | /// `choice` must be 0 (QR) or 1 (URL). Output goes directly to stderr so it |
| 488 | /// is visible before the relay-selection choice prompt that follows. | 542 | /// is visible before the relay-selection choice prompt that follows. |
| 489 | fn display_nostr_connect(choice: usize, url: &NostrConnectURI) -> Result<()> { | 543 | fn display_nostr_connect(choice: usize, url: &NostrConnectURI) -> Result<()> { |
| 490 | eprintln!("login to nostr with remote signer via nostr connect"); | 544 | let dim = Style::new().for_stderr().color256(247); |
| 545 | let hint = dim.apply_to("(ctrl+c to change)"); | ||
| 546 | eprintln!( | ||
| 547 | "{}", | ||
| 548 | Style::new().for_stderr().bold().apply_to("nostr connect") | ||
| 549 | ); | ||
| 491 | if choice == 0 { | 550 | if choice == 0 { |
| 492 | eprintln!("scan QR code in signer app (eg Amber):"); | 551 | eprintln!( |
| 552 | "{}", | ||
| 553 | dim.apply_to("scan QR code in signer app (eg. Amber):") | ||
| 554 | ); | ||
| 493 | for line in generate_qr(&url.to_string())? { | 555 | for line in generate_qr(&url.to_string())? { |
| 494 | eprintln!("{line}"); | 556 | eprintln!("{line}"); |
| 495 | } | 557 | } |
| 496 | } else { | 558 | } else { |
| 497 | eprintln!(); | 559 | eprintln!(); |
| 498 | eprintln!("{}", Style::new().bold().apply_to(url.to_string())); | 560 | eprintln!( |
| 561 | "{}", | ||
| 562 | Style::new() | ||
| 563 | .for_stderr() | ||
| 564 | .bold() | ||
| 565 | .cyan() | ||
| 566 | .apply_to(url.to_string()) | ||
| 567 | ); | ||
| 499 | eprintln!(); | 568 | eprintln!(); |
| 500 | } | 569 | } |
| 570 | let relays = url | ||
| 571 | .relays() | ||
| 572 | .iter() | ||
| 573 | .map(std::string::ToString::to_string) | ||
| 574 | .collect::<Vec<_>>() | ||
| 575 | .join(", "); | ||
| 576 | eprintln!( | ||
| 577 | "{} {}", | ||
| 578 | dim.apply_to(format!("signer relays: {relays}")), | ||
| 579 | hint | ||
| 580 | ); | ||
| 501 | Ok(()) | 581 | Ok(()) |
| 502 | } | 582 | } |
| 503 | 583 | ||