diff options
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 | ||