UNPKG

tinyroom

Version:

A minimalistic room/lobby system for multiplayer applications. Built on TinyPeer with automatic reconnection support.

371 lines (255 loc) 8.95 kB
# TinyRoom [![npm version](https://img.shields.io/npm/v/tinyroom.svg)](https://www.npmjs.com/package/tinyroom) [![license](https://img.shields.io/npm/l/tinyroom.svg)](https://github.com/jamsinclair/tinyroom/blob/main/LICENSE.md) [![bundlephobia minzipped size](https://badgen.net/bundlephobia/minzip/tinyroom)](https://bundlephobia.com/package/tinyroom) > [!WARNING] > This project is in early development. The API may change before a stable 1.0 release. Use with caution in production. A tiny, host‑authoritative P2P room/lobby for the browser. Built on [TinyPeer](https://github.com/jamsinclair/tinypeer) with [PeerJS Server](https://peerjs.com/server) signaling. One peer hosts the room (authority) and everyone, including the host, joins as a client. This gives a clean separation of concerns: write client code as if everyone talks to a single remote server. ## Why TinyRoom? - **Host‑authoritative rooms** – One peer acts as the room/server; same client API for everyone (host included) - **No server** – Uses a public signaling server (PeerJS) only; messages sent peer-to-peer over WebRTC data channels - **Reconnect that sticks** – Optional layer with health checks, grace periods, and stable IDs across refreshes - **Small and focused** – Minimal bundle, zero deps beyond [TinyPeer](https://github.com/jamsinclair/tinypeer) - **Flexible by design** – Start with basic rooms; add reconnection when you need it Great for multiplayer games, collaborative tools, and real‑time apps where a single peer manages connections and game state. ## Installation ```bash npm install tinyroom ``` ## Quick Start ```typescript import { joinOrCreateRoom } from 'tinyroom' const { room, client } = await joinOrCreateRoom('game-123', { metadata: { username: 'Alice' } }) if (room) { // Handle peers connecting room.onPeerJoin((peer) => { console.log(`${peer.id} joined`) }) // Handle messages from clients room.on('move', (data, fromId) => { // Validate and relay to everyone room.broadcast('move', { player: fromId, ...data }) }) } // All players can have the same client code (including host!) client.send('move', { x: 5, y: 10 }) client.on('move', (data) => { console.log(`Player ${data.player} moved`) }) ``` ## API ### Factory Functions #### `createRoom(roomId, options?)` Creates a new room with the caller as host. ```typescript import { createRoom } from 'tinyroom' const { room, client } = await createRoom('my-room', { metadata: { username: 'Alice' }, // Optional: peer metadata // ... all TinyPeer options (host, port, path, etc.) }) ``` **Returns:** `{ room: HostRoom, client: Client }` - `room` - Room management interface (host only) - `client` - Client interface for the host (hosts participate as a regular player too) #### `joinRoom(roomId, options?)` Joins an existing room as a client. ```typescript import { joinRoom } from 'tinyroom' const client = await joinRoom('my-room', { metadata: { username: 'Bob' } }) ``` **Returns:** `Client` **Throws:** If room doesn't exist or connection fails. #### `joinOrCreateRoom(roomId, options?)` Attempts to join a room, creates it if it doesn't exist. ```typescript import { joinOrCreateRoom } from 'tinyroom' const { client, room } = await joinOrCreateRoom('my-room', { metadata: { username: 'Charlie' } }) if (room) { console.log('Created room - I am host') room.onPeerJoin((peer) => { console.log(`${peer.id} joined`) }) } else { console.log('Joined existing room') } // Everyone uses client the same way client.send('hello', { message: 'Hi!' }) ``` ### HostRoom Interface The `room` object provides host-only management capabilities. **Properties:** - `id: string` - Room ID (same as host's peer ID) - `peers: Map<string, Peer>` - All connected peers (including host) **Methods:** ##### `room.on(event, handler)` Receives custom messages from all clients. ```typescript room.on('move', (data, fromId, metadata) => { console.log(`Client ${fromId} wants to move`, data) // Validate, process, then broadcast if (isValidMove(data)) { room.broadcast('move', { player: fromId, ...data }) } console.log('Optional metadata from client', metadata) }) ``` ##### `room.broadcast(event, data, metadata?)` Send message to all connected clients (including host's own client). ```typescript await room.broadcast('game-start', { level: 1 }) ``` ##### `room.sendToPeer(event, data, target, metadata?)` Send message to specific client(s). ```typescript // Single target await room.sendToPeer('welcome', { msg: 'Hi!' }, 'peer-123') // Multiple targets await room.sendToPeer('team-update', data, ['peer-1', 'peer-2']) ``` ##### `room.ping(peerId)` Measure latency to a specific peer. ```typescript const latency = await room.ping('peer-123') console.log(`Latency: ${latency}ms`) ``` ##### `room.onPeerJoin(handler)` Called when a peer joins. **Immediately fires for host when handler first set**. ```typescript room.onPeerJoin((peer) => { console.log(`${peer.id} joined`, peer.metadata) console.log(`Is host: ${peer.isHost}`) // Kick a peer if (shouldKick(peer)) { peer.close('You are not allowed') } }) ``` The handler receives: - `peer.id` - Peer's unique ID - `peer.metadata` - Connection metadata - `peer.isHost` - Whether this is the host - `peer.close(reason?)` - Function to disconnect the peer ##### `room.onPeerLeave(handler)` Called when a peer permanently leaves. ```typescript room.onPeerLeave((peerId) => { console.log(`${peerId} left`) removeFromGame(peerId) }) ``` ##### `room.close()` Closes the room and disconnects all clients. ```typescript await room.close() ``` ### Client Interface The `client` object is used by everyone (including host) for player behavior. **Properties:** - `id: string` - This client's peer ID **Methods:** ##### `client.send(event, data, metadata?)` Send message to host (who will process/relay it). ```typescript await client.send('move', { x: 10, y: 20 }) ``` ##### `client.on(event, handler)` Receive messages (broadcasts from host). ```typescript client.on('move', (data, fromId, metadata) => { console.log(`Player ${fromId} moved to`, data) updateSprite(fromId, data.x, data.y) console.log('Optional metadata from client', metadata) }) ``` ##### `client.ping()` Measure latency to host (client only, host should use `room.ping(peerId)`). ```typescript const latency = await client.ping() console.log(`Latency to host: ${latency}ms`) ``` ##### `client.onClose(handler)` Called when connection closes. ```typescript client.onClose((reason) => { console.log('Connection closed:', reason) }) ``` ##### `client.onError(handler)` Called on connection errors. ```typescript client.onError((error) => { console.error('Connection error:', error) }) ``` ##### `client.leave()` Disconnect from the room. ```typescript client.leave() ``` ## Reconnection (Experimental) TinyRoom is experimenting with reconnection support with stable peer identities. See [reconnect.md](./docs/reconnect.md) for full documentation. ## Advanced Topics ### Host as Player The host uses their `client` object just like everyone else: ```typescript const { room, client } = await createRoom('game-123') // Host sends moves as a client client.send('move', { x: 10, y: 10 }) // This triggers the room.on() handler room.on('move', (data, fromId) => { // fromId will be the host's own ID when host sends room.broadcast('move', { player: fromId, ...data }) }) // Host receives broadcasts like everyone else client.on('move', (data) => { updatePlayerSprite(data.player, data.x, data.y) }) ``` ### Message Routing **Client → Host:** ```typescript client.send('move', data) // Goes to host ``` **Host receives and processes:** ```typescript room.on('move', (data, fromId) => { // Validate, process, then broadcast room.broadcast('move', { player: fromId, ...data }) }) ``` **Everyone receives broadcast:** ```typescript client.on('move', (data) => { // Both host and clients receive this }) ``` ## Examples ### Basic Room See the [basic room example](examples/basic/) for a complete working demo. ### Reconnection See the [reconnection example](examples/reconnect/) for a complete working demo with: - Host controls (simulate disconnect, kick peer) - Client reconnection UI - Health checks - Grace periods - All lifecycle events ## Browser Support Requires modern browsers with WebRTC support (ES2020+). TinyRoom uses modern APIs and assumes recent browser versions. If you encounter browser-specific issues, please [open an issue](https://github.com/jamsinclair/tinyroom/issues). ## License MIT ## Built With - [TinyPeer](https://github.com/jamsinclair/tinypeer) - Minimalistic WebRTC peer-to-peer library ## Acknowledgements Inspired by: - [Colyseus](https://github.com/colyseus/colyseus) - [PeerJS](https://peerjs.com/) - [Trystero](https://github.com/dmotz/trystero)