Live: any-share.site · Free, web-based, peer-to-peer file sharing between devices on the same Wi-Fi. No install, no signup, nothing uploaded.
Share files, media, and text directly between devices on the same wifi — no app install, no account, nothing uploaded to a server. Built for locked-down places like university lab PCs where you can't install software.
Open any-share.site on two devices, read your 6-digit code to the other person (or
show your QR), they type it in, you tap Accept, and the browsers open a direct,
end-to-end-encrypted WebRTC connection. Files stream peer-to-peer over your wifi.
- Private by design — you never see a list of devices. The only way to reach someone is to be told their code, the receiver must tap Accept, and both screens show a matching verify word before anything connects. Codes are short-lived and unique among the devices currently online. (The actual transfer is still LAN-only — bytes never leave the wifi.)
- Free to run — only cost is the domain. Frontend on Cloudflare Pages; signaling on a Cloudflare Durable Object (WebSocket Hibernation), both on the free plan.
This build is strict local-only: WebRTC uses iceServers: [], so connections use only
LAN/mDNS candidates and bytes never leave the wifi. The trade-off: many university/lab
networks enable client isolation, which blocks device-to-device traffic even on the same
wifi. There, the app shows a clear "couldn't connect on this network" screen instead of
hanging. Most home/hotspot wifi works fine.
To make it work everywhere (including isolated networks) while staying private and free, you can later turn on Cloudflare's encrypted TURN relay (1,000 GB/month free). The hook is already in place — see Enabling the relay.
Browser A ──WS──┐ ┌──WS── Browser B
▼ ▼
Cloudflare Worker (one global signaling room)
│ routes every socket to →
Durable Object "RoomDO" (one global instance)
• gives each device a unique 6-digit code
• runs the pair → accept handshake (+ shared verify word)
• relays WebRTC offer/answer/ICE between the two paired peers only
│
A and B then open a DIRECT WebRTC DataChannel ───────────►
(local-only: bytes stay on the wifi, encrypted end-to-end)
worker/ Cloudflare Worker + Durable Object — signaling only (tiny control messages)
src/index.ts WS upgrade, route every socket to the one global RoomDO
src/room.ts RoomDO: code assignment, pairing, signal relay, rate-limiting
frontend/ Vite + TypeScript single-page PWA — Cloudflare Pages
src/signaling.ts WebSocket client
src/webrtc.ts RTCPeerConnection + DataChannel; getIceServers() seam
src/transfer.ts chunked file transfer (backpressure) + text
src/qr.ts QR generate + camera scan (BarcodeDetector, jsQR fallback)
src/main.ts UI + orchestration
# terminal 1 — signaling Worker (http://127.0.0.1:8787)
cd worker && npm install && npm run dev
# terminal 2 — frontend (http://127.0.0.1:5173, proxies /rtc to the Worker)
cd frontend && npm install && npm run devOpen http://127.0.0.1:5173 in two tabs/devices. (On localhost both tabs are a "secure
context", so WebRTC works. For phone testing, deploy first — HTTPS is required off-localhost.)
- Buy a domain at any cheap registrar (Porkbun, Namecheap, …).
- Add it to Cloudflare (free plan) and point the registrar's nameservers at Cloudflare.
- Deploy the frontend to Cloudflare Pages with custom domain
any-share.site:Then in the Cloudflare dashboard add the custom domain to the Pages project.cd frontend && npm run build npx wrangler pages deploy dist --project-name any-share
- Deploy the signaling Worker and put it under the same domain:
worker/wrangler.tomlalready has theroutesblock (any-share.site/rtc/*).
The Worker route is more specific than Pages, socd worker && npx wrangler deploy
/rtc/*hits the Worker and everything else is served by Pages — all under one domain. The frontend already talks towss://any-share.site/rtc/ws. Done — $0/month.
Note: the Worker is not in CI (only the frontend auto-deploys to Pages). After any change under
worker/, ship it manually withcd worker && npx wrangler deploy.
- HTTPS everywhere (Cloudflare auto-TLS); WebRTC DataChannels are DTLS-encrypted end-to-end.
- The server only ever sees tiny signaling metadata — never your files or text.
- The server never logs IPs or files; it only relays tiny signaling messages.
- Pairing is gated by a short code, per-device rate-limited wrong-code attempts, an
explicit Accept, and a matching verify word shown on both sides. The data path is LAN-only
(
iceServers: []), so a remote guesser can't pull files even if they guessed a live code.
To work on client-isolated networks, return real ICE servers from getIceServers() in
frontend/src/webrtc.ts:
- Enable Cloudflare Realtime → TURN and create a TURN key (free up to 1,000 GB/mo).
- Add a Worker route, e.g.
GET /rtc/turn-credentials, that mints short-lived TURN credentials with your TURN key + API token (Cloudflare provides a one-call REST endpoint). - Have
getIceServers()fetch that endpoint and return theiceServersarray.
That single change flips the app from strict-local-only to local-first-with-relay-fallback, keeping transfers private (still E2E encrypted) and free at student scale.