The Problem

Moving a file between two devices without cloud storage or a network connection is awkward. USB cables require drivers, Bluetooth pairing is slow, and cloud uploads expose data to a third party. QRFerry solves this with an air-gapped optical link: one screen displays an animated QR stream, a phone camera reads it, and the file transfers entirely in-browser.

What This Does

QRFerry is a Next.js app that encodes file data into a sequence of QR codes and decodes them on the receiving end. The sender runs at /, the receiver at /scan. The core protocol lives in lib/qr-transfer.ts (37 functions) and lib/optical-transfer.ts (10 functions); both handle framing, CRC-32 checksums, and RaptorQ fountain encoding for loss-tolerant reconstruction.

The pipeline compresses with Brotli/gzip (lib/compression.ts), renders QR codes via WebAssembly (lib/qr-renderer.ts), and scans with ZXing-C++ WebAssembly (lib/qr-scanner.ts). The receiver verifies four separate checksums before saving, and the file never touches a server.

How It Is Wired

Execution starts at app/page.tsx (sender) or app/scan/page.tsx (receiver). The sender path flows through app/send-client.tsxlib/optical-transfer.tslib/compression.tslib/qr-renderer.ts. The receiver path goes app/scan/scanner-client.tsxlib/qr-scanner.tslib/optical-transfer.tslib/compression.ts.

The highest-risk function is crc32 in lib/qr-transfer.ts, called from 11 places—change it and the entire integrity chain breaks. buildOpticalContainer and parseOpticalContainer in lib/optical-transfer.ts are the framing hub, each called from 2 files. The module graph shows no circular dependencies (0 of 29 modules), which keeps refactoring straightforward.

The only outbound network call is in worker/index.ts (a Cloudflare Worker), which handles auth redirects. The database layer (db/index.ts, db/schema.ts) uses Drizzle but is optional—the core transfer path is fully client-side.

How To Use It

npm install
npm run dev

Requires Node.js >=22.13.0. No environment variables are needed for the core transfer. npm test runs the codec, corruption-rejection, and end-to-end checksum tests. npm run lint and npx tsc --noEmit provide additional checks.

Real-World Use

A field engineer needs to transfer a 10 MB config file to an air-gapped industrial controller. They open / on a laptop, select the file, and point the controller's phone camera at the screen using /scan. The phone reconstructs the file after enough RaptorQ symbols arrive, verifies all checksums, and saves it—no USB stick, no network.

Code Health & Issues

Static analysis found 3 medium findings across 29 analyzed files:

  • Medium - Deep nesting in app/send-client.tsx: max indentation depth 6. Flatten with guard clauses.
  • Medium - Oversized files: app/scan/scanner-client.tsx and lib/qr-transfer.ts (829 lines each). Split by responsibility.

SDLC gaps from the file structure:

  • High - No LICENSE at repo root; default is all-rights-reserved, blocking reuse.
  • High - No CI pipeline; 28 source files merge with no automated build/test gate.
  • Medium - No Dependabot/Renovate; advisories sit unpatched.
  • Low - Missing .editorconfig, .gitattributes, formatter config; cross-contributor inconsistency.

Tests exist (tests/codec.test.ts, tests/rendered-html.test.mjs) and cover corruption, erasures, and real renderer-scanner round trips.

The Bottom Line

QRFerry is a technically solid implementation of a niche but real problem—air-gapped file transfer with strong integrity guarantees. The RaptorQ + multi-checksum design is sound, and the test harness is genuinely thorough. The main risks are maintenance ones: no CI, no license, and two files that need splitting. Use it if you need verified optical transfer; fix the license and CI before production.