Warning
This project is in early development. The API may change before a stable 1.0 release. Use with caution in production.
A minimalistic, peer-to-peer WebRTC library for the browser. Compatible with PeerJS servers.
A younger sibling to PeerJS focused on simplicity, modern features, and a tiny bundle size.
- Simple - Functional design, minimal API surface
- Modern - ES2020+, TypeScript, ESM-only, Promise-based
- Full-featured - Data channels + media streaming (audio/video)
- Tiny - ~4.19 KB minified + gzipped
- Compatible - Works with existing PeerJS servers
- Zero dependencies
Build multiplayer games, collaborative tools, video chat apps, and real-time experiences without a backend.
npm install tinypeerimport { createPeer } from 'tinypeer'
const peer = await createPeer()
const conn = await peer.connect('host-peer-id', {
metadata: { username: 'Alice' }
})
await conn.send({ message: 'Hello!' })
conn.on('data', (data) => {
console.log('Received:', data)
})import { createPeer } from 'tinypeer'
const peer = await createPeer({ id: 'host-peer-id' })
peer.on('connection', async (conn) => {
console.log(`${conn.peerId } connected`)
await conn.send({ message: 'Welcome!' })
conn.on('data', (data) => {
console.log('Received:', data)
})
})
console.log(`Listening with ID: ${peer.id}`)Creates a new peer instance. Resolves when connected to the signaling server.
const peer = await createPeer({
id: 'my-peer', // Optional: auto-generated if not provided
host: '0.peerjs.com', // Optional: PeerJS server host
port: 443, // Optional: PeerJS server port
path: '/peerjs', // Optional: PeerJS server path
key: 'peerjs', // Optional: API key
secure: true, // Optional: Use WSS/HTTPS
pingInterval: 5000, // Optional: Heartbeat interval in ms
rtcConfig: {...}, // Optional: Custom RTCConfiguration
})Properties:
id- Your peer ID
Methods:
connect(peerId, options?)- Connect to another peercall(peerId, stream, options?)- Call another peer with mediadisconnect()- Close signaling connectiondestroy()- Close all connectionson('connection', handler)- Handle incoming connectionson('call', handler)- Handle incoming calls
Initiates a connection to another peer. Resolves when the connection is open and ready.
const conn = await peer.connect('other-peer', {
metadata: { username: 'Alice' }, // Optional: custom metadata
reliable: true, // Use reliable/ordered data channel
})Initiates a media call to another peer. Returns immediately with a MediaConnection object. The stream promise resolves when the remote peer answers and media is received.
const localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
const call = peer.call('other-peer', localStream, {
metadata: { username: 'Alice' }
})
const remoteStream = await call.streamFires when another peer connects to you for data. The connection is already open when this event fires.
peer.on('connection', async (conn) => {
console.log(`${conn.peer} connected`)
console.log('Metadata:', conn.metadata)
await conn.send({ welcome: true })
})Fires when another peer calls you for media. The call is ready but not yet answered.
peer.on('call', async (call) => {
console.log(`Call from ${call.peer}`)
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
await call.answer(stream)
const remoteStream = await call.stream
})Closes the signaling connection but keeps peer-to-peer connections alive.
Closes all connections and disconnects from the signaling server.
Properties:
peer- Remote peer IDmetadata- Optional connection metadata
Methods:
send(data, metadata?)- Send data to peerclose()- Close the connectionon('data', handler)- Handle incoming dataon('close', handler)- Handle connection closeon('error', handler)- Handle errors
Sends data with optional metadata. Supports JSON objects, strings, and binary data (ArrayBuffer/TypedArray).
// Send JSON
await connection.send({ type: 'move', x: 10, y: 20 })
// Send binary with metadata
const imageData = new Uint8Array(imageBytes)
await connection.send(imageData, {
filename: 'photo.jpg',
type: 'image/jpeg'
})Note: Binary data cannot be nested in JSON. Use the metadata parameter instead:
// ❌ Won't work
await connection.send({ image: new Uint8Array([...]) })
// ✅ Works
await connection.send(new Uint8Array([...]), { type: 'image' })Fires when data is received from the remote peer.
connection.on('data', (data, metadata) => {
console.log('Received:', data)
// Handle different data types
if (data instanceof Uint8Array) {
console.log('Binary data received:', data.byteLength, 'bytes')
if (metadata?.type === 'image/jpeg') {
displayImage(data, metadata.filename)
}
} else if (typeof data === 'string') {
console.log('String:', data)
} else {
console.log('JSON object:', data)
}
})Fires when the connection closes.
Fires when a connection error occurs.
Closes the connection.
Properties:
peer- Remote peer IDmetadata- Optional call metadatastream- Promise that resolves with remote MediaStream
Methods:
answer(stream?)- Answer the callreject()- Reject the callclose()- End the callon('stream', handler)- Handle remote streamon('close', handler)- Handle call closeon('error', handler)- Handle errors
Promise that resolves with the remote media stream. Rejects if call fails or is rejected.
try {
const remoteStream = await call.stream
videoEl.srcObject = remoteStream
} catch (error) {
console.log('Call failed:', error.message)
}Answer an incoming call, optionally with a local stream.
// With local stream
const localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
await call.answer(localStream)
// Without local stream (receive-only)
await call.answer()Reject an incoming call. The caller's call.stream promise will reject.
peer.on('call', async (call) => {
if (!userAccepts) {
await call.reject()
}
})Fires when remote stream is received.
Fires when the call closes.
Fires when a call error occurs.
End the call.
import { createPeer } from 'tinypeer'
// Caller
const caller = await createPeer({ id: 'alice' })
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
localVideoEl.srcObject = localStream
const call = caller.call('bob', localStream)
const remoteStream = await call.stream
remoteVideoEl.srcObject = remoteStream
// Callee (separate peer)
const callee = await createPeer({ id: 'bob' })
callee.on('call', async (call) => {
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
await call.answer(localStream)
const remoteStream = await call.stream
remoteVideoEl.srcObject = remoteStream
})
// Variations:
// Audio only: { audio: true, video: false }
// Screen share: navigator.mediaDevices.getDisplayMedia({ video: true })
// Receive only: await call.answer() without a streamimport { createPeer } from 'tinypeer'
const host = await createPeer({ id: 'room-host' })
const connections = new Map()
host.on('connection', async (conn) => {
connections.set(conn.peer, conn)
conn.on('data', (data) => {
// Broadcast to all other peers
for (const [id, connection] of connections) {
if (id !== conn.peer) {
connection.send({ from: conn.peer, data })
}
}
})
conn.on('close', () => {
connections.delete(conn.peer)
})
})For more examples, see the examples/ directory.
For advanced serialization (MessagePack, CBOR, etc.), compression, or encryption, see Custom Codecs.
For applications that only need data channels (no video/audio), you can use createDataPeer() for a smaller bundle size:
import { createDataPeer } from 'tinypeer'
const peer = await createDataPeer({ id: 'my-peer' })
// All connection features work the same
const conn = await peer.connect('other-peer')
await conn.send({ message: 'Hello!' })
peer.on('connection', (conn) => {
console.log(`${conn.peer} connected`)
})Benefits:
- Smaller bundle size 3.37KB (no media connection code)
- Same API as
createPeer()for data connections - Perfect for multiplayer games, chat apps, collaborative tools
What's different:
- No
call()method - data connections only - No
on('call')event - won't receive media calls
See the Migration Guide for API differences and examples.
Aims to support all modern browsers with WebRTC support.
Part of Peer.js's bundle size comes from support for old browsers and stabilising the WebRTC APIs across browsers. TinyPeer uses modern APIs and assumes a recent browser version (ES2020+).
If there is a notable browser quirk or bug, please open an issue.
MIT
Inspired by: