Build 12: failed username checks no longer read as 'Taken'
check_availability returns Unknown when the Tor request dies, and the claim panels collapsed everything but Available into 'Taken' — a free name looked taken whenever a circuit flaked. The full enum now reaches both panels: Unknown reads 'Couldn't check — connection hiccup. Try again.' in neutral gray, and Reserved/Invalid/Quarantined get their own copy. Tor::http_request also backs off up to ~15s across 5 attempts: fresh circuits fail while arti refreshes its directory consensus over the bridge, and 3 tries in 4.5s couldn't ride that out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+24
-13
@@ -168,11 +168,25 @@ struct ClaimState {
|
||||
}
|
||||
|
||||
enum ClaimMsg {
|
||||
Availability(bool),
|
||||
Availability(crate::nostr::nip05::Availability),
|
||||
Registered(String),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
/// Map an availability probe to user-facing state: `None` availability
|
||||
/// means the check itself failed — never present that as "Taken".
|
||||
fn availability_feedback(avail: crate::nostr::nip05::Availability) -> (Option<bool>, &'static str) {
|
||||
use crate::nostr::nip05::Availability::*;
|
||||
match avail {
|
||||
Available => (Some(true), "Available!"),
|
||||
Taken => (Some(false), "Taken"),
|
||||
Reserved => (Some(false), "Reserved"),
|
||||
Invalid => (Some(false), "Names are 3–30 chars: a–z, 0–9, _ or -"),
|
||||
Quarantined => (Some(false), "Not available"),
|
||||
Unknown => (None, "Couldn't check — connection hiccup. Try again."),
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ClaimState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -2099,13 +2113,10 @@ impl GoblinWalletView {
|
||||
if let Some(msg) = claim.result.lock().unwrap().take() {
|
||||
claim.checking = false;
|
||||
match msg {
|
||||
ClaimMsg::Availability(ok) => {
|
||||
claim.available = Some(ok);
|
||||
claim.message = Some(if ok {
|
||||
"Available!".into()
|
||||
} else {
|
||||
"Taken".into()
|
||||
});
|
||||
ClaimMsg::Availability(avail) => {
|
||||
let (available, msg) = availability_feedback(avail);
|
||||
claim.available = available;
|
||||
claim.message = Some(msg.to_string());
|
||||
}
|
||||
ClaimMsg::Registered(nip05) => {
|
||||
claim.message = Some(format!("Registered {}", nip05));
|
||||
@@ -2156,10 +2167,10 @@ impl GoblinWalletView {
|
||||
ui.label(
|
||||
RichText::new(msg)
|
||||
.font(FontId::new(13.0, fonts::regular()))
|
||||
.color(if claim.available == Some(false) {
|
||||
t.neg
|
||||
} else {
|
||||
t.pos
|
||||
.color(match claim.available {
|
||||
Some(false) => t.neg,
|
||||
Some(true) => t.pos,
|
||||
None => t.surface_text_dim,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -2227,7 +2238,7 @@ fn start_claim_check(claim: &mut ClaimState, name: &str, wallet: &Wallet) {
|
||||
Err(_) => return,
|
||||
};
|
||||
let avail = rt.block_on(crate::nostr::nip05::check_availability(&server, &name));
|
||||
let msg = ClaimMsg::Availability(avail == crate::nostr::nip05::Availability::Available);
|
||||
let msg = ClaimMsg::Availability(avail);
|
||||
*slot.lock().unwrap() = Some(msg);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -732,13 +732,10 @@ impl OnboardingContent {
|
||||
if let Some(msg) = self.claim.result.lock().unwrap().take() {
|
||||
self.claim.checking = false;
|
||||
match msg {
|
||||
ClaimMsg::Availability(ok) => {
|
||||
self.claim.available = Some(ok);
|
||||
self.claim.message = Some(if ok {
|
||||
"Available!".into()
|
||||
} else {
|
||||
"Taken".into()
|
||||
});
|
||||
ClaimMsg::Availability(avail) => {
|
||||
let (available, msg) = super::availability_feedback(avail);
|
||||
self.claim.available = available;
|
||||
self.claim.message = Some(msg.to_string());
|
||||
}
|
||||
ClaimMsg::Registered(nip05) => {
|
||||
self.claim.message =
|
||||
@@ -805,10 +802,10 @@ impl OnboardingContent {
|
||||
ui.label(
|
||||
RichText::new(msg)
|
||||
.font(FontId::new(13.0, fonts::regular()))
|
||||
.color(if self.claim.available == Some(false) {
|
||||
t.neg
|
||||
} else {
|
||||
t.pos
|
||||
.color(match self.claim.available {
|
||||
Some(false) => t.neg,
|
||||
Some(true) => t.pos,
|
||||
None => t.surface_text_dim,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
+6
-3
@@ -401,16 +401,19 @@ impl Tor {
|
||||
/// Perform an HTTP request over the embedded Tor client with optional
|
||||
/// headers, returning the response body. Used for NIP-05 and price APIs.
|
||||
/// Retries with a fresh isolated circuit: single-circuit connect failures
|
||||
/// right after bootstrap are common and transient.
|
||||
/// right after bootstrap are common and transient, and fresh circuits
|
||||
/// fail for tens of seconds while arti refreshes its directory consensus
|
||||
/// over the bridge (existing circuits keep working) — back off long
|
||||
/// enough to ride a refresh out.
|
||||
pub async fn http_request(
|
||||
method: &str,
|
||||
url: String,
|
||||
body: Option<String>,
|
||||
headers: Vec<(String, String)>,
|
||||
) -> Option<String> {
|
||||
for attempt in 0..3 {
|
||||
for attempt in 0..5 {
|
||||
if attempt > 0 {
|
||||
tokio::time::sleep(Duration::from_millis(1500)).await;
|
||||
tokio::time::sleep(Duration::from_secs(1 << (attempt - 1))).await;
|
||||
}
|
||||
let resp =
|
||||
Self::http_request_once(method, url.clone(), body.clone(), headers.clone()).await;
|
||||
|
||||
Reference in New Issue
Block a user