diff --git a/apps/machine/.env.example b/apps/machine/.env.example index 8748c21..3230ecb 100644 --- a/apps/machine/.env.example +++ b/apps/machine/.env.example @@ -73,18 +73,6 @@ VITE_SPIRE_SEED= # Show "Under Service" screen and block all transactions # VITE_MAINTENANCE_MODE=true -# ============================================================================= -# Public Web Demo -# ============================================================================= - -# Set ONLY for the browser demo build (atm.demo.aiolabs.dev). Leave blank on -# every real machine. When set it: -# - keeps the mouse cursor visible (kiosk builds hide it) -# - mints one extra, never-used LNbits wallet named with this exact string, -# so the throwaway accounts the demo creates (one per page load, each with -# its own ephemeral identity) can be swept by name instead of guessed at. -# VITE_DEMO_TAG=bitspire-web-demo - # ============================================================================= # Mock Fallback (Production Safety) # ============================================================================= diff --git a/apps/machine/electron/nfc-service.ts b/apps/machine/electron/nfc-service.ts index e94ddaf..ca9ace1 100644 --- a/apps/machine/electron/nfc-service.ts +++ b/apps/machine/electron/nfc-service.ts @@ -13,8 +13,6 @@ * QR path keeps working — cash-out never depends on this. */ -import { execFile } from 'node:child_process' - export type NfcState = 'ready' | 'reading' | 'error' | 'card-removed' | 'unavailable' export interface NfcStatus { state: NfcState @@ -85,11 +83,7 @@ export async function readNdefLnurlw( const send = (bytes: number[]) => transmit(Buffer.from(bytes), 256) // Select the NDEF Tag Application (AID D2760000850101). - if ( - !swOk( - await send([0x00, 0xa4, 0x04, 0x00, 0x07, 0xd2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00]) - ) - ) { + if (!swOk(await send([0x00, 0xa4, 0x04, 0x00, 0x07, 0xd2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00]))) { return null } @@ -113,29 +107,6 @@ export async function readNdefLnurlw( let stopFn: (() => void) | null = null -// ── Wedge auto-recovery ─────────────────────────────────────────────────── -// Cheap CCID readers (the Feitian R502-CL especially) occasionally wedge: they -// keep detecting a card but every APDU returns "card absent or mute", and ONLY -// a USB power-cycle clears it — pcscd/app restarts do NOT. When we see a run of -// consecutive read failures we trigger nfc-reader-reset.service (a root oneshot -// that re-binds the reader's USB device = a software replug); nfc-pcsc then -// re-detects the reader on hotplug with no app restart. The trigger is gated by -// a cooldown so a still-wedged reader can't reset-loop. A quality reader (e.g. -// ACR1252U) wedges far less; this is belt-and-suspenders for any reader. -const WEDGE_FAILURE_THRESHOLD = 3 -const RESET_COOLDOWN_MS = 30_000 -// Persist across reader re-enumerations (a reset spawns a fresh reader closure). -let lastReaderResetAt = 0 - -/** Trigger the privileged USB power-cycle of the reader. Best-effort. */ -function resetWedgedReader(): void { - // NixOS: the app runs unprivileged as `bitspire`; a polkit rule authorises it - // to start this one unit. systemctl lives at a stable path on the device. - execFile('/run/current-system/sw/bin/systemctl', ['start', 'nfc-reader-reset.service'], () => { - /* best-effort — if it fails the reader stays wedged until a manual reset */ - }) -} - /** * Start listening for Bolt Card taps. Idempotent. Returns a stop function. * Never throws — failures surface via onStatus. @@ -188,10 +159,6 @@ export async function startNfcReader( // 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 - // Consecutive failed reads → wedge detection (see resetWedgedReader above). - // A completed read (Bolt Card or not) proves the reader is healthy and - // clears the count; only a run of thrown transmits trips the reset. - let consecutiveFailures = 0 r.on('card', async () => { if (Date.now() < cooldownUntil) return onStatus({ state: 'reading', reader: name }) @@ -200,30 +167,13 @@ export async function startNfcReader( // user simply re-taps. try { const lnurlw = await readNdefLnurlw((apdu, maxLen) => r.transmit(apdu, maxLen)) - consecutiveFailures = 0 if (lnurlw) { onCard(lnurlw) return } onStatus({ state: 'error', reader: name, message: 'not a Bolt Card' }) } catch (e) { - consecutiveFailures++ - if ( - consecutiveFailures >= WEDGE_FAILURE_THRESHOLD && - Date.now() - lastReaderResetAt > RESET_COOLDOWN_MS - ) { - // Reader looks wedged — auto power-cycle it (only fix that works). - lastReaderResetAt = Date.now() - consecutiveFailures = 0 - onStatus({ state: 'error', reader: name, message: 'reader stuck — auto-resetting…' }) - resetWedgedReader() - } else { - onStatus({ - state: 'error', - reader: name, - message: 'card read failed — hold steady & retap', - }) - } + onStatus({ state: 'error', reader: name, message: 'card read failed — hold steady & retap' }) void e } cooldownUntil = Date.now() + 1500 @@ -232,9 +182,7 @@ export async function startNfcReader( r.on('error', (err: unknown) => onStatus({ state: 'error', reader: name, message: errMsg(err) }) ) - r.on('end', () => - onStatus({ state: 'unavailable', reader: name, message: 'reader disconnected' }) - ) + r.on('end', () => onStatus({ state: 'unavailable', reader: name, message: 'reader disconnected' })) }) nfc.on('error', (err: unknown) => onStatus({ state: 'error', message: errMsg(err) })) diff --git a/apps/machine/index.html b/apps/machine/index.html index 7b3a983..cc57859 100644 --- a/apps/machine/index.html +++ b/apps/machine/index.html @@ -2,7 +2,7 @@
- +