npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tinypeer

v0.2.0

Published

Tiny, modern WebRTC library compatible with PeerJS servers

Downloads

100

Readme

TinyPeer

npm version license bundlephobia minzipped size

[!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.

Why TinyPeer?

  • 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.

Installation

npm install tinypeer

Quick Start

Client connecting to peer

import { 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)
})

Host accepting connections

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}`)

API

createPeer(options?): Promise<Peer>

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 
})

Peer

Properties:

  • id - Your peer ID

Methods:

  • connect(peerId, options?) - Connect to another peer
  • call(peerId, stream, options?) - Call another peer with media
  • disconnect() - Close signaling connection
  • destroy() - Close all connections
  • on('connection', handler) - Handle incoming connections
  • on('call', handler) - Handle incoming calls

peer.connect(peerId, options?): Promise<Connection>

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
})

peer.call(peerId, stream, options?): MediaConnection

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.stream

peer.on('connection', handler)

Fires 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 })
})

peer.on('call', handler)

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
})

peer.disconnect()

Closes the signaling connection but keeps peer-to-peer connections alive.

peer.destroy()

Closes all connections and disconnects from the signaling server.

Connection

Properties:

  • peer - Remote peer ID
  • metadata - Optional connection metadata

Methods:

  • send(data, metadata?) - Send data to peer
  • close() - Close the connection
  • on('data', handler) - Handle incoming data
  • on('close', handler) - Handle connection close
  • on('error', handler) - Handle errors

connection.send(data, metadata?): Promise<void>

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' })

connection.on('data', handler)

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)
  }
})

connection.on('close', handler)

Fires when the connection closes.

connection.on('error', handler)

Fires when a connection error occurs.

connection.close()

Closes the connection.

MediaConnection

Properties:

  • peer - Remote peer ID
  • metadata - Optional call metadata
  • stream - Promise that resolves with remote MediaStream

Methods:

  • answer(stream?) - Answer the call
  • reject() - Reject the call
  • close() - End the call
  • on('stream', handler) - Handle remote stream
  • on('close', handler) - Handle call close
  • on('error', handler) - Handle errors

call.stream: Promise<MediaStream>

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)
}

call.answer(stream?): Promise<void>

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()

call.reject(): Promise<void>

Reject an incoming call. The caller's call.stream promise will reject.

peer.on('call', async (call) => {
  if (!userAccepts) {
    await call.reject()
  }
})

call.on('stream', handler)

Fires when remote stream is received.

call.on('close', handler)

Fires when the call closes.

call.on('error', handler)

Fires when a call error occurs.

call.close()

End the call.

Examples

Video 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 stream

Broadcasting to Multiple Peers

import { 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.

Advanced

Custom Data Encoding

For advanced serialization (MessagePack, CBOR, etc.), compression, or encryption, see Custom Codecs.

Data-Only Peer

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

Migrating from PeerJS

See the Migration Guide for API differences and examples.

Browser Support

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.

License

MIT

Acknowledgements

Inspired by: