tinyroom
v0.1.0
Published
A minimalistic room/lobby system for multiplayer applications. Built on TinyPeer with automatic reconnection support.
Maintainers
Readme
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 with PeerJS 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
- 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
npm install tinyroomQuick Start
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.
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.
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.
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.
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).
await room.broadcast('game-start', { level: 1 })room.sendToPeer(event, data, target, metadata?)
Send message to specific client(s).
// 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.
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.
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 IDpeer.metadata- Connection metadatapeer.isHost- Whether this is the hostpeer.close(reason?)- Function to disconnect the peer
room.onPeerLeave(handler)
Called when a peer permanently leaves.
room.onPeerLeave((peerId) => {
console.log(`${peerId} left`)
removeFromGame(peerId)
})room.close()
Closes the room and disconnects all clients.
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).
await client.send('move', { x: 10, y: 20 })client.on(event, handler)
Receive messages (broadcasts from host).
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)).
const latency = await client.ping()
console.log(`Latency to host: ${latency}ms`)client.onClose(handler)
Called when connection closes.
client.onClose((reason) => {
console.log('Connection closed:', reason)
})client.onError(handler)
Called on connection errors.
client.onError((error) => {
console.error('Connection error:', error)
})client.leave()
Disconnect from the room.
client.leave()Reconnection (Experimental)
TinyRoom is experimenting with reconnection support with stable peer identities.
See reconnect.md for full documentation.
Advanced Topics
Host as Player
The host uses their client object just like everyone else:
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:
client.send('move', data) // Goes to hostHost receives and processes:
room.on('move', (data, fromId) => {
// Validate, process, then broadcast
room.broadcast('move', { player: fromId, ...data })
})Everyone receives broadcast:
client.on('move', (data) => {
// Both host and clients receive this
})Examples
Basic Room
See the basic room example for a complete working demo.
Reconnection
See the reconnection example 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.
License
MIT
Built With
- TinyPeer - Minimalistic WebRTC peer-to-peer library
Acknowledgements
Inspired by:
