Peer-to-peer local network file transfer in the browser using WebRTC.
Zap is a local-first web app for quickly sending files between devices on the same LAN/hotspot. The server is only for peer discovery and signaling; file data is transferred directly between browsers.
- Unstable or no outside internet, but local network still works Teams on the same Wi-Fi/LAN can still share files, post quick session chat updates, and use shared clipboard snippets for coordination even when cloud chat/apps are unreliable.
- Field operations / pop-up teams On construction sites, incident response setups, film sets, or lab environments, people can exchange photos, logs, and docs directly over a local hotspot without relying on external services.
- Classrooms, workshops, and local events Instructors and participants can quickly distribute materials and gather submissions device-to-device on venue Wi-Fi, reducing setup friction and avoiding account sign-ins.
- Zap is intended to run on a local machine and local network.
- It is not intended to be deployed as a public internet website.
- Keep contributions aligned with local/private usage and wireless transfer between nearby devices.
- Direct browser-to-browser transfers over WebRTC data channels
- Device discovery via WebSocket signaling
- Drag-and-drop or file picker upload flow
- Receiver accept/decline confirmation before transfer starts
- Real-time progress, transfer speed, and ETA
- PWA support via service worker + manifest
- Hotspot fallback mode with QR-based SDP exchange when signaling is unavailable
- Node.js + Express static server
wsWebSocket signaling server- Vanilla HTML/CSS/JS frontend
- WebRTC (
RTCPeerConnection+RTCDataChannel)
- Node.js 18+
- npm
- Modern browsers with WebRTC support
npm installFor same-machine development:
npm run devFor local-network sharing (other devices on your Wi-Fi/hotspot):
npm run dev:lanThen open Zap from another device on the same network:
http://<host-machine-lan-ip>:3000
Default mode:
npm startBy default Zap listens on port 3000. To use a custom port:
PORT=8080 npm startserver.js checks for:
certs/fullchain.pemcerts/privkey.pem
Behavior:
- If both files exist and you run
npm start, Zap starts with HTTPS. - If cert files are missing,
npm startfails closed (won't start). npm run devandnpm run dev:lanare still HTTP for local development.- If you intentionally want insecure HTTP in start mode, use
ALLOW_INSECURE_HTTP=1 npm start(ornode server.js --insecure-http).
Note: iOS Safari generally requires HTTPS for reliable WebRTC support.
- Install
mkcert:
brew install mkcert- Install and trust the local CA (this prompts for your macOS admin password):
mkcert -install- Find your current LAN IP (use whichever interface is active):
ipconfig getifaddr en0 || ipconfig getifaddr en1- Generate cert/key files for localhost and that LAN IP:
mkdir -p certs
mkcert -cert-file certs/fullchain.pem -key-file certs/privkey.pem localhost 127.0.0.1 ::1 <your-lan-ip>- Start Zap in HTTPS mode:
npm start- Verify TLS trust:
curl -fsS https://localhost:3000 >/dev/null && echo "HTTPS trust OK"Note: browser/curl trust checks are the right signal on macOS; Node.js TLS clients may still report trust errors unless explicitly configured to use system roots.
If your machine's LAN IP changes, rerun step 4 with the new IP so the certificate SAN list stays valid.
For a public DNS hostname, use Let's Encrypt:
sudo certbot certonly --standalone -d your-hostname.example.com
mkdir -p certs
sudo cp /etc/letsencrypt/live/your-hostname.example.com/fullchain.pem certs/fullchain.pem
sudo cp /etc/letsencrypt/live/your-hostname.example.com/privkey.pem certs/privkey.pem
sudo chown "$(whoami)" certs/fullchain.pem certs/privkey.pem
chmod 600 certs/privkey.pemZAP_JOIN_TOKEN: Optional shared token for signaling/auth-protected LAN use. Start with a token and open Zap usinghttps://<host>:3000/?token=<token>.ZAP_WS_MAX_PAYLOAD: Max signaling message size in bytes (default262144).ZAP_WS_RATE_WINDOW_MS: Rate-limit window for signaling messages (default5000).ZAP_WS_RATE_MAX_MESSAGES: Allowed messages per connection per window (default120).ZAP_MAX_TRANSFER_BYTES: Max accepted transfer size for metadata validation (default2147483648).
- Start Zap on a machine reachable by both devices.
- Open Zap in both browsers on the same network.
- Select a peer device.
- Drop/select a file and send.
- Receiver accepts and the transfer begins.
If signaling server connection fails, the app can switch to Hotspot Mode:
- One device creates a session (shows offer QR).
- Other device joins by scanning and generating answer QR.
- Creator scans answer QR.
- Data channel connects directly and transfer proceeds.
- File payloads are not stored by the server.
- Server relays signaling messages only.
- WebRTC data channels are encrypted (DTLS).
- Receiver must accept incoming transfer requests.
npm start: Run secure server (requires TLS certs unless you explicitly setALLOW_INSECURE_HTTP=1)npm run dev: Run local dev server bound to loopback (127.0.0.1)npm run dev:lan: Run dev server bound to all interfaces (0.0.0.0)npm run check:syntax: Syntax-check backend and frontend JS filesnpm test: Run smoke testsnpm run check: Run all regression checks (check:syntax+test)
GitHub Actions runs .github/workflows/ci.yml on:
- Pull requests targeting
main - Direct pushes to
main
The workflow installs dependencies and runs npm run check on Node 18 and Node 20.
Passing CI status checks are required before merging.