1# Trystero P2P Messaging Demo
3A tiny **serverless, peer-to-peer messaging** web app built on
4[Trystero](https://github.com/dmotz/trystero). Everyone who opens the app lands
5in the same room, sees everyone else who is connected, and can send **text
6messages** and **large binary payloads** directly to any other peer (or
7broadcast to all) — with live progress bars and SHA-256 integrity checks.
9No backend, no accounts, no infrastructure. Peer discovery happens over the
10public **Nostr** relay network (Trystero's default strategy); after that, all
11data travels **directly browser-to-browser over WebRTC, end-to-end encrypted**.
13Built with **Vite + React + TypeScript**.
15**▶ Live demo: https://concept-collection.github.io/trystero-messaging-demo/**
16(open it in two tabs to see peers connect)
18## Quick start
20```sh
21npm install
22npm run dev
23```
25Open the printed URL (e.g. http://localhost:5173). Then open the **same URL in
26another browser tab, another browser, or another device**. Within a few seconds
27the two tabs discover each other and appear in each other's peer list.
29> The first connection can take a few seconds while peers find each other on the
30> Nostr relays. A couple of relays in the default list may be offline at any
31> time — Trystero connects to several for redundancy, so this is expected and
32> harmless.
34## What it demonstrates
36- **Presence** — each browser tab is its own peer with a unique id
37 (Trystero's `selfId`). Joining/leaving updates everyone's roster live.
38- **Display names** — your name is broadcast to peers and persisted in
39 `localStorage`.
40- **Targeted text messages** — pick a peer (or "Everyone") and send a chat
41 message. This is the "messages between pairs of people" case.
42- **Large binary transfers** — send a file, or generate a random payload up to
43 32 MB. Trystero automatically chunks/throttles it and reassembles it on the
44 other side. You get:
45 - a **send progress** bar (`onProgress`) and a **receive progress** bar
46 (`onReceiveProgress`),
47 - a **SHA-256** computed on both ends so you can confirm the bytes arrived
48 intact,
49 - a **download link** for the received payload.
51## How it maps to the Trystero API
53Everything P2P lives in [`src/useRoom.ts`](src/useRoom.ts):
55```ts
56import {joinRoom, selfId} from 'trystero' // default export = Nostr strategy
58const room = joinRoom({appId: APP_ID}, roomId)
60const chat = room.makeAction<string>('chat')
61const binary = room.makeAction('binary')
63room.onPeerJoin = peerId => name.send(myName, {target: peerId})
64room.onPeerLeave = peerId => { /* drop from roster */ }
66chat.onMessage = (text, {peerId}) => { /* show it */ }
68binary.send(bytes, {
69 target: peerId, // or omit to broadcast
70 metadata: {fileName, mime, size, transferId},
71 onProgress: pct => updateBar(pct)
72})
73binary.onReceiveProgress = (pct, {peerId, metadata}) => updateBar(pct)
74binary.onMessage = (data, {peerId, metadata}) => { /* data is an ArrayBuffer */ }
75```
77- `APP_ID` and the default room are set in [`src/config.ts`](src/config.ts).
78- No relay/strategy setup is needed — importing from `trystero` uses Nostr out
79 of the box.
81## Rooms
83Everyone shares one room (`lobby`) by default. To use an isolated room, add a
84hash to the URL, e.g. `http://localhost:5173/#my-room`. Anyone using the same
85`#hash` is in the same room; the change is shareable as a link.
87## Project layout
89```
90src/
91 config.ts APP_ID + room handling
92 useRoom.ts Trystero wrapper hook (presence, names, text + binary)
93 util.ts byte formatting, SHA-256, random payload generator
94 types.ts shared types
95 App.tsx layout + target selection
96 components/
97 Header.tsx identity, room, relay/peer status
98 PeerList.tsx pick who to message (a peer or "Everyone")
99 Composer.tsx send text / file / random binary
100 TransferList.tsx live progress bars
101 MessageLog.tsx message + transfer history
102```
104## Scripts
106| Command | Description |
107| ----------------- | ------------------------------------ |
108| `npm run dev` | Start the Vite dev server |
109| `npm run build` | Type-check and build for production |
110| `npm run preview` | Preview the production build locally |
112## Notes
114- WebRTC needs a secure context. `localhost` is fine; to test across devices on
115 your network, serve over HTTPS (or use a tunnel) since plain `http://<ip>`
116 origins are not treated as secure for WebRTC in most browsers.
117- This demo intentionally does **not** wrap the app in `<StrictMode>` — its
118 double-mounting of effects in development would make the room leave and
119 rejoin, causing spurious peer churn. See the comment in
120 [`src/main.tsx`](src/main.tsx).