diff --git a/apps/machine/electron/nfc-service.ts b/apps/machine/electron/nfc-service.ts index 970d17d..ca9ace1 100644 --- a/apps/machine/electron/nfc-service.ts +++ b/apps/machine/electron/nfc-service.ts @@ -155,22 +155,28 @@ export async function startNfcReader( r.autoProcessing = false onStatus({ state: 'ready', reader: name }) + // Cooldown after a failed read: these cheap CCID readers can get wedged into + // a present↔empty storm when hammered, so ignore re-detections for a beat + // after a failure. Successful reads don't cool down. + let cooldownUntil = 0 r.on('card', async () => { + if (Date.now() < cooldownUntil) return onStatus({ state: 'reading', reader: name }) - // Single attempt: hammering a flaky RF link with retries wedges these - // cheap CCID readers. A read is a few APDU round-trips; if the card - // shifts mid-read the transmit fails and the user simply re-taps. + // Single attempt: retrying hammers a flaky RF link. A read is a few APDU + // round-trips; if the card shifts mid-read the transmit fails and the + // user simply re-taps. try { const lnurlw = await readNdefLnurlw((apdu, maxLen) => r.transmit(apdu, maxLen)) if (lnurlw) { onCard(lnurlw) - } else { - onStatus({ state: 'error', reader: name, message: 'not a Bolt Card' }) + return } + onStatus({ state: 'error', reader: name, message: 'not a Bolt Card' }) } catch (e) { onStatus({ state: 'error', reader: name, message: 'card read failed — hold steady & retap' }) void e } + cooldownUntil = Date.now() + 1500 }) r.on('card.off', () => onStatus({ state: 'card-removed', reader: name })) r.on('error', (err: unknown) =>