How I built a PWA barcode scanner that pairs your phone to a desktop spreadsheet
The problem Small retail teams either buy a $100+ USB barcode scanner per workstation or type codes by hand. The scanners work, but they are tethered and priced per seat. Meanwhile everybody already carries a barcode reader in their pocket: a phone camera. So the question became: what if the phone is the scanner, and the desktop browser is the sheet? That became ScanSheet, a PWA I have been…
Small retail teams often purchase expensive USB barcode scanners for each workstation or manually input codes. These scanners are tethered and priced per seat, while most people already own barcode readers in the form of their phone cameras. This led to the creation of ScanSheet, a Progressive Web App (PWA) built during nights and weekends. The following sections detail the engineering story behind ScanSheet.
Architecture:
The desktop browser tab hosts a table, and the phone camera opens its own browser tab. A QR code containing a session token appears on the desktop, which the phone scans. Both devices then connect via a WebSocket relay. Every scan is streamed in real-time to the desktop table, and quantities are automatically aggregated. The sheet can export data locally to CSV/XLSX files. The relay is intentionally simple, never persisting scan payloads. Scan data is stored only in the two browsers (IndexedDB + localStorage).
Decoding:
ZXing, a browser-based library, is used for barcode decoding. The BarcodeMultiFormatReader class is instantiated, and then decodeFromVideoDevice is called with the necessary parameters. The result callback is used to send the scanned text to the server when a valid scan is detected.
The Relay:
A simple WebSocket is set up with the following structure:
1. Connection event listener
2. Message event listener for handling incoming data
3. Iterating over clients with the same session and sending the received data to them
Optimization:
To improve accuracy and performance on low-end Android devices, only specific barcode formats (Code128, EAN-13, QR) are enabled instead of allowing all formats. Debouncing is also implemented to ignore duplicates within a 1.5-second window, reducing redundant scans.
WebSocket and Data Persistence:
The relay is a straightforward WebSocket connection with no database involved. The phone queues scans in IndexedDB when disconnected and flushes them upon reconnection. This setup ensures the scanner never blocks, even during dead zones in a warehouse environment.
Service Worker Bug:
A recurring issue faced during development involved the Service Worker constantly serving the old version of scripts to returning visitors. The solution involved:
1. Serving the script with Cache-Control: no-store
2. Bumping the Service Worker's cache name to purge old caches
3. Adding a version query parameter to the script URL
Lessons Learned:
Cache-first strategies coupled with stable URLs can lead to outdated caches, causing issues in the application. It is crucial to version every mutable asset URL to prevent such problems.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.