๐ฌ Meshcore โ P2P Chat
Serverless peer-to-peer chat with an automatic relay fallback. It runs on static hosting (GitHub Pages) โ zero servers, zero databases, zero accounts.
Try it now: ๐ Open Meshcore (opens in a new tab)
How it worksโ
โโโโโโโโโโโโโโโ WebRTC data channel โโโโโโโโโโโโโโโ
โ Peer A โโโโโโโโโโโโโโโโโโโโโโโโโโบโ Peer B โ
โ (your tab) โ (direct, P2P) โ (their tab) โ
โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ
โ SDP offer/answer/ICE via โ
โ public MQTT topic (signaling only) โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Public MQTT broker (per-room topic) โ
โ presence + signaling + fallback for messages โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Layer 1 โ P2P (WebRTC)โ
- Peers announce their presence on a per-room MQTT topic (public broker, no account โ the same way messengers discover each other).
- When two peers see each other, they exchange WebRTC offer/answer/ICE candidates through that topic (the classic signaling-server pattern used by PeerJS & co.). The broker only introduces peers โ it never sees message payloads.
- Once connected, chat messages travel over the WebRTC data channel directly between browsers โ real peer-to-peer.
Layer 2 โ Relay (automatic fallback)โ
Pure browser P2P cannot connect on mobile networks: carriers use CGNAT and block NAT hole-punching, and the free public TURN servers that used to bridge it are dead or paid (2026). That is why every message is also published in parallel to a per-room topic on a public MQTT broker (WSS, no account). Every receiver deduplicates by message ID, so:
- peers that can connect P2P โ messages travel directly over the WebRTC data channel,
- devices that cannot (phones on LTE/5G) โ exchange messages via the relay.
P2P first, relay as a fallback โ the same pattern real messengers use.
Why not WebTorrent trackers? The original build found peers through public WebTorrent WebSocket trackers (
tracker.webtorrent.dev,tracker.openwebtorrent.com). Those trackers now accept announces and see the swarm, but no longer relay WebRTC offers between peers (verified live: two peers registered in the same swarm, zero offers ever returned). Since the browser build of WebTorrent can only use WebSocket trackers (no UDP/DHT in the browser), peers could never find each other โ so signaling moved to the MQTT relay.
Featuresโ
- ๐ Rooms โ the same room name = the same signaling topic = the same peer group
- ๐ก Relay fallback โ a dependency-free MQTT 3.1.1 client (~100 lines, no
libraries), publishing to
n2mesh/{room}, room isolation by exact topic - ๐ Deduplication by ID โ a message received over P2P and relay and the local bridge is displayed once
- ๐ฌ True P2P messages โ over WebRTC data channels, with MQTT fallback
- ๐ท๏ธ Nicknames, peer counter, connection status (P2P / P2P+relay / relay)
- ๐ Shareable room links (
#/room-name) - ๐ Dark, keyboard-accessible interface, zero dependencies (no CDN, no build)
- ๐ฅ๏ธ Local bridge โ tabs of the same browser cannot connect via WebRTC
(browsers block WebRTC loopback), so a
BroadcastChannelbridge connects them locally
How to use itโ
- Open Meshcore on two devices (or two tabs of one browser).
- Set the same room on both sides (default:
lobby). - Pick a nickname and send messages โ on a desktop they travel P2P, on a phone automatically via the relay. Both cases work from the same link.
โ ๏ธ This is a demo-grade mesh: scrollback is cached per room in your browser (last 100 messages), but there is no server-side history โ messages sent while you are offline are lost (no broker-side queueing).
Run locallyโ
git clone https://github.com/BartoszOsiej/Meshcore.git
cd n2-mesh
python3 -m http.server 8080
# open http://localhost:8080
Securityโ
- P2P mode: messages travel exclusively peer-to-peer; the MQTT broker only performs signaling and never receives content.
- Relay mode: messages pass through a public MQTT broker โ a fallback for networks where P2P is impossible. Rooms are isolated (each subscriber only gets its own topic).
- Everything is plain JavaScript (zero dependencies โ no CDN), no tracking, no data stored.