Auto-heal wallets stuck on a CORS-blocked mint
- useCashu now probes existing mints on every init (not just when empty); if none are browser-reachable it adds a reachable candidate mint. This fixes existing users whose only saved mint is the legacy CORS-blocked mint.minibits.cash default. - CashuWallet active-mint selection now prefers the first browser-reachable mint instead of blindly mints[0], so users aren't stuck with a mint that fails every invoice. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
a0c59375ed
commit
b830bf965d
2 changed files with 53 additions and 13 deletions
|
|
@ -98,11 +98,31 @@ export function CashuWallet() {
|
||||||
mints,
|
mints,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set first mint as active if none selected
|
// Pick an active mint when none is selected — prefer a browser-REACHABLE mint
|
||||||
|
// so a user whose first saved mint is CORS-blocked (e.g. legacy minibits)
|
||||||
|
// doesn't get stuck with a mint that fails every invoice.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mints.length > 0 && !activeMintUrl) {
|
if (mints.length === 0 || activeMintUrl) return;
|
||||||
setActiveMintUrl(mints[0].url);
|
let cancelled = false;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// Probe mints in order; select the first reachable one.
|
||||||
|
for (const m of mints) {
|
||||||
|
const { probeMintReachability } = await import('@/lib/mintReachability');
|
||||||
|
const probe = await probeMintReachability(m.url);
|
||||||
|
if (cancelled) return;
|
||||||
|
if (probe.reachability === 'ok') {
|
||||||
|
setActiveMintUrl(m.url);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Fallback: nothing reachable — select the first so the UI still renders.
|
||||||
|
if (!cancelled) setActiveMintUrl(mints[0].url);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
}, [mints, activeMintUrl, setActiveMintUrl]);
|
}, [mints, activeMintUrl, setActiveMintUrl]);
|
||||||
|
|
||||||
// Show wallet loading state
|
// Show wallet loading state
|
||||||
|
|
|
||||||
|
|
@ -167,35 +167,55 @@ export function useCashu(): UseCashuReturn {
|
||||||
console.warn('[Cashu] Failed to enable some coco services:', err);
|
console.warn('[Cashu] Failed to enable some coco services:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a default mint if the user has none yet.
|
// Ensure the user has at least one BROWSER-REACHABLE mint.
|
||||||
//
|
//
|
||||||
// A mint MUST send permissive CORS headers or the browser blocks every
|
// A mint MUST send permissive CORS headers or the browser blocks every
|
||||||
// request with a NetworkError ("Failed to fetch mint"). We can't know from
|
// request with a NetworkError ("Failed to fetch mint"). We can't know from
|
||||||
// build time which mint is reachable in THIS browser, so we probe an
|
// build time which mint is reachable in THIS browser, so we probe.
|
||||||
// ordered list of web-friendly candidates and add the first that works.
|
//
|
||||||
// (mint.minibits.cash is intentionally excluded — it's mobile-app only.)
|
// This runs on every init so it also AUTO-HEALS an existing user whose
|
||||||
|
// only saved mint is CORS-blocked (e.g. legacy mint.minibits.cash default):
|
||||||
|
// if none of their mints are reachable, we add a working one and — since
|
||||||
|
// the UI picks the first mint — keep the broken one but ensure a good one
|
||||||
|
// exists. (mint.minibits.cash is excluded from candidates — mobile only.)
|
||||||
try {
|
try {
|
||||||
const existingMints = await coco.mint.getAllTrustedMints();
|
const existingMints = await coco.mint.getAllTrustedMints();
|
||||||
if (existingMints.length === 0) {
|
|
||||||
|
// Does the user already have at least one reachable mint?
|
||||||
|
let hasReachable = false;
|
||||||
|
for (const m of existingMints) {
|
||||||
|
const probe = await probeMintReachability(m.mintUrl);
|
||||||
|
if (probe.reachability === 'ok') {
|
||||||
|
hasReachable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
console.warn('[Cashu] Existing mint not usable in browser:', m.mintUrl, probe.reachability);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasReachable) {
|
||||||
const CANDIDATE_MINTS = [
|
const CANDIDATE_MINTS = [
|
||||||
'https://mint.coinos.io',
|
'https://mint.coinos.io',
|
||||||
'https://21mint.me',
|
'https://21mint.me',
|
||||||
'https://stablenut.cashu.network',
|
'https://stablenut.cashu.network',
|
||||||
'https://mint.lnvoltz.com',
|
'https://mint.lnvoltz.com',
|
||||||
];
|
];
|
||||||
|
const alreadyHave = new Set(
|
||||||
|
existingMints.map((m: Mint) => m.mintUrl.toLowerCase()),
|
||||||
|
);
|
||||||
for (const url of CANDIDATE_MINTS) {
|
for (const url of CANDIDATE_MINTS) {
|
||||||
|
if (alreadyHave.has(url.toLowerCase())) continue;
|
||||||
const probe = await probeMintReachability(url);
|
const probe = await probeMintReachability(url);
|
||||||
if (probe.reachability === 'ok') {
|
if (probe.reachability === 'ok') {
|
||||||
console.log('[Cashu] Adding default mint:', url);
|
console.log('[Cashu] Adding reachable mint:', url);
|
||||||
await coco.mint.addMint(url, { trusted: true });
|
await coco.mint.addMint(url, { trusted: true });
|
||||||
console.log('[Cashu] Default mint added successfully');
|
console.log('[Cashu] Reachable mint added successfully');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
console.warn('[Cashu] Skipping unreachable default candidate:', url, probe.reachability);
|
console.warn('[Cashu] Skipping unreachable candidate:', url, probe.reachability);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[Cashu] Failed to add default mint:', err);
|
console.warn('[Cashu] Failed to ensure a reachable mint:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsInitialized(true);
|
setIsInitialized(true);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue