Appearance
Vroum Connector
Vroum Connector is the local print agent. It runs on a PC or Mac that is plugged into the shop's printers and lets any computer in the shop print invoices, estimates and work orders from the web app, on any connected printer.
The agent lives in its own repository, vinnygambiny/vroum-connector. This section documents both halves: what the agent does, and what this application does to drive it.
- Installation and daily use — for the people in the shop.
- Protocol — the localhost API, the server API and the WebSocket events.
- Releases and auto-update — building installers, signing, publishing.
- Troubleshooting — when nothing comes out of the printer.
How it works
Browser (any PC) ──HTTPS──▶ vroum-app ──WebSocket (Reverb)──▶ Vroum Connector ──lp / SumatraPDF──▶ Printer
│ ▲ │
└── localhost HTTP ──────┼───────────────────────────────────────┘
(pairing, status, │ HTTPS with the agent's bearer token:
printer list) │ heartbeat, download PDF, report status- Pairing. On the computer running the agent, Settings › Printers talks to the agent on
http://127.0.0.1:41780. The browser registers aPrintAgentwith the server, receives the agent's token once, and hands it to the agent. The server keeps only the token's SHA-256. - Heartbeat. Every five minutes, and whenever its printer list changes, the agent posts its printers and reports itself alive. The server answers with the Reverb connection details, so the agent never stores anything but the server URL and its token.
- Printing. A click on Print creates a
PrintJob, renders the PDF on the queue, stores it on the private disk and broadcastsprint-job.createdon the agent's private channel. The agent downloads the file over HTTPS, prints it and reportsreceived, thenprintedorfailed. The file is deleted on a terminal status; the job row stays as the audit trail shown on the settings page. - Offline agents. A job sent while the agent is offline stays
sent. On reconnect the agent fetches the jobs sent in the last 24 hours and prints them oldest first. The window isprinting.delivery_window_hours.
Where things live in this application
| Concern | Files |
|---|---|
| Models | app/Models/PrintAgent.php, app/Models/PrintJob.php, print_settings on Shop |
| Enums | app/Entities/PrintJobStatus.php, app/Entities/PrintDocumentType.php |
| Shop defaults | app/ValueObjects/PrintSettings.php, app/ValueObjects/PrintTarget.php, app/Casts/PrintSettingsCast.php |
| Agent API | routes/printAgent.php, app/Http/Middleware/AuthenticatePrintAgent.php, app/Actions/PrintAgent/* |
| Rendering and dispatch | app/Actions/Printing/* (CreatePrintJob, RenderPrintJob, RenderWorkOrderPdf, RenderTestPagePdf). RenderWorkOrderPdf resolves the shop's chosen layout and the customer's locale — see Documents |
| Broadcast events | app/Events/PrintJobCreated.php, PrintAgentRevoked.php, PrintAgentsChanged.php, PrintJobUpdated.php |
| Settings screen | app/Actions/Settings/*Print*.php, resources/js/Pages/Shop/Settings/Printer/Index.vue, resources/js/Hooks/useLocalPrintAgent.ts |
| Print menu | app/Actions/WorkOrder/PrintWorkOrder.php, resources/js/Elements/PrintMenu.vue |
| Configuration | config/printing.php |
| Tests | tests/Feature/PrintAgent/, tests/Feature/Settings/PrinterSettingsTest.php, tests/Feature/WorkOrder/PrintWorkOrderTest.php, tests/Unit/ValueObjects/PrintSettingsTest.php |
Design decisions worth knowing
- The PDF never travels over the WebSocket. Reverb frames are capped (
reverb.max_message_size) and a payload that overflows throws inside the writing transaction. The event carries the job id and printer; the file is fetched over HTTPS. - The agent authenticates with a bearer token, not a session. Its routes are registered through the
then:callback inbootstrap/app.phpunder theprint-agentmiddleware group (throttle, token auth, route bindings). No CSRF, no Inertia, no cookies. The middleware makes the agent's shop current, soShop::current()and theHasShoproute bindings behave as they do for a signed-in user. - Channel authorization is narrow.
POST /print-agent/broadcasting/authonly ever authorizesprivate-print-agents.{uuid}for the agent that owns it. A leaked token cannot subscribe to the shop channel. NoBroadcast::channelcallback exists for that channel, so a signed-in user cannot subscribe to it either. - Defaults are per document type on the shop, not on the agent. The question a user asks is "where do invoices print", and the answer can be a different computer for estimates, work orders and invoices.
- Rendering is queued. Chromium takes a second or two. A render that throws marks the job
failedwith the message rather than retrying, because a user is watching the status badge. - Realtime for humans is a signal.
PrintAgentsChangedandPrintJobUpdatedgo on theshop.{id}channel; pages reload the props they care about.PrintJobUpdatedcarries the job uuid and status so the print menu can toast the outcome for the jobs it sent.