| .github | |
| src | |
| .gitignore | |
| index.html | |
| package-lock.json | |
| package.json | |
| README.md | |
| tsconfig.app.json | |
| tsconfig.json | |
| tsconfig.node.json | |
| vite.config.ts |
Trystero P2P Messaging Demo#
A tiny serverless, peer-to-peer messaging web app built on Trystero. Everyone who opens the app lands in the same room, sees everyone else who is connected, and can send text messages and large binary payloads directly to any other peer (or broadcast to all) — with live progress bars and SHA-256 integrity checks.
No backend, no accounts, no infrastructure. Peer discovery happens over the public Nostr relay network (Trystero's default strategy); after that, all data travels directly browser-to-browser over WebRTC, end-to-end encrypted.
Built with Vite + React + TypeScript.
▶ Live demo: https://concept-collection.github.io/trystero-messaging-demo/ (open it in two tabs to see peers connect)
Quick start#
npm install
npm run dev
Open the printed URL (e.g. http://localhost:5173). Then open the same URL in another browser tab, another browser, or another device. Within a few seconds the two tabs discover each other and appear in each other's peer list.
The first connection can take a few seconds while peers find each other on the Nostr relays. A couple of relays in the default list may be offline at any time — Trystero connects to several for redundancy, so this is expected and harmless.
What it demonstrates#
- Presence — each browser tab is its own peer with a unique id
(Trystero's
selfId). Joining/leaving updates everyone's roster live. - Display names — your name is broadcast to peers and persisted in
localStorage. - Targeted text messages — pick a peer (or "Everyone") and send a chat message. This is the "messages between pairs of people" case.
- Large binary transfers — send a file, or generate a random payload up to
32 MB. Trystero automatically chunks/throttles it and reassembles it on the
other side. You get:
- a send progress bar (
onProgress) and a receive progress bar (onReceiveProgress), - a SHA-256 computed on both ends so you can confirm the bytes arrived intact,
- a download link for the received payload.
- a send progress bar (
How it maps to the Trystero API#
Everything P2P lives in src/useRoom.ts:
import {joinRoom, selfId} from 'trystero' // default export = Nostr strategy
const room = joinRoom({appId: APP_ID}, roomId)
const chat = room.makeAction<string>('chat')
const binary = room.makeAction('binary')
room.onPeerJoin = peerId => name.send(myName, {target: peerId})
room.onPeerLeave = peerId => { /* drop from roster */ }
chat.onMessage = (text, {peerId}) => { /* show it */ }
binary.send(bytes, {
target: peerId, // or omit to broadcast
metadata: {fileName, mime, size, transferId},
onProgress: pct => updateBar(pct)
})
binary.onReceiveProgress = (pct, {peerId, metadata}) => updateBar(pct)
binary.onMessage = (data, {peerId, metadata}) => { /* data is an ArrayBuffer */ }
APP_IDand the default room are set insrc/config.ts.- No relay/strategy setup is needed — importing from
trysterouses Nostr out of the box.
Rooms#
Everyone shares one room (lobby) by default. To use an isolated room, add a
hash to the URL, e.g. http://localhost:5173/#my-room. Anyone using the same
#hash is in the same room; the change is shareable as a link.
Project layout#
src/
config.ts APP_ID + room handling
useRoom.ts Trystero wrapper hook (presence, names, text + binary)
util.ts byte formatting, SHA-256, random payload generator
types.ts shared types
App.tsx layout + target selection
components/
Header.tsx identity, room, relay/peer status
PeerList.tsx pick who to message (a peer or "Everyone")
Composer.tsx send text / file / random binary
TransferList.tsx live progress bars
MessageLog.tsx message + transfer history
Scripts#
| Command | Description |
|---|---|
npm run dev |
Start the Vite dev server |
npm run build |
Type-check and build for production |
npm run preview |
Preview the production build locally |
Notes#
- WebRTC needs a secure context.
localhostis fine; to test across devices on your network, serve over HTTPS (or use a tunnel) since plainhttp://<ip>origins are not treated as secure for WebRTC in most browsers. - This demo intentionally does not wrap the app in
<StrictMode>— its double-mounting of effects in development would make the room leave and rejoin, causing spurious peer churn. See the comment insrc/main.tsx.