Skip to content

"Printer is not connected. Call connect() first." fires due to no silent WebUSB reconnect and inconsistent fallback guards #534

Description

@itsbkm

Bug description

"Printer is not connected. Call connect() first." is tied for the #1 printer-related support complaint. It's thrown by the frontend WebUSB printer path any time a print is attempted while no WebUSB device handle is held — which is true after every app relaunch or page reload, and in several code paths where it fires even when a fully-configured backend (network/USB) printer exists.

Root cause

Thrown at frontend/src/lib/printer/PrinterService.ts:193:

if (!this.device || this._status !== 'connected') {
  throw new Error('Printer is not connected. Call connect() first.');
}

There are three separate defects that each independently trigger this:

1. No silent reconnect on startup. connect() (PrinterService.ts:76-158) only calls navigator.usb.requestDevice(), which requires a live user gesture and shows a picker every time. Nothing calls the non-prompting navigator.usb.getDevices() to silently re-acquire a device the user already granted permission for in a previous session. So this.device is null after every relaunch/reload until the user manually clicks "Connect" again in PrinterStatus.tsx (frontend/src/components/pos/PrinterStatus.tsx:84-96, only rendered via PosTopbar.tsx:49).

2. Inconsistent fallback guards across print functions in frontend/src/hooks/usePrinter.ts:

Function Behavior when no backend printer / not connected
printBill (usePrinter.ts:107-216) Falls back to executeBrowserPrint() (:162-167) — does not throw.
printTaxBill (usePrinter.ts:218-281) No hardware-printer branch at all. Whenever printMethod !== 'browser', unconditionally calls printerService.print(bytes) (:275) — even on stores with a configured backend network/USB printer, because no /printers/print-tax-bill backend route exists.
printKot (usePrinter.ts:283-341) Has a backend-hardware branch (:296-305), but its fallthrough (:307-314) calls printerService.print(bytes) directly with no isConnected check and no browser fallback, unlike printBill.

So printTaxBill and printKot throw this error in exactly the cases printBill handles gracefully.

3. A population race for hardwarePrinter. It's loaded only by refreshHardwarePrinter() (usePrinter.ts:80-92), invoked from usePrinterStatusSync() (usePrinter.ts:390-411), which only mounts on the POS page (via PrinterStatus.tsx) and Settings. It's explicitly excluded from persisted state (usePrinter.ts:375), so it's always null on fresh load and filled in asynchronously via GET /printers. Since KOT auto-print fires immediately on order placement (frontend/src/app/(dashboard)/pos/page.tsx:256-264, 530, 732, gated by autoPrintKot), a fast first order right after app start can race ahead of that fetch and hit the unguarded branch in defect #2.

Combined, this explains the complaint volume: it fires for (a) any WebUSB-only shop that reloaded/relaunched without re-clicking Connect, (b) every printTaxBill call on shops using tax/GST invoices regardless of printer type, and (c) intermittent KOT auto-print failures right after startup.

Proposed fix

  1. In PrinterService.connect()/init, attempt a silent navigator.usb.getDevices() reconnect before falling back to the prompting requestDevice() flow, so a previously-granted device reattaches automatically on load.
  2. Add the same hw-check + browser-fallback pattern that printBill already uses (usePrinter.ts:162-167) to printTaxBill and the printKot fallthrough branch, including adding a /printers/print-tax-bill backend route so tax invoices route through the backend printer path like bills do.
  3. Hoist usePrinterStatusSync() to a layout-level provider (instead of only PrinterStatus.tsx) so hardwarePrinter is populated before the POS page can place an order, closing the startup race.

Environment

Affects WebUSB-configured printers on all platforms; also affects tax/GST-invoice printing on stores using backend network/USB printers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:frontendFrontend (React/Next.js)bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions