concept-collection / trystero-messaging-demo
Go to fileHistoryFork
.githubTrystero P2P messaging demo (text + large binary over Nostr)
srcTrystero P2P messaging demo (text + large binary over Nostr)
.gitignoreTrystero P2P messaging demo (text + large binary over Nostr)
index.htmlTrystero P2P messaging demo (text + large binary over Nostr)
package-lock.jsonTrystero P2P messaging demo (text + large binary over Nostr)
package.jsonTrystero P2P messaging demo (text + large binary over Nostr)
README.mdTrystero P2P messaging demo (text + large binary over Nostr)
tsconfig.app.jsonTrystero P2P messaging demo (text + large binary over Nostr)
tsconfig.jsonTrystero P2P messaging demo (text + large binary over Nostr)
tsconfig.node.jsonTrystero P2P messaging demo (text + large binary over Nostr)
vite.config.tsUse a relative base so the build works at any mount point

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.

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_ID and the default room are set in src/config.ts.
  • No relay/strategy setup is needed — importing from trystero uses 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. localhost is fine; to test across devices on your network, serve over HTTPS (or use a tunnel) since plain http://<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 in src/main.tsx.