fix(access): session Complete failed on IPC clone; keep rate lookup off the unlock path #97

Merged
padreug merged 2 commits from fix/boltcard-session-ipc-clone into dev 2026-09-22 14:02:47 +00:00
Showing only changes of commit 42e3657fe1 - Show all commits

fix(access): pass plain objects over IPC for session Complete

At Complete the store handed the session's withdraw/pay step to
window.electronAPI straight out of the loadedBoltCard ref — a Vue
reactive proxy — and Electron's structured clone refused it:
'[ATM] Bolt Card withdraw failed: Error: An object could not be cloned.'
(sintra, 2026-09-21 06:51). The customer had to re-tap, which works
because the direct-tap path passes a plain string. Copy the steps field
by field into plain objects before they cross the bridge.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Padreug 2026-09-22 14:24:13 +02:00

View file

@ -306,6 +306,22 @@ export const useAtmStore = defineStore('atm', () => {
// screen (raw lnurlw, spent by this call) or the session opened at entry
// (hit-keyed steps, no p/c).
type BoltCardSource = { lnurlw: string } | { session: CardSession }
// Electron IPC structured-clones its arguments and rejects Vue reactive
// proxies with "An object could not be cloned". `loadedBoltCard` is a ref,
// so anything reached through it is a proxy — copy the steps field by field
// into plain objects before they cross the bridge.
const plainWithdrawStep = (w: NonNullable<CardSession['withdraw']>) => ({
callback: w.callback,
k1: w.k1,
minWithdrawable: w.minWithdrawable,
maxWithdrawable: w.maxWithdrawable,
})
const plainPayStep = (p: CardSession['pay']) => ({
callback: p.callback,
minSendable: p.minSendable,
maxSendable: p.maxSendable,
metadata: p.metadata,
})
// Access-control gate config (ADR-003). Defaults disabled → the machine's
// `locked` state bypasses straight to `idle` (behaviour identical to no gate).
// Populated from RuntimeConfig.accessControl in initializeForProduction.
@ -673,7 +689,7 @@ export const useAtmStore = defineStore('atm', () => {
'session' in source
? source.session.withdraw
? await api.withdrawWithSession({
withdraw: source.session.withdraw,
withdraw: plainWithdrawStep(source.session.withdraw),
bolt11: invoice,
amountMsat,
})
@ -713,7 +729,7 @@ export const useAtmStore = defineStore('atm', () => {
const api = window.electronAPI!
const res =
'session' in source
? await api.resolveSessionInvoice({ pay: source.session.pay, amountMsat })
? await api.resolveSessionInvoice({ pay: plainPayStep(source.session.pay), amountMsat })
: await api.resolveCardInvoice({ lnurlw: source.lnurlw, amountMsat })
if (!res.ok || !res.bolt11) {
boltCardProcessing.value = false