Offline file transfer and QR code generation. Share files between devices using animated QR codes with no internet required.
Demo: qrsecure.kuvi.dev
- Generate QR codes from text or URLs
- Transfer files using QR codes only - no internet, servers, or third-party services
- Fountain codes (LT codes) for robust large file transfers with error correction
- Sequential QR mode for simpler transfers
- CRC32 checksums for data integrity verification
- Camera-based scanning with rxing-reader WASM
- Image upload support
- Frontend: React 19 + TypeScript + Vite
- Styling: Tailwind CSS + Radix UI
- QR Scanning:
@andrewtheguy/rxing-wasmfrom rxing-reader - QR Generation:
@andrewtheguy/fast-qr-wasmfrom fast_qr - Binary QR Transport: Fountain transfer worker path only (raw byte mode)
- Fountain Codes: Custom Rust/WASM implementation (architecture docs)
- Bun (the version in
packageManagerinpackage.json) - rustup;
wasm/fountain/rust-toolchain.tomlpins the toolchain and thewasm32-unknown-unknowntarget, andwasm-packcomes from the devDependencies
# Clone and install
git clone <repository-url>
cd qrcodesecureshare
bun install
# Build the fountain WASM module (once, and after every change under wasm/)
bun run build:wasm
# Development
bun run dev
# Production build (builds the WASM module first)
bun run build- Go to "Generate QR Code" tab
- Enter text or URL
- Download or share the QR code
- Go to "Scan QR" tab
- Use camera or upload an image
- Scanned data is displayed and can be copied
- Go to "Send File" tab
- Select a file to transfer
- Choose transfer mode (Fountain Code recommended for large files)
- Display animated QR codes for receiver to scan
- Go to "Send File" > "Receive a file"
- Scan the metadata QR code from sender
- Continue scanning animated QR codes until transfer completes
- Download the reconstructed file
src/
├── components/
│ ├── GenerateQR.tsx # Text/URL QR generator
│ ├── Scan.tsx # QR scanner (camera/upload)
│ ├── OfflineTransfer.tsx # File transfer workflow
│ ├── fountain_qr/ # Fountain code transfer UI
│ └── ui/ # Shared UI components (shadcn)
├── utils/ # WASM bindings and helpers
└── workers/ # Web Workers (QR scanning, decoding)
wasm/
└── fountain/ # LT fountain codes implementation (Rust → WASM)
- QR decoding runs in a Web Worker that reads one camera frame at a time and skips to the newest frame when it frees up.
- The file-transfer receiver offers every new camera frame; the other scanners read at most 8 to 10 frames per second.
- Phones ask the camera for a lower resolution while receiving a file.
Planned work is in docs/roadmap.md.
- Offline by design: No servers or internet required for file transfers
- Local processing: All QR operations happen in-browser via WebWorkers
- No intermediaries: Data stays on participating devices only
- Integrity checks: CRC32 checksums verify transfer completeness
# Run development server
bun run dev
# Rust tests and clippy for wasm/fountain
bun run test
bun run clippy
# Lint (Biome) and type-check
bun run lint
bunx tsc -b
# Format and apply safe fixes
bun run format
# Build WASM only
bun run build:wasmThe QR reader WASM is built and published from
andrewtheguy/rxing-reader; this
app consumes it as the pinned package @andrewtheguy/rxing-wasm. The QR
generator WASM is built and published from
andrewtheguy/fast_qr; this app
consumes it as the pinned package @andrewtheguy/fast-qr-wasm. Both are
pulled in as GitHub release tarball URLs declared in package.json; to move to
a new release, run bun add <release tarball URL>. bun run build:wasm builds
only the in-repo wasm/fountain crate.
# Build for production
bun run build
# Preview build locally
bun run preview
# Deploy dist/ folder to any static hosting- Build command
bun run build:cloud, output directorydist, root directory/. SetBUN_VERSIONto the version inpackageManagerinpackage.json. cloud_build.shinstalls rustup with no default toolchain, runsrustup toolchain installinwasm/fountainto get the toolchain and target itsrust-toolchain.tomlpins, then runsbun run build.wasm-packdownloads its binary in a postinstall script, which Bun runs only becausepackage.jsonlists it intrustedDependencies.
MIT License - see LICENSE file for details.