feat(machine): connectivity auto-recovery + on-screen Retry
A connectivity-type init failure (e.g. "No connected relays" when the box boots before the network) landed on "ATM Unavailable" permanently: init is one-shot and the nostr reconnect only helps after a first successful connect, so a machine never self-healed when internet returned. Recover by reloading the renderer, which re-runs init from a clean JS context (no leaked actors/subscriptions) while the main process keeps HAL: - main.ts: new `app:recover` IPC → reloadRenderer() (resets secretsConsumed). - hal:init is now idempotent (reuse the existing instance) so the reload — and the pre-existing watchdog crash-reload — can't double-open serial ports. - App.vue: when initError is a connectivity type (not the operator/ self-clearing states unpaired/awaiting-fees/maintenance), watch for the `online` event (recover immediately) plus a 45s backoff safety net, and render a kiosk-sized Retry button for a person at the machine. Preserves pairing + /var/lib state (renderer reload, not a process restart). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
44f5c0dbcf
commit
6a833d357e
4 changed files with 104 additions and 1 deletions
|
|
@ -404,6 +404,17 @@ ipcMain.handle('app:relaunch', (): void => {
|
|||
app.exit(0)
|
||||
})
|
||||
|
||||
// Connectivity recovery: reload the renderer to re-run init from a clean slate
|
||||
// (fresh JS context → no leaked actors/subscriptions), while preserving HAL in
|
||||
// this main process (reloadRenderer resets secretsConsumed so get-atm-secrets
|
||||
// works again, and hal:init is idempotent). The renderer calls this when it's
|
||||
// stuck on a connectivity-type "ATM Unavailable" and the network returns, or
|
||||
// when the operator taps the on-screen Retry (ADR-002 amendment 2026-08-04).
|
||||
ipcMain.handle('app:recover', (): void => {
|
||||
console.log('[Recovery] Reloading renderer to re-attempt initialization')
|
||||
reloadRenderer()
|
||||
})
|
||||
|
||||
// State persistence IPC handlers
|
||||
ipcMain.handle('state:load-cassettes', () => loadCassettes())
|
||||
ipcMain.handle('state:set-cassettes', (_event, cassettes) => setCassettes(cassettes))
|
||||
|
|
@ -480,6 +491,17 @@ let pendingBillDenomination: number | null = null
|
|||
|
||||
ipcMain.handle('hal:init', async (_event, config) => {
|
||||
try {
|
||||
// Idempotent: HAL lives in this (long-lived) main process, but the renderer
|
||||
// re-runs full init on every reload — the watchdog's crash-recovery reload
|
||||
// and the connectivity-recovery reload (app:recover) both re-invoke this.
|
||||
// initializeHal opens serial ports without closing prior handles, so
|
||||
// re-entering it would double-open the validator/dispenser. Reuse the
|
||||
// existing instance instead; its validator event wiring already targets the
|
||||
// (reloaded) mainWindow, so the reloaded renderer keeps receiving bill events.
|
||||
if (halInstance) {
|
||||
console.log('[Electron] HAL already initialized — reusing existing instance')
|
||||
return { success: true }
|
||||
}
|
||||
// Override cassette config with DB values (operator may have changed them via atm-tui
|
||||
// or via an operator-config publish from satmachineadmin). Pass per-position so the
|
||||
// HAL knows about every bay including duplicates of the same denomination — real
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue