Skip to content

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.

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
  1. Pairing. On the computer running the agent, Settings › Printers talks to the agent on http://127.0.0.1:41780. The browser registers a PrintAgent with the server, receives the agent's token once, and hands it to the agent. The server keeps only the token's SHA-256.
  2. 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.
  3. Printing. A click on Print creates a PrintJob, renders the PDF on the queue, stores it on the private disk and broadcasts print-job.created on the agent's private channel. The agent downloads the file over HTTPS, prints it and reports received, then printed or failed. The file is deleted on a terminal status; the job row stays as the audit trail shown on the settings page.
  4. 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 is printing.delivery_window_hours.

Where things live in this application ​

ConcernFiles
Modelsapp/Models/PrintAgent.php, app/Models/PrintJob.php, print_settings on Shop
Enumsapp/Entities/PrintJobStatus.php, app/Entities/PrintDocumentType.php
Shop defaultsapp/ValueObjects/PrintSettings.php, app/ValueObjects/PrintTarget.php, app/Casts/PrintSettingsCast.php
Agent APIroutes/printAgent.php, app/Http/Middleware/AuthenticatePrintAgent.php, app/Actions/PrintAgent/*
Rendering and dispatchapp/Actions/Printing/* (CreatePrintJob, RenderPrintJob, RenderWorkOrderPdf, RenderTestPagePdf). RenderWorkOrderPdf resolves the shop's chosen layout and the customer's locale — see Documents
Broadcast eventsapp/Events/PrintJobCreated.php, PrintAgentRevoked.php, PrintAgentsChanged.php, PrintJobUpdated.php
Settings screenapp/Actions/Settings/*Print*.php, resources/js/Pages/Shop/Settings/Printer/Index.vue, resources/js/Hooks/useLocalPrintAgent.ts
Print menuapp/Actions/WorkOrder/PrintWorkOrder.php, resources/js/Elements/PrintMenu.vue
Configurationconfig/printing.php
Teststests/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 in bootstrap/app.php under the print-agent middleware group (throttle, token auth, route bindings). No CSRF, no Inertia, no cookies. The middleware makes the agent's shop current, so Shop::current() and the HasShop route bindings behave as they do for a signed-in user.
  • Channel authorization is narrow. POST /print-agent/broadcasting/auth only ever authorizes private-print-agents.{uuid} for the agent that owns it. A leaked token cannot subscribe to the shop channel. No Broadcast::channel callback 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 failed with the message rather than retrying, because a user is watching the status badge.
  • Realtime for humans is a signal. PrintAgentsChanged and PrintJobUpdated go on the shop.{id} channel; pages reload the props they care about. PrintJobUpdated carries the job uuid and status so the print menu can toast the outcome for the jobs it sent.