QR & Barcode Scanner — qr.codemonkey.ro
▶ Live app: qr.codemonkey.ro — open it on your phone and add it to your home screen.
A privacy-first QR and barcode scanner that runs entirely in your browser.
Most barcode-scanner apps on the Android store are ad-infested, over-permissioned, or outright malicious — and almost all of them wrap the same handful of decoding libraries. This is the boring, honest alternative: a static web app that scans codes with your camera, keeps everything on your device, and asks for nothing it doesn't need.
- No backend. Pure static HTML + CSS + JS, deployed on Cloudflare Workers (static assets).
- No accounts, no servers, no analytics, no tracking. Your scans never leave the browser unless you export them yourself.
- Installable (PWA). Add to your home screen and it works offline — without being a "native app".
- Many symbologies. QR, Aztec, Data Matrix, PDF417 (boarding passes), EAN/UPC, Code 128/39/93, Codabar, ITF.
- Continuous camera scanning with a configurable re-scan cooldown (default 2s).
- Capture button: decodes a full-resolution still for dense codes (boarding-pass PDF417, Aztec) that the live preview can't resolve.
- Tap-to-focus, torch toggle, and a camera picker that defaults to the main autofocus rear lens (see why this matters).
- Scan feedback: vibration + an optional short sound + an on-screen toast.
- Local, timestamped history of every scan; add notes, delete entries.
- Understands structured codes — Wi-Fi, contacts (save as
.vcf), locations, calendar events (save as.ics), email/phone/SMS, boarding passes (IATA BCBP), SEPA (EPC) and Swiss QR-bill payments, and 2FA/OTP setup — each shown as a readable card with the right action. Sensitive fields (Wi-Fi password, 2FA secret) are masked. - Content-aware actions — links are shown in full and never opened automatically; generic URLs are deliberately kept as plain text (no risky auto-handling).
- Optional GPS location capture per scan (off by default).
- Optional camera frame capture per scan (off by default).
- Export everything to JSON (images inlined as data URLs; self-contained).
A hybrid engine, chosen at runtime:
- The browser's native
BarcodeDetectorAPI when available (Android Chrome — the primary target). Fast, hardware-accelerated, zero download. - ZXing (pure JS, lazy-loaded) as a fallback for browsers without the native API.
The native detector is weak on dense codes, so the Capture path always decodes the still with ZXing, trying multiple rotations (a boarding pass held in landscape is rotated 90°). ZXing is only downloaded when it's actually needed.
See docs/DECISIONS.md for the reasoning behind these choices and docs/ROADMAP.md for what's planned.
npm install
npm run dev # http://localhost:5173 (camera works on localhost)
npm run build # type-check + production build to dist/
npm run preview # serve the production build locallyCamera + GPS need a secure context (HTTPS).
localhostcounts as secure, so desktop dev works. To test on your phone, plain LAN HTTP will not grant the camera — deploy to the live site, or expose dev over HTTPS with a tunnel (e.g.cloudflared tunnel --url http://localhost:5173).
The repo is connected to Cloudflare's Git builds. It deploys as a Workers
static-assets project (not Pages — Cloudflare's "import a repo" flow now creates
Workers builds), configured by wrangler.jsonc:
- Build command:
npm run build→ outputdist/ - Deploy command:
npx wrangler deploy(servesdistas static assets, SPA fallback) - Node pinned to 22 via
.nvmrc; Vite must be ≥ 6 (Cloudflare's build auto-config rejects older Vite) — currently on Vite 8.
Every push to main auto-deploys; qr.codemonkey.ro is bound as a custom domain.
HTTPS is automatic. Security/permissions headers live in public/_headers.
See DECISIONS.md → D9
for the full story (it was not smooth).
Preact + Vite + TypeScript, IndexedDB (idb) for storage, vite-plugin-pwa for the
service worker and installability. Tiny dependency surface on purpose — it's easier
to trust and to audit.
Dependencies are pinned to exact versions (no ^), with a committed lockfile and
.npmrc save-exact=true, so builds are fully reproducible. Dependabot
opens weekly PRs for updates — opt-in and reviewed, never auto-merged.