From bd149fdb2c7ca9f6b930bcb2f95f1edec041ad3c Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Fri, 3 Jul 2026 11:48:03 +0700 Subject: [PATCH] Fix Amber nostrconnect flow: 20s timeout, error state, stale closure fix, better relays, debug logs - Replace wss://relay.primal.net with wss://nos.lol (more reliable for NIP-46) - Shorten timeout from 10 minutes to 20 seconds - Fix stale closure bug: track amberStep in amberStepRef so timeout can correctly reset UI - Add 'error' step with red UI and 'Try Again' button instead of endless spinner - Extend since filter from 5s to 10s in the past - Add console logs for URI, relays, incoming events, decryption result, secret matching Co-authored-by: shakespeare.diy --- src/components/auth/LoginDialog.tsx | 74 +++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/src/components/auth/LoginDialog.tsx b/src/components/auth/LoginDialog.tsx index ac6e206..ff16cb4 100644 --- a/src/components/auth/LoginDialog.tsx +++ b/src/components/auth/LoginDialog.tsx @@ -25,7 +25,7 @@ interface LoginDialogProps { const NOSTRCONNECT_RELAYS = [ 'wss://relay.nsec.app', 'wss://relay.damus.io', - 'wss://relay.primal.net', + 'wss://nos.lol', ]; // Key for storing pending nostrconnect session across app restarts @@ -91,13 +91,14 @@ export const LoginDialog: React.FC = ({ isOpen, onClose, onLog const [nsecInput, setNsecInput] = useState(''); const [nsecError, setNsecError] = useState(''); - const [amberStep, setAmberStep] = useState<'idle' | 'connecting' | 'waiting' | 'success'>('idle'); + const [amberStep, setAmberStep] = useState<'idle' | 'connecting' | 'waiting' | 'error' | 'success'>('idle'); const [amberStatus, setAmberStatus] = useState(''); const [nostrConnectUri, setNostrConnectUri] = useState(''); const abortRef = useRef(null); const poolRef = useRef(null); const subRef = useRef<{ close: () => void } | null>(null); const pendingRef = useRef(null); + const amberStepRef = useRef<'idle' | 'connecting' | 'waiting' | 'error' | 'success'>('idle'); const login = useLoginActions(); const { addLogin, setLogin } = useNostrLogin(); @@ -144,6 +145,7 @@ export const LoginDialog: React.FC = ({ isOpen, onClose, onLog setLogin(nlogin.id); clearPendingNC(); cleanup(); + amberStepRef.current = 'success'; setAmberStep('success'); toast({ title: '🛹 Logged in!', description: 'Connected via Amber!' }); setTimeout(() => { onLogin(); onClose(); }, 800); @@ -218,6 +220,7 @@ export const LoginDialog: React.FC = ({ isOpen, onClose, onLog abortRef.current?.abort(); abortRef.current = null; cleanup(); + amberStepRef.current = 'idle'; setAmberStep('idle'); setAmberStatus(''); setNostrConnectUri(''); @@ -261,18 +264,26 @@ export const LoginDialog: React.FC = ({ isOpen, onClose, onLog savePendingNC(pending); setNostrConnectUri(uri); + amberStepRef.current = 'connecting'; setAmberStep('connecting'); setAmberStatus('Opening Amber...'); + console.log('[Amber] Generated nostrconnect URI:', uri); + console.log('[Amber] Relays:', NOSTRCONNECT_RELAYS); + console.log('[Amber] Client pubkey:', clientPubkey); + try { window.open(uri, '_blank'); } catch { /* user copies manually */ } + amberStepRef.current = 'waiting'; setAmberStep('waiting'); setAmberStatus('Waiting for Amber to approve...'); - // Also keep a live relay subscription running (works on web/PWA, may sleep in Capacitor) + // Live relay subscription (works on web/PWA; may be backgrounded in Capacitor) const pool = new SimplePool(); poolRef.current = pool; - const since = Math.floor(Date.now() / 1000) - 5; + const since = Math.floor(Date.now() / 1000) - 10; + + console.log('[Amber] Subscribing to relays since:', since); const sub = pool.subscribeMany( NOSTRCONNECT_RELAYS, @@ -280,33 +291,45 @@ export const LoginDialog: React.FC = ({ isOpen, onClose, onLog { onevent: async (event) => { if (controller.signal.aborted) return; + console.log('[Amber] Received event from:', event.pubkey); try { const decrypted = await nip04.decrypt(clientSk, event.pubkey, event.content); const parsed = JSON.parse(decrypted) as { id: string; result: string; error?: string }; - if (parsed.error && parsed.error !== '') return; - if (parsed.result !== secret && parsed.result !== 'ack') return; - + console.log('[Amber] Decrypted response:', parsed); + if (parsed.error && parsed.error !== '') { + console.warn('[Amber] Error in response:', parsed.error); + return; + } + if (parsed.result !== secret && parsed.result !== 'ack') { + console.warn('[Amber] Secret mismatch, ignoring'); + return; + } + console.log('[Amber] Valid response — completing login'); controller.abort(); pendingRef.current = null; await completeLogin(event.pubkey, clientSk, clientNsec, NOSTRCONNECT_RELAYS); - } catch { /* ignore */ } + } catch (e) { + console.warn('[Amber] Failed to decrypt event:', e); + } }, } ); subRef.current = sub; - // Timeout after 10 minutes + // Timeout after 20 seconds — show error, let user retry setTimeout(() => { if (!controller.signal.aborted) { + console.warn('[Amber] Timed out after 20s — no response received'); controller.abort(); cleanup(); - if (amberStep === 'waiting') { - setAmberStep('idle'); - toast({ title: 'Timed out', description: 'Amber did not respond. Try again.', variant: 'destructive' }); + if (amberStepRef.current === 'waiting') { + amberStepRef.current = 'error'; + setAmberStep('error'); + setAmberStatus('Amber did not respond in time. Did you approve the connection?'); } } - }, 10 * 60 * 1000); - }, [cleanup, completeLogin, amberStep, toast]); + }, 20 * 1000); + }, [cleanup, completeLogin, toast]); const handleBunkerLogin = async () => { const uri = bunkerUri.trim(); @@ -419,11 +442,16 @@ export const LoginDialog: React.FC = ({ isOpen, onClose, onLog )} - {amberStep === 'waiting' && ( -
+ {(amberStep === 'waiting' || amberStep === 'error') && ( +
- -

Waiting for Amber...

+ {amberStep === 'error' + ?

⏱ Timed out

+ : + } +

+ {amberStep === 'error' ? 'No response from Amber' : 'Waiting for Amber...'} +

{amberStatus}

@@ -451,11 +479,15 @@ export const LoginDialog: React.FC = ({ isOpen, onClose, onLog )}
- -