Universal any-device-to-any-device file sharing. AirDrop / Quick Share / Snapdrop / WeTransfer rolled into one open protocol — browser-first, zero-account, end-to-end encrypted in transit (DTLS) with optional application-layer AES-GCM-256.
Phone → Laptop. Mac → Windows. Android → iPad. Any direction. Files stream peer-to-peer in your browser. No upload server holds them, no size limit, no account. Works without internet when both devices are on the same Wi-Fi.
Transport ladder (fastest → most compatible):
loopback same host, in-memory
lan-quic same LAN, Rust/QUIC binary (zero-RTT, TLS 1.3)
lan same LAN, WebRTC DataChannels (host candidates)
wifi-direct nearby, OS-native (MultipeerConnectivity / WifiP2p)
p2p-direct cross-NAT, WebRTC (STUN hole-punch)
p2p-relayed cross-NAT via TURN relay
The apps/web/ PWA is the universal frontend — any browser, any device.
- Two-mode picker: Same Wi-Fi (no internet, host-candidate WebRTC) or Anywhere (STUN/TURN over the internet). Auto-detected from the host the page is served from.
- QR + short code: senders get both a typeable share code and a QR
encoding a deep-link URL. Receivers either type the code or scan with
their camera (Chrome on Android uses the native
BarcodeDetectorAPI). - Deep-link auto-join:
/?c=K7-9P3-MX2Aauto-fills the code and jumps straight to the receive flow. - Live transport badge: shows whether the chosen WebRTC path ended up
as
LAN-direct · no internet,P2P-direct · encrypted, orTURN relayed. - Streaming to disk: receivers pick a save folder (
showDirectoryPickerin Chrome/Edge) — files stream straight to disk, never held in memory. - PWA installable: works offline (app shell cached) once visited once.
- Drag, paste, click, share: clipboard paste, drag-and-drop, native Web Share, copy code, copy link.
DropBeam/
├── packages/
│ ├── protocol/ — wire types (signaling + frame codec)
│ ├── transfer/ — transfer engine (sender, receiver, QUIC transport, WASM core)
│ └── transfer-core/ — Rust crate → WASM (sha256, AES-GCM, frame codec)
├── apps/
│ ├── web/ — Vite/TS PWA, the universal frontend
│ ├── signaling/ — Bun WebSocket signaling server (also serves the web bundle when WEB_ROOT is set)
│ ├── cli/ — Node/Bun CLI (dropbeam send / recv)
│ ├── quic-relay/ — Rust/quinn QUIC binary (dropbeam-quic)
│ ├── desktop/ — Tauri 2 desktop app (mDNS, tray, clipboard)
│ ├── ios/ — Swift app (MultipeerConnectivity + WebRTC) — stub
│ └── android/ — Kotlin app (WifiP2p + WebRTC) — stub
- Bun ≥ 1.0 — bun.sh
- Node.js ≥ 20 (for
ws/weriftnative deps) - Rust ≥ 1.78 +
wasm-pack(for QUIC binary / WASM core, optional)
git clone https://github.com/you/DropBeam && cd DropBeam
bun installIn one terminal:
bun run dev:signaling # ws://0.0.0.0:8787/wsIn another:
bun run dev:web # http://localhost:5173Open the URL on both devices (same machine for a quick test). Click Send, drop a file, share the code or QR. On the other device click Receive and either type the code or scan the QR.
For the "two phones on a hotel Wi-Fi with no uplink" case, host the built web app from the signaling process itself so you only need one URL:
bun run lan
# → builds apps/web → dist, then starts signaling on :8787
# → web app served at http://<your-lan-ip>:8787/
# → signaling WS at ws://<your-lan-ip>:8787/wsThe web app auto-detects that it's on a private IP and switches to LAN mode (no STUN servers, host-only ICE candidates).
# Terminal A — receiver creates a room and prints a code
dropbeam recv --signaling ws://localhost:8787/ws --out ./received
# Terminal B — sender joins using that code
dropbeam send --signaling ws://localhost:8787/ws --join-code "K7-9P3-MX2A" ./photo.jpg ./video.mp4
# Or let sender create the room and wait for receiver
dropbeam send --signaling ws://localhost:8787/ws ./file.zipBuild the binary once:
cd apps/quic-relay && cargo build --release
# binary: apps/quic-relay/target/release/dropbeam-quicThen transfer:
# Receiver machine
dropbeam recv --quic --port 9898 --token my-secret --out ./received
# Sender machine (same LAN)
dropbeam send --quic --host 192.168.1.42 --port 9898 --token my-secret ./bigfile.isodropbeam recv --signaling ws://... --out ./recv --passphrase "hunter2"
dropbeam send --signaling ws://... ./secret.zip --passphrase "hunter2"dropbeam send --signaling <url> [OPTIONS] <file>...
dropbeam recv --signaling <url> --out <dir> [OPTIONS]
# QUIC (skips signaling)
dropbeam send --quic --host <ip> --port <n> --token <tok> [--lanes 4] <file>...
dropbeam recv --quic --port <n> --token <tok> --out <dir>
OPTIONS
--signaling <url> Signaling server (or DROPBEAM_SIGNALING env var)
--join-code <code> Join existing room
--passphrase <str> AES-GCM-256 passphrase
--ttl <sec> Room TTL (default 1800)
--adapter <name> WebRTC adapter: auto (default) | node-dc | werift
--wasm/--no-wasm Force WASM or TS crypto core
--out <dir> Output directory (recv)
--lanes <n> Parallel QUIC streams (default 4)
--transfer-id <id> Resume ID
bun test # all unit + integration tests (26 tests)
bun run e2e # end-to-end: signaling + full transfer round-tripIndividual package tests:
bun test packages/transfer # transfer engine (sender/receiver/router)
bun test apps/signaling # signaling server (room codes, rate limiting, WS)Use apps/signaling/Dockerfile as the Dockerfile path and / as the build
context. The container listens on PORT and exposes /healthz, so configure
the Northflank health check to hit:
GET /healthz
The WebSocket endpoint is:
wss://<your-northflank-domain>/ws
The root vercel.json deploys apps/web as a Vite app:
Install Command: bun install --frozen-lockfile
Build Command: bun run --cwd apps/web build
Output Dir: apps/web/dist
Set this Vercel environment variable before production deploys:
VITE_SIGNALING_URL=wss://<your-northflank-domain>/ws
cd packages/transfer-core
wasm-pack build --target bundler --out-dir pkgThe WASM module is imported automatically by @dropbeam/transfer with a
pure-TypeScript fallback if the build is absent.
cd apps/quic-relay
cargo build --release
# → target/release/dropbeam-quic
# Place on PATH or next to the dropbeam CLI binarycd apps/desktop
npm install
npx tauri build # production bundle
npx tauri dev # dev modeRequires Tauri 2 prerequisites: tauri.app/v2/guides/prerequisites
apps/ios/DropBeam/ — Swift 5.9, iOS 15+
cd apps/ios/DropBeam
pod install # installs GoogleWebRTC
open DropBeam.xcworkspaceKey classes:
NearbyTransfer— MultipeerConnectivity (same-LAN, AirDrop-style)WebRTCFallback— GoogleWebRTC DataChannels (remote/cross-NAT)SignalingClient— WebSocket room/SDP relayFileTransfer— coordinator (picks transport, routes events)
apps/android/ — Kotlin, minSdk 26, Gradle 8
cd apps/android
./gradlew assembleDebugDependencies: stream-webrtc-android, okhttp3, kotlinx-coroutines.
Key classes:
NearbyTransfer— WifiP2pManager (WiFi Direct), raw TCP socket transferWebRTCFallback— WebRTC DataChannels via stream-webrtc-androidSignalingClient— OkHttp WebSocket + Kotlin Flow eventsFileTransfer— coordinator
Required manifest permissions: NEARBY_WIFI_DEVICES, CHANGE_WIFI_STATE, INTERNET.
0 1 2 3
magic ver flags reserved
0xDB 0x01 0bxxxxxx 0x00
4 ─── 7 : uint32 BE fileId
8 ─── 11 : uint32 BE chunkIndex
12 ── 15 : uint32 BE payloadLen
16+ : payload (optionally AES-GCM encrypted)
flags:
bit 0 = ENCRYPTED
bit 1 = LAST (final chunk of file)
sameHost? → loopback
sameLan+quic? → lan-quic (Rust/quinn, UDP, TLS 1.3, zero-RTT)
sameLan? → lan (WebRTC DataChannels)
nearby? → wifi-direct (OS-native: MPC / WifiP2p)
directReach? → p2p-direct (WebRTC, STUN hole-punch)
else → p2p-relayed (WebRTC + TURN)
node-datachannel (C++, libdatachannel) ← fastest, requires native binary
↓ if unavailable
werift (pure TypeScript) ← zero native deps, works everywhere
WASM (Rust: sha2 + aes-gcm crates) ← hot path, ~10× faster
↓ if WASM not bundled
TypeScript (WebCrypto API) ← universal fallback
- Add a new transport: implement
PeerConnectioninterface inpackages/transfer/src/peer.ts - Add a new signaling backend: implement the wire types in
packages/protocol/src/signaling.ts - Plug in TURN: edit
iceServersinweriftAdapter.ts/nodeDCAdapter.ts/ iOSWebRTCFallback.swift/ AndroidWebRTCFallback.kt - Custom frame format: extend
packages/transfer-core/src/frame.rs+packages/protocol/src/transfer.ts
DropBeam cannot see your files. They go peer-to-peer over a DTLS-encrypted WebRTC data channel; the signaling server only relays opaque SDP/ICE handshake blobs to introduce the two devices. No upload bucket, no cache, no analytics. Add the optional passphrase and every chunk is also AES-GCM-256 encrypted in your browser before it leaves the device.
The user-facing privacy statement is in-app — open the web UI and click "Privacy" in the footer.
- SETUP.md — start here. Plain-English fork → install → run → first-transfer walkthrough.
- FEATURES.md — comprehensive feature catalog (use this as the source-of-truth for building marketing pages, app listings, or mobile screens).
- DEPLOY.md — production deploy: Vercel for the web app, Koyeb (or Render / Fly.io) for the signaling server. Includes the Vercel CLI walkthrough and a section on running ads honestly.
- FORK.md — coding conventions and where to add new transports / brandings / self-hosted deploys.
If this ever earns money — ads, freemium API, office tier — 30 % is donated to people in need (recipients voted on quarterly, receipts posted publicly) and 70 % is reinvested in infrastructure, dev, and keeping the free tier alive. Built by Vara Prasad Karewar (@VaraKare · @the.varaprasad).
MIT — see header in each source file. Use it, fork it, rebrand it, ship it commercially. Just don't sue us if a file gets corrupted.