Skip to content

fix(printer): surface real print errors and fix WebUSB connectivity - #535

Merged
itsbkm merged 8 commits into
mainfrom
fix/printer-connectivity-errors
Aug 27, 2026
Merged

fix(printer): surface real print errors and fix WebUSB connectivity#535
itsbkm merged 8 commits into
mainfrom
fix/printer-connectivity-errors

Conversation

@itsbkm

@itsbkm itsbkm commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the two most-reported printer complaints (generic "Print failed" / "Printer is not connected" errors) and the manual-add name-mismatch bug, per investigation in #532, #533, #534.

Test plan

  • npm run lint — 0 errors (pre-existing warnings only)
  • npm run build — backend TypeScript compiles clean
  • npm run build:frontend — Next.js build + type-check clean
  • npm run i18n:check — translation parity across en/es/pt/fa passes
  • npm run test:printer, test:print-parity, test:issue-134-routing, test:printer-migrations, test:printer-width-refresh, test:printer-fallback-and-popup-verification — all pass
  • CI

🤖 Generated with Claude Code

Print failures on /print-bill and /print-kot discarded the backend's
already-computed diagnostic (offline, paper out, wrong queue name, etc.)
and always showed a generic message, so every failure looked identical
and undiagnosable (#532). The manual "add printer by name" flow also
rejected common OS printer-name characters and had no way to match the
exact OS queue name, guaranteeing dispatch failures for anyone who
couldn't find their printer via auto-detect (#533).

Separately, "Printer is not connected. Call connect() first." fired on
every app reload/relaunch because WebUSB never silently re-attached to
a previously-granted device, plus printTaxBill/printKot lacked the
browser-print fallback printBill already had (#534). The WebUSB device
picker also never worked in the packaged Electron app at all, since
Electron's main process requires explicit session-level USB permission
handlers that were never wired up.

Fixes #532, #533, #534.
@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown

Greptile Summary

The PR improves printer diagnostics, manual printer-name handling, browser fallback behavior, and WebUSB reconnection and authorization.

  • Propagates detailed backend printing failures to POS, orders, and settings interfaces.
  • Adds detected-printer name assistance and permits valid OS queue-name characters.
  • Reconnects previously granted WebUSB printers and coordinates reconnects with printing and manual connections.
  • Adds origin-scoped, user-mediated Electron USB permissions with safe persistent identities.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
main/usb-device-permissions.ts Adds trusted-origin, explicit per-device USB authorization and safely limits serial-less approvals to the running session.
frontend/src/lib/printer/PrinterService.ts Adds serialized device opening, silent reconnection, connection switching cleanup, and reconnect coordination.
frontend/src/hooks/usePrinter.ts Waits for startup reconnection before routing print jobs and provides browser fallback when no thermal transport is available.
main/index.ts Registers Electron USB permission handlers once against the stable application origin and shared session.
main/routes/printers.ts Returns detailed print failures and broadens printer-name validation while trimming accidental surrounding whitespace.
frontend/src/app/(dashboard)/settings/page.tsx Surfaces backend printer errors and adds detected-name autocomplete with exact-match guidance.

Sequence Diagram

sequenceDiagram
    participant UI as Printer UI
    participant PS as PrinterService
    participant USB as WebUSB
    participant Main as Electron permission handlers
    UI->>PS: Startup status sync
    PS->>USB: getDevices()
    USB->>Main: Check device permission
    Main-->>USB: Approve known device only
    USB-->>PS: Previously granted devices
    PS->>PS: Serialize openDevice()
    PS-->>UI: Connected status
    UI->>PS: Print
    PS->>PS: awaitPendingReconnect()
    alt Thermal transport connected
        PS->>USB: Transfer ESC/POS bytes
    else No thermal transport
        PS-->>UI: Browser print fallback
    end
Loading

Reviews (8): Last reviewed commit: "fix(printer): don't let serial-less USB ..." | Re-trigger Greptile

Comment thread main/usb-device-permissions.ts Outdated
Comment thread frontend/src/hooks/usePrinter.ts
…nding reconnect before printing

Addresses Greptile P1 findings on #535:

- registerUsbDevicePermissions previously auto-approved USB device
  access for any origin. Scope select-usb-device and
  setDevicePermissionHandler to the app's own served origin, so a
  compromised dependency or stray navigation can't silently get
  hardware access.

- The silent WebUSB reconnect kicked off at app startup is
  asynchronous, so a print triggered moments later (e.g. KOT
  auto-print on the very first order) could read isConnected as
  false and wrongly fall back to browser print. printBill,
  printTaxBill, and printKot now await any in-flight reconnect
  before checking connection state.
Comment thread main/usb-device-permissions.ts Outdated
…ccess

Addresses a follow-up Greptile P1 finding on #535: scoping USB grants
to the trusted origin (previous commit) still auto-approved every
matching device without any user-visible confirmation, unlike a real
browser's device picker. Electron has no built-in picker UI, so show
a native confirmation dialog naming the specific device the first
time it's offered, and only remember that device as approved (for the
rest of the running app session) once the user clicks Allow.
Comment thread main/index.ts Outdated
Comment thread frontend/src/lib/printer/PrinterService.ts Outdated
…econnect

Addresses two further Greptile P1 findings on #535:

- createWindow() can run more than once per app lifetime (renderer
  crash recovery, macOS 'activate'), but every window shares the same
  default session. Re-registering on each call was stacking duplicate
  select-usb-device listeners on that shared session, firing multiple
  confirmation dialogs for a single request. Register at most once.

- connect() (user-initiated) and tryReconnect() (silent, at startup)
  could run concurrently and both mutate the same device/interface
  state; a failure in one would run openDevice()'s disconnect()
  cleanup and tear down a connection the other had just established.
  Both now serialize through a single async lock so only one device
  acquisition is ever in flight at a time.
Comment thread frontend/src/lib/printer/PrinterService.ts Outdated
…ser activation

Addresses a further Greptile P1 finding on #535: wrapping the whole
of connect() in the connectLock meant a click on Connect while
tryReconnect() was still in flight would delay
navigator.usb.requestDevice() until that reconnect settled. WebUSB
requires requestDevice() to run within the click's transient
activation window, so any meaningful delay makes the browser reject
it with SecurityError instead of opening the picker.

requestDevice() now always runs immediately relative to the user's
click; only the openDevice() step that follows it (which is what
actually mutates shared device/interface state) goes through the
lock.
Comment thread frontend/src/lib/printer/PrinterService.ts Outdated
…picker cancel/error

Addresses a further Greptile P1 finding on #535: since
requestDevice() (per the previous fix) can now run concurrently with
an in-flight tryReconnect(), that reconnect can succeed and set
this.device while the picker is still open. Cancelling or failing the
picker afterward unconditionally set status to 'disconnected'/'error',
leaving this.device populated but isConnected reporting false —
prints would then wrongly fall back or report the printer as
disconnected despite a working connection. Both catch branches now
only override status when there isn't already a connected device.
Comment thread main/usb-device-permissions.ts Outdated
Comment thread frontend/src/lib/printer/PrinterService.ts
…rwriting an open device

Addresses two further Greptile P1 findings on #535:

- approvedDeviceIds was in-memory only, reset to empty on every app
  restart. setDevicePermissionHandler then rejected the previously
  approved printer during the silent startup getDevices() reconnect,
  so the user had to click Connect and approve again every launch —
  defeating the point of tryReconnect(). Approvals are now persisted
  to userData/usb-printer-approvals.json, keyed by the device's
  stable vendorId+productId+serialNumber identity (a deviceId isn't
  stable across sessions, so it can't be the persisted key).

- openDevice() unconditionally overwrote this.device without
  releasing the previous device's interface claim first. If
  tryReconnect() succeeded while the user's picker was still open and
  they picked a different device, the original device's USB claim
  leaked. openDevice() now disconnects any existing different device
  first, and short-circuits if the "new" device is actually the same
  one already open.
Comment thread main/usb-device-permissions.ts Outdated
…roval identity

Addresses a further Greptile P1 security finding on #535:
vendorId+productId alone is not a unique physical-device identity —
many printers share generic VID/PID pairs from the same USB-to-serial
chipset. Persisting approval under that bare pair meant a different
physical device sharing it could silently inherit another device's
standing trust after a restart, with no renewed confirmation.

Only devices that report a serial number now get a persisted,
cross-restart approval (keyed by vendorId+productId+serialNumber).
A device without a serial number gets a session-scoped-only approval
(keyed by Electron's own per-process deviceId) and must be
re-confirmed via the dialog after every restart — mirrors how
browsers themselves scope WebUSB's persisted-grant store.
@itsbkm
itsbkm merged commit bbfe7b2 into main Aug 27, 2026
14 checks passed
@itsbkm
itsbkm deleted the fix/printer-connectivity-errors branch August 27, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant