diff options
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | src/lib/login/fresh.rs | 109 |
2 files changed, 96 insertions, 14 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f88b1d..930fe39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
| @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 13 | - `ngit sync` now publishes the current state event to grasp server relays that are missing it or have a stale version before attempting git pushes, preventing rejections; per-relay state visibility is captured during the nostr fetch and surfaced via `FetchReport::state_per_relay` | 13 | - `ngit sync` now publishes the current state event to grasp server relays that are missing it or have a stale version before attempting git pushes, preventing rejections; per-relay state visibility is captured during the nostr fetch and surfaced via `FetchReport::state_per_relay` |
| 14 | - Fetch filters now request kind-5 deletion events for cached state and repo announcement events by `#e` tag (NIP-09), in addition to the existing `#a`-tagged filter; ensures deletions of these events are received even from clients that do not embed a repo coordinate in their deletion event | 14 | - Fetch filters now request kind-5 deletion events for cached state and repo announcement events by `#e` tag (NIP-09), in addition to the existing `#a`-tagged filter; ensures deletions of these events are received even from clients that do not embed a repo coordinate in their deletion event |
| 15 | - `FetchReport` now tracks and displays a count of kind-5 deletion events received (e.g. `"1 deletion"` in the fetch summary) | 15 | - `FetchReport` now tracks and displays a count of kind-5 deletion events received (e.g. `"1 deletion"` in the fetch summary) |
| 16 | - `ngit account login` nostrconnect flow now shows current signer relays and allows changing them | ||
| 16 | 17 | ||
| 17 | ### Fixed | 18 | ### Fixed |
| 18 | 19 | ||
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; | |||
| 23 | use crate::{ | 23 | use 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, ¤t_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(¤t_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. | ||
| 433 | fn 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). | ||
| 452 | fn 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 | |||
| 399 | pub async fn fetch_nip46_uri_from_nip05(nip05: &str) -> Result<NostrConnectURI> { | 480 | pub 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...")?; |