fix: NWC payment timeout/error now rejects properly (BUG-F4)
- Timeout and WebSocket errors reject with proper Error objects - Caller catches and falls through to poll-based confirmation - Preimage undefined check prevents calling confirm with no preimage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
acecc79d04
commit
74cb5cc728
@@ -0,0 +1,43 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
|
||||
// Test the NWC timeout/error behavior in isolation
|
||||
// Full component test would require too many mocks, so we test the promise pattern
|
||||
|
||||
describe('NWC payment timeout handling', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('timeout produces proper rejection error', async () => {
|
||||
// Simulate the payViaNWC timeout pattern
|
||||
const payPromise = new Promise<string | undefined>((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('NWC payment timed out'))
|
||||
}, 30_000)
|
||||
})
|
||||
|
||||
vi.advanceTimersByTime(30_000)
|
||||
|
||||
await expect(payPromise).rejects.toThrow('NWC payment timed out')
|
||||
})
|
||||
|
||||
it('websocket error produces proper rejection', async () => {
|
||||
const payPromise = new Promise<string | undefined>((_, reject) => {
|
||||
// Simulate immediate ws error
|
||||
queueMicrotask(() => reject(new Error('NWC WebSocket error')))
|
||||
})
|
||||
|
||||
await expect(payPromise).rejects.toThrow('NWC WebSocket error')
|
||||
})
|
||||
|
||||
it('undefined preimage is treated as failure', () => {
|
||||
const preimage: string | undefined = undefined
|
||||
// The fixed code checks: if (preimage) { ...confirm... }
|
||||
// So undefined preimage falls through to polling
|
||||
expect(!preimage).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -173,16 +173,22 @@ export function useWallet() {
|
||||
}
|
||||
if (nwcUrl && nwcValid) {
|
||||
paymentStatus.value = 'paying'
|
||||
const preimage = await payViaNWC(nwcUrl, bolt11)
|
||||
|
||||
// Tell server payment is confirmed (skip lookup_invoice polling)
|
||||
await authFetch(`/api/payments/confirm/${paymentId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ preimage, pubkey: pubkey.value }),
|
||||
})
|
||||
paymentStatus.value = 'confirmed'
|
||||
return paymentId
|
||||
try {
|
||||
const preimage = await payViaNWC(nwcUrl, bolt11)
|
||||
if (preimage) {
|
||||
await authFetch(`/api/payments/confirm/${paymentId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ preimage, pubkey: pubkey.value }),
|
||||
})
|
||||
paymentStatus.value = 'confirmed'
|
||||
return paymentId
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[Wallet] NWC payment failed, falling back to polling:', err)
|
||||
}
|
||||
// NWC failed or returned no preimage — fall through to polling
|
||||
paymentStatus.value = 'pending'
|
||||
}
|
||||
|
||||
// No NWC — poll for confirmation (manual payment / QR code flow)
|
||||
@@ -268,7 +274,7 @@ async function payViaNWC(nwcUrl: string, bolt11: string): Promise<string | undef
|
||||
const ws = new WebSocket(nwc.relay)
|
||||
const timeout = setTimeout(() => {
|
||||
ws.close()
|
||||
resolve(undefined)
|
||||
reject(new Error('NWC payment timed out'))
|
||||
}, 30_000)
|
||||
|
||||
ws.onopen = () => {
|
||||
@@ -309,7 +315,7 @@ async function payViaNWC(nwcUrl: string, bolt11: string): Promise<string | undef
|
||||
|
||||
ws.onerror = () => {
|
||||
clearTimeout(timeout)
|
||||
resolve(undefined)
|
||||
reject(new Error('NWC WebSocket error'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user