Bookodi Desktop & Print Bridge
A hardened Windows/macOS shell that prints kitchen tickets from the cloud to printers on a restaurant’s private network.
- Role
- Sole engineer — design, build, tests, CI, docs
- Team
- Solo
- Timeline
- Sep 2026 – Ongoing
- Organisation
- Bookodi (XEOO)
- Code
- Proprietary (employer)
- Reading time
- 5 min
Native desktop app for the Bookodi restaurant platform with a built-in Print Bridge: it pairs with the cloud in one click, discovers printers on the local network and delivers server-rendered tickets over raw TCP, IPP, LPD, CUPS or the Windows spooler.
Overview
Bookodi renders every kitchen ticket on its servers — but a server in a data centre cannot reach a thermal printer sitting on a restaurant's Wi-Fi. Before this project, staff bridged the gap with a separate command-line agent started from PowerShell, and there was no macOS option at all.
Bookodi Desktop wraps the live web app in a hardened native shell and builds the bridge in: install, sign in, click Pair, and tickets print.
The problem
- Printing is server-side, but printers live on private networks the cloud can't reach.
- The existing bridge was a Windows-only CLI that non-technical staff had to start by hand.
- The desktop app had to behave exactly like the web app — same features, same speed — without any backend changes.
- Card payments had to stay on the website.
Context & constraints
The Go backend already spoke an agent protocol over WebSockets (discover and print commands). Changing the server was out of scope, so the desktop app had to implement that protocol exactly, byte for byte, and interoperate with the existing job queue and retry worker.
Approach
I built the app in Electron 44 with a strict security posture, then implemented the Print Bridge as a service inside the main process:
- Pairing — when the Printers page shows a new bridge key, the app offers a one-click pair; the key is encrypted at rest with the OS keychain facility on Windows and a mode-600 file on macOS.
- Connection — a WSS client to the agent hub with backoff from 2 to 60 seconds and a 90-second liveness timeout.
- Discovery — installed OS printers, a scan of up to four local /24 networks on ports 9100, 631 and 515, and SNMP to read model names.
- Delivery — five transports: raw TCP 9100, IPP 1.1, LPD (RFC 1179), the Windows spooler through FFI, and macOS CUPS. One job at a time per printer; different printers print in parallel.
Errors are written in plain language and include the computer's name, so a manager can tell which till needs attention.
Key capabilities
- One-click pairing with the cloud printer settings
- Automatic printer discovery: OS queues, LAN scan and SNMP model names
- Five delivery transports including first-ever macOS support
- Per-printer job queues with parallelism across printers
- Private-network guard: the bridge refuses public addresses
- Payments kept on the web — billing endpoints answered locally with a clear dialog
- Tray icon, background mode and start-at-login for unattended tills
- Crash and hang recovery, remembered window state, native menus and downloads
Architecture
Cloud
Desktop app
Restaurant network
The cloud renders tickets; the desktop app is a protocol-exact agent that relays them to whatever printers it can reach on the local network. Select a component to trace its connections.
Key decisions
ADR-01Implement the existing protocol exactly
- Context
- The backend already had an agent hub and a job retry worker; the web repository was off-limits.
- Decision
- Treat the old CLI agent as the specification and reproduce its behaviour precisely, including edge cases.
- Trade-offs
- No new server features — but zero backend risk, and old and new agents stay interchangeable.
ADR-02Refuse to connect to public addresses
- Context
- Printer addresses arrive from the server. A compromised or buggy server could point the bridge at arbitrary hosts.
- Decision
- A network guard only allows private, link-local and loopback addresses for any outbound printer connection.
- Trade-offs
- A printer reachable only through a public IP cannot be used — an acceptable limit for in-restaurant hardware.
ADR-03Keep payments on the website
- Context
- Selling subscriptions inside desktop apps brings signing, store and compliance overhead.
- Decision
- The preload layer answers the checkout and billing-portal endpoints with a 403 and an explanatory dialog, and Stripe domains are blocked.
- Trade-offs
- Owners upgrade in a browser, but the desktop app ships without payment-related review.
ADR-04Disable background throttling
- Context
- Chromium throttles timers in hidden windows, which slowed the web app’s order polling when the till was minimised.
- Decision
- Turn off background throttling for the main window.
- Trade-offs
- Slightly higher idle CPU in exchange for orders arriving on time.
Challenges
Testing printing in CI
Problem. Printing bugs only show up with real print queues, and CI runners have no printers.
Solution. CI provisions a file-backed printer on Windows and a CUPS queue on macOS, then runs an end-to-end test that prints through the system queue and checks the output.
LPD on Windows
Problem. The Windows LPD service acknowledges commands differently from the RFC, breaking strict clients.
Solution. The LPD transport tolerates the Windows acknowledgement while staying RFC 1179-compliant for other servers.
Printing from macOS
Problem. The previous agent had no macOS path at all.
Solution. A CUPS transport sends raw jobs with `lp -o raw`, so ESC/POS bytes reach thermal printers untouched.
Quality & delivery
Testing
- 115 unit tests across 16 files (Vitest) cover the protocol, queues, transports, discovery and the network guard.
- 21 end-to-end tests (Playwright for Electron): 9 for the shell, 7 for the Print Bridge and payment blocking, one CI-only test that prints through a real system queue, and 4 against a local copy of the web app.
- Installer smoke tests in CI: a silent
.exeinstall on Windows and a launch from the.dmgon macOS.
Deployment
GitHub Actions runs type-checking, linting, formatting and unit tests on Ubuntu, end-to-end tests on macOS and Windows, and builds both installers. Pushing a version tag drafts a release. Code signing and an update feed are the next phase.
Outcome
- Unit tests
- 115
- 16 files, Vitest
- End-to-end tests
- 21
- Shell, Print Bridge, payments
- Delivery transports
- 5
- Raw TCP, IPP, LPD, spooler, CUPS
- To first window
- ≈0.33 s
- Measured on a development Mac
Version 1.1.0 ships as a Windows installer and a universal macOS disk image. In local end-to-end runs against the real backend, a receipt printed byte-for-byte and all 20 dashboard pages rendered without JavaScript errors. The release is in its Windows and macOS test round before code signing.
Lessons
- An existing implementation is the best specification — reading the old agent saved days of guessing.
- Hardware integration needs hardware in CI, even if it is a file pretending to be a printer.
- Security boundaries belong in the client too: the network guard turned a server-trust problem into a non-issue.
Building something similar?
Tell me what you’re working on — I reply to every serious message.
Related work
Bookodi — Restaurant Operating System
Bookodi handles reservations, POS, kitchen display, menus, guest CRM, delivery, printing and billing for many restaurants from one Go API. Built by the XEOO team; I owned the analytics module, the reservation views and the dashboard home.
- TypeScript
- Go
- React
- Tailwind CSS
- Vite
- PWA & service workers
- +6