diff --git a/apps/machine/electron/nfc-service.ts b/apps/machine/electron/nfc-service.ts index ecafc07..970d17d 100644 --- a/apps/machine/electron/nfc-service.ts +++ b/apps/machine/electron/nfc-service.ts @@ -87,20 +87,20 @@ export async function readNdefLnurlw( return null } - // Prefer the FileID advertised by the Capability Container (EF E103). - const candidates: Array<[number, number]> = [] + // NTAG424 Bolt Cards use NDEF FileID E104. Try it (and 0004) directly to + // minimise APDU round-trips over a flaky RF link; only fall back to reading + // the Capability Container to discover the id if both direct reads fail. + for (const fid of [[0xe1, 0x04] as [number, number], [0x00, 0x04] as [number, number]]) { + const found = await readNdefFile(send, fid) + if (found) return found + } if (swOk(await send([0x00, 0xa4, 0x00, 0x0c, 0x02, 0xe1, 0x03]))) { const cc = await send([0x00, 0xb0, 0x00, 0x00, 0x0f]) // CC layout: …[07]=TLV tag 0x04, [08]=len, [09..10]=NDEF FileID. - if (swOk(cc) && cc.length >= 13 && cc[7] === 0x04) candidates.push([cc[9], cc[10]]) - } - for (const fid of [[0xe1, 0x04] as [number, number], [0x00, 0x04] as [number, number]]) { - if (!candidates.some((c) => c[0] === fid[0] && c[1] === fid[1])) candidates.push(fid) - } - - for (const fid of candidates) { - const found = await readNdefFile(send, fid) - if (found) return found + if (swOk(cc) && cc.length >= 13 && cc[7] === 0x04) { + const found = await readNdefFile(send, [cc[9], cc[10]]) + if (found) return found + } } return null } @@ -157,12 +157,19 @@ export async function startNfcReader( r.on('card', async () => { 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. 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' }) + if (lnurlw) { + onCard(lnurlw) + } else { + onStatus({ state: 'error', reader: name, message: 'not a Bolt Card' }) + } } catch (e) { - onStatus({ state: 'error', reader: name, message: errMsg(e) }) + onStatus({ state: 'error', reader: name, message: 'card read failed — hold steady & retap' }) + void e } }) r.on('card.off', () => onStatus({ state: 'card-removed', reader: name }))