# Trystero P2P Messaging Demo A tiny **serverless, peer-to-peer messaging** web app built on [Trystero](https://github.com/dmotz/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 ```sh 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`](src/useRoom.ts): ```ts import {joinRoom, selfId} from 'trystero' // default export = Nostr strategy const room = joinRoom({appId: APP_ID}, roomId) const chat = room.makeAction('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`](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://` origins are not treated as secure for WebRTC in most browsers. - This demo intentionally does **not** wrap the app in `` — 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`](src/main.tsx).