Offline file transfer via QR codes. One single HTML file — no server, no cloud, no tracking.
- Open
index.htmlon both devices (sender and receiver) - Sender: Select a file → QR codes are generated and displayed in sequence
- Receiver: Start camera → point at sender's screen → chunks are scanned automatically
- When all chunks are received, the file is assembled with CRC32 integrity check and offered for download
- Single HTML file — everything bundled, works offline
- No server required — runs entirely in the browser
- CRC32 integrity check — detects corrupted transfers
- Gzip compression — reduces transfer size
- Configurable chunk size and speed — tune for your setup
- Visual chunk grid — see transfer progress in real-time
- Drag & drop file selection
- Responsive — works on desktop and mobile
- Online: Open the live demo on both devices
- Offline: Download
index.htmland open it in any modern browser
Default settings: 300 B chunk size, 100 ms speed.
npm install
npm run dev # Start dev server
npm test # Run unit tests
npm run build # Build single HTML file to dist/Files are compressed (gzip level 9), base64-encoded, and split into chunks. Each chunk becomes a QR code containing a JSON packet:
{
"v": 1, // protocol version
"i": 0, // chunk index
"t": 50, // total chunks
"n": "file.pdf", // filename (chunk 0 only)
"s": 123456, // file size in bytes (chunk 0 only)
"h": "a1b2c3d4", // CRC32 hex of original file (chunk 0 only)
"d": "base64..." // chunk data
}The receiver collects chunks in any order, reassembles, decompresses, and verifies the CRC32 checksum.
Smart Transfer creates a bidirectional channel where the receiver tells the sender which chunks are still missing via audio tones (like a modem). The sender then skips already-received chunks, dramatically speeding up the transfer.
- Data flows via QR codes (sender screen → receiver camera) — fast, visual
- Feedback flows via audio FSK tones (receiver speaker → sender microphone) — omnidirectional, no line-of-sight needed
Device A (Smart Send) Device B (Smart Receive)
┌──────────────────┐ ┌──────────────────┐
│ │ │ │
│ [DATA QR] │ ── eye ──► │ [CAMERA] │
│ chunks 3,7,12 │ │ scanning QR │
│ │ │ │
│ [MICROPHONE] │ ◄─ ear ── │ [SPEAKER] │
│ listening │ audio │ FSK tones │
│ │ │ "got 1,2,4,5" │
└──────────────────┘ └──────────────────┘
The phone can lie flat on a table — audio works in all directions, no camera positioning needed.
The feedback is encoded as FSK (Frequency Shift Keying) audio:
- 1200 Hz = bit "0", 2400 Hz = bit "1" (Bell 202 standard)
- 300 baud — each bit lasts ~3.3ms
- Frame format:
[preamble 0xAA 0xAA] [sync 0xD5] [length] [bitfield] [CRC-8] - Bitfield: 1 bit per chunk (received=1, missing=0), raw bytes
- CRC-8/CCITT: integrity check, invalid frames are silently discarded
For 100 chunks: 13 bytes bitfield → 144 bits total → 0.48 seconds per feedback cycle.
The receiver plays the feedback audio in a continuous loop. The sender's microphone picks it up, decodes via Goertzel algorithm, and adapts its QR playlist to only show missing chunks.
| Size | Transfer time (est.) | Experience |
|---|---|---|
| < 10 KB | seconds | Instant |
| 10-100 KB | 1-5 min | Comfortable |
| 100 KB - 1 MB | 5-30 min | Feasible |
| > 1 MB | 30+ min | Use only as last resort |
- TypeScript
- Vite + vite-plugin-singlefile
- qrcode — QR generation
- jsQR — QR scanning
- pako — gzip compression
- Vitest — unit testing
MIT