Skip to content

Sumit-Ks1/PeerDrop

Repository files navigation

PeerDrop — P2P Encrypted File Sharing

Two people open a shared room link and transfer files directly between their browsers. Files are end-to-end encrypted (AES‑256‑GCM with ECDH key agreement) and never touch a server — the only server-side component is a tiny WebSocket that relays connection handshakes (SDP/ICE) and never sees file data.

  • No upload limits — files stream in 256 KB chunks, never fully buffered in memory (tested design target: multi‑GB files).
  • End‑to‑end encrypted — keys are derived per‑session in the browser and never leave it.
  • Ephemeral — received files live in IndexedDB and auto‑delete 10 minutes after completion. Rooms die when the host leaves or after 30 min idle.
  • Resumable — if a peer reloads mid‑transfer, it resumes from the last stored chunk.

The full original engineering specification lives in docs/SPEC.md.


Quick start (local development)

Requires Node.js ≥ 20.18.0.

npm install
cp .env.example .env.local   # defaults already work for local dev
npm run dev

npm run dev starts two processes (this is intentional — it mirrors the production/Vercel split and avoids a clash between Next.js's dev HMR socket and our signaling socket):

The client finds the signaling server via NEXT_PUBLIC_SIGNALING_URL in .env.local. Open http://localhost:3000.

To actually transfer a file you need two browser contexts (the two "peers"):

  1. In window A, click Create a Room → you get a code like X7K2PQ and a share link.
  2. Copy the link into window B (a second tab, an incognito window, or another device on your network). Two normal tabs in the same browser work fine for testing.
  3. Once both show Connected, drag a file into either side and click Send File.
  4. The receiver gets a Download button and a 10‑minute auto‑delete countdown.

Use two separate browser profiles/incognito windows so each gets its own sessionStorage identity. Same-origin localhost peer-to-peer works without any TURN server.

Available scripts

Script What it does
npm run dev Dev: Next.js (:3000) + standalone signaling server (:3001) as two processes.
npm run dev:next Next.js dev only (if you run signaling yourself).
npm run signal Standalone WebSocket signaling server only (port 3001).
npm run build Production Next.js build.
npm run start Production single host: Next.js (:3000) + signaling (:3001) as two processes (front with Caddy — see DEPLOY.md).
npm run start:vercel next start only — frontend without the bundled WebSocket (Vercel).
npm run start:combined / dev:combined Single-port Next+WS server — not browser-safe (Next attaches its own WS upgrade handler); kept for reference only.
npm run typecheck tsc --noEmit.
npm run lint Next.js ESLint.

How it works

 Browser A  ──(encrypted file chunks over WebRTC DataChannel)──▶  Browser B
     │                                                                │
     └──────────── SDP / ICE / public keys (JSON) ───────────────────┘
                                   │
                         WebSocket signaling server
                    (room registry only — never sees files)
  • Signaling (lib/signaling/) — a WebSocket room registry. Pairs two peers, relays their WebRTC offer/answer/ICE and ECDH public keys, enforces 2‑peers‑per‑room, rate‑limits room creation, validates Origin, and reaps idle rooms. State is in‑memory and intentionally ephemeral.
  • WebRTC (lib/webrtc/) — reliable, ordered RTCDataChannel. The creator is always the initiator (no glare). Backpressure via bufferedAmount events, ICE restart on failure.
  • Crypto (lib/crypto/) — ECDH P‑256 → AES‑GCM‑256. Each chunk gets a fresh 12‑byte IV. A pure‑TS streaming SHA‑256 lets us checksum huge files with constant memory.
  • Transfer (lib/transfer/) — FileSender chunks/encrypts/sends with a sliding ACK window, retransmits, and resume; FileReceiver decrypts, stores chunks in IndexedDB, and verifies the whole‑file checksum before confirming.
  • Storage (lib/storage/) — idb wrapper for chunks + transfer records, with the 10‑minute auto‑delete rule.
  • Orchestration (lib/room/RoomController.ts) — ties it all together and projects state into a Zustand store the React UI renders from.

See the directory map in docs/SPEC.md §4.


Environment variables

Copy .env.example.env.local and edit. All variables are documented inline there.

Variable Purpose
NEXT_PUBLIC_SIGNALING_URL WebSocket URL of the signaling server. Blank = same origin (combined local/Node server). Set this for Vercel/split deploys.
NEXT_PUBLIC_APP_URL Public base URL used for share links.
SIGNALING_ALLOWED_ORIGINS Comma‑separated allow‑list of origins the signaling server accepts. Blank = allow all (dev only).
SIGNAL_PORT Port for the standalone signaling server (npm run signal).
NEXT_PUBLIC_TURN_URL / _USERNAME / _CREDENTIAL Optional TURN server for users behind symmetric NATs.

TURN matters in production. Without a TURN server, ~10–15% of real‑world peer pairs (corporate / carrier‑grade NAT) can't form a direct connection. Managed options: Metered, Twilio, Cloudflare Calls; or self‑host coturn.


Deployment

The catch with WebRTC apps: signaling needs a persistent WebSocket, and Vercel's serverless functions cannot hold one open. So pick one of these:

Option A — One Node host (simplest, fully featured)

Deploy the whole thing to a host that runs a long‑lived Node process: Render, Railway, Fly.io, a VPS, etc.

npm run build
npm run start        # serves the app AND the signaling WebSocket on $PORT

Set env vars on the host before building (NEXT_PUBLIC_* are inlined at build time):

NODE_ENV=production
NEXT_PUBLIC_APP_URL=https://your-app.example.com
SIGNALING_ALLOWED_ORIGINS=https://your-app.example.com
# leave NEXT_PUBLIC_SIGNALING_URL blank — client uses same origin (/api/signal)

Nothing else to wire up; in production Next.js attaches no HMR socket, so the combined server's WebSocket and HTTP coexist on one port. The client connects to /api/signal on the same origin.

Option B — Vercel (frontend) + separate signaling server

This is the path for shipping the UI to Vercel.

  1. Deploy the signaling server to a Node host (Render/Railway/Fly). Run npm run signal. Note its public URL, e.g. https://peerdrop-signal.onrender.com. Set on that host:

    SIGNALING_ALLOWED_ORIGINS=https://your-app.vercel.app
    
  2. Deploy the frontend to Vercel. Import the repo; Vercel auto‑detects Next.js (build next build, output handled automatically). Set these Vercel env vars:

    NEXT_PUBLIC_SIGNALING_URL=wss://peerdrop-signal.onrender.com/api/signal
    NEXT_PUBLIC_APP_URL=https://your-app.vercel.app
    # TURN vars if you have them
    

    The CSP connect-src in next.config.js reads NEXT_PUBLIC_SIGNALING_URL and whitelists that origin automatically.

  3. Redeploy. The browser loads from Vercel and opens its signaling WebSocket against your separate server.

Switching between environments is just .env changes — no code edits. Blank NEXT_PUBLIC_SIGNALING_URL → same-origin combined server; set it → split deploy (Vercel + separate signaling). Remember NEXT_PUBLIC_* is baked in at build time, so rebuild/redeploy after changing it.


What was verified

  • npm run typecheck — clean (strict mode).
  • npm run build — production build succeeds.
  • ✅ Servers boot; /api/health responds; signaling WebSocket accepts connections.
  • Signaling protocol integration‑tested with two/three clients: room create/join, OFFER/ANSWER/ICE/PUBKEY relay, ROOM_FULL on a third peer, PEER_LEFT on disconnect, reconnect grace, origin enforcement.
  • Crypto/framing unit‑tested: streaming SHA‑256 matches Web Crypto, ECDH+AES‑GCM round‑trips between two parties, chunk header pack/parse round‑trips.
  • Full two‑peer transfer in real headless Chrome: room create → join → WebRTC connect → encrypted chunked send → receive → SHA‑256 verified (status: complete) → Download button + 10‑minute auto‑delete countdown. Verified the received file's chunk count and size in IndexedDB.

Still worth exercising by hand in normal browsers: resume‑on‑reload mid‑transfer, the 10‑minute deletion actually firing, very large files (multi‑GB), and cross‑browser (Firefox/Safari). Use the Quick start two‑window flow above; the full manual checklist is in docs/SPEC.md §20.

Browser support

Chrome/Edge 120+, Firefox 120+, Safari 17+. WebRTC, Web Crypto (ECDH/AES‑GCM), and IndexedDB are all required.


Security notes

  • File bytes never reach any server — only encrypted chunks over the peer‑to‑peer DataChannel.
  • E2E encryption uses ephemeral ECDH keys; the signaling server cannot decrypt anything even though it relays the public keys.
  • Signaling logs only room codes and peer UUIDs — never file names or sizes.
  • CSP, X-Frame-Options: DENY, nosniff, and origin validation are configured (next.config.js, lib/signaling/server.ts).
  • Room codes are 6 chars from a 32‑symbol unambiguous alphabet — fine for ephemeral rooms, not meant as a long‑term secret.

Project layout

A condensed view (full tree in docs/SPEC.md §4):

app/            Next.js App Router pages + API routes (health, signal info)
components/     UI primitives (ui/) and room screens (room/)
hooks/          useRoom, useTransfer, useCountdown
lib/
  crypto/       E2ECrypto (ECDH/AES-GCM) + streaming sha256
  webrtc/       PeerConnection, DataChannelManager, IceConfig
  transfer/     FileSender, FileReceiver
  storage/      FileStore (IndexedDB via idb)
  signaling/    server (Node handler) + SignalingClient (browser)
  room/         RoomController (orchestration)
store/          Zustand room store
server.ts                    combined Next + WebSocket server
server/signaling-standalone.ts   WebSocket-only server (for Vercel split)

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors