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

rtc.io

v1.5.6

Published

Gold-standard WebRTC client for the browser. Peer-to-peer media streams, broadcast and per-peer DataChannels, perfect negotiation, ICE restart, backpressure, late-joiner stream replay — wrapped in a familiar socket.io emit/on API. Pairs with rtc.io-server

Readme

rtc.io

npm license docs

WebRTC peer-to-peer media and data channels with socket.io-style ergonomics.

npm install rtc.io rtc.io-server

rtc.io ships a browser client and a Node signaling server. The client extends socket.io-client, so if you already speak socket.io you'll feel at home — io(), socket.emit, socket.on. On top of that you get peer-to-peer media streams, broadcast and per-peer DataChannels, and built-in handling for the gnarly parts (perfect negotiation, ICE restarts, glare, transceiver reuse, flow control).

What rtc.io gives you

  • Built on socket.io. socket.emit('chat', msg) works exactly like socket.io — except the message rides a peer-to-peer DataChannel between browsers. Existing socket.io idioms (io(), emit, on, namespaces, the wire protocol) are unchanged because we extend the classes rather than re-implement them.
  • Perfect negotiation handled for you. The W3C polite/impolite pattern, stale-answer detection, manual rollback for older browsers, automatic ICE restart on connectionState === 'failed'. You write emit/on; the SDP/ICE machinery never reaches your code.
  • Disconnect detection that doesn't lie. A WebRTC liveness watchdog catches departed peers from ICE consent freshness, not from socket events alone — so a flaky signaling channel can't tear down a working P2P call. When the server also signals a peer-left, the watchdog tightens its window so tab-close cleanup is fast.
  • Multiple named channels per peer. socket.createChannel('telemetry', { ordered: false }) for unreliable low-latency, socket.peer(id).createChannel('rpc') for one-to-one, socket.createChannel('chat') for a broadcast every peer (including late joiners) shares. Built-in flow control with high/low watermarks and a backpressure-aware queue.
  • Streams as first-class. socket.emit('camera', new RTCIOStream(mediaStream)) — replays to late joiners automatically. Toggle tracks at runtime; transceivers are reused, no orphan senders.
  • Broadcast channels. One channel object, all peers, peer-left events, automatic cleanup. Like socket.io rooms but P2P.
  • No SDP wrangling. You never see an offer, answer, or candidate.

Quick start

Server

// server.ts
import { Server } from "rtc.io-server";

const server = new Server({ cors: { origin: "*" } });

server.on("connection", (socket) => {
  socket.on("join-room", ({ roomId, name }) => {
    socket.data.name = name;
    socket.join(roomId);
    // Tell every existing peer in the room to initiate an offer to the new one.
    socket.to(roomId).emit("#rtcio:init-offer", { source: socket.id });
  });
});

server.listen(3001);

Client — basic peer connection

import io, { RTCIOStream } from "rtc.io";

const socket = io("http://localhost:3001", {
  iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
});


const local = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
const camera = new RTCIOStream(local);

socket.server.emit("join-room", { roomId: "demo", name: "alice" });
socket.emit("camera", camera);

// You can ship app-level metadata alongside the stream in the same emit.
// The library walks the args looking for any RTCIOStream and preserves
// the rest of the payload verbatim:
socket.emit("camera", {
  stream: camera,
  metadata: { displayName: "Alice", userId: "abc123" },
});

// Receive remote streams.
socket.on("camera", ({ stream, metadata }: { stream: RTCIOStream; metadata: { displayName: string; userId: string } }) => {
  videoEl.srcObject = stream.mediaStream;
  label.textContent = metadata.displayName;
});

socket.on("peer-connect", ({ id }) => console.log("peer joined:", id));
socket.on("peer-disconnect", ({ id }) => console.log("peer left:", id));

Per-peer messaging

socket.peer(peerId).emit("hello", { from: "alice" });
socket.peer(peerId).on("hello", (msg) => console.log(msg));

Broadcast channel (every peer, ordered or unordered)

const chat = socket.createChannel("chat", { ordered: true });
chat.on("msg", (text: string) => console.log(text));
chat.emit("msg", "hi everyone");

Custom DataChannel with backpressure

const file = socket.peer(peerId).createChannel("file", {
  ordered: true,
  // All three knobs are per-channel and optional.
  queueBudget:   16 * 1024 * 1024,   // JS-side cap (default 1 MB)
  highWatermark: 16 * 1024 * 1024,   // pause threshold (default 16 MB)
  lowWatermark:   4 * 1024 * 1024,   // 'drain' fires at this level (default 1 MB)
});

file.on("open", () => {
  // Send chunks; the channel will pause and emit 'drain' when full.
  for (const chunk of chunks) {
    if (!file.send(chunk)) {
      await new Promise((r) => file.once("drain", r));
    }
  }
});

Defaults work for most apps. Lower highWatermark for tighter memory caps; raise it for fat-pipe LAN bulk transfers. Keep lowWatermark below highWatermark — otherwise 'drain' fires on every send and the throttling collapses. See Backpressure & flow control for the tuning guide.

API surface

| API | Use | |---|---| | io(url, opts) | Create a Socket; opts include iceServers and any socket.io-client option | | socket.emit(ev, ...args) | Broadcast over the ctrl DataChannel to all connected peers | | socket.on(ev, handler) | Listen to events from any peer | | socket.peer(id).emit/on/off | Targeted, per-peer messaging over the same ctrl channel | | socket.peer(id).createChannel(name, opts) | Open a custom DataChannel to one peer | | socket.createChannel(name, opts) | Broadcast DataChannel — every peer (and late joiners) shares it | | socket.server.emit/on | Escape hatch to talk to the signaling server directly | | RTCIOStream(mediaStream) | Wrap a MediaStream so it can be emit-ed and replayed to late joiners | | socket.untrackStream(stream) | Stop replaying a stream to future peers (already-connected peers unaffected; signal them at app level) | | socket.getStats(id), getSessionStats(id), getIceCandidateStats(id) | Per-peer WebRTC stats |

Reserved events

These are emitted by the library and filtered on receive so peers can't spoof them:

  • peer-connect — fires when a peer's ctrl DataChannel opens
  • peer-disconnect — fires when a peer's connection is torn down (only after peer-connect fired)
  • track-added — late-arriving track on an existing remote stream
  • track-removed — track was dropped from an existing remote stream (peer stopped a camera, ended a screen share, etc.)

Any event prefixed with #rtcio: is also reserved for internal signaling.

Where it sits in the ecosystem

rtc.io is built on top of socket.io — the client extends socket.io-client's Socket, and rtc.io-server extends socket.io's Server. If you already use socket.io, rtc.io is additive: every existing emit / on / namespace / room idiom works unchanged, and rtc.io adds peer-to-peer media and DataChannels behind the same API. We're enormously grateful to the socket.io maintainers — that part of the work is theirs, not ours.

The other libraries we love and learned from each take a different shape:

  • peerjs — a lovely, friendly API that has introduced more developers to WebRTC than probably anything else. Built around one DataChannel + one media slot per connection, which is exactly right for many apps. If your app fits that shape, it's a great choice.
  • simple-peer — a clean, well-tested wrapper around RTCPeerConnection that hands you offers/answers/candidates and lets you transport them yourself. Excellent if you already have a signaling protocol you like.
  • mediasoup, LiveKit, Janus, Jitsi — SFUs (Selective Forwarding Units). They terminate every stream on a server and forward it to subscribers. Right answer for 10+ person rooms, recording, server-side composition, simulcast, dial-in. rtc.io is happy to coexist with one in the same app.

We wrote rtc.io for a specific shape we kept building: small (≤ 8) groups of browsers that want multiple named DataChannels per peer (chat broadcast + cursor stream + file channel side-by-side), streams that flow through the same emit/on as everything else, late-joiner replay as a default, and DataChannel backpressure built-in. Those four were enough that we wanted them in the library rather than re-implemented in every app — so we wrote one. None of that means anything was wrong with the libraries above; it just means they were optimised for different shapes.

For a longer write-up of the use cases we built rtc.io for, see Why rtc.io.

⚠️ A note about the public signaling server

For prototyping, we host a free signaling server at server.rtcio.dev. It's shared with everyone using rtc.io for demos and prototypes — it has no authentication, no room ownership, and no notion of which app a connection came from. Anyone who joins a room with the same name will land in the same call, including strangers.

For prototyping, generate a hard-to-guess room id (a UUID, or 16+ random characters via crypto.randomUUID()). For anything you ship, run your own server — it's an npm install rtc.io-server and a 30-line file. That gives you authentication, persistence, and full control over who joins what.

Channel semantics

Custom channels (createChannel) use negotiated:true DataChannels with a deterministic SCTP stream id derived from the channel name. Both peers must call createChannel(name) for two-way communication — broadcast channels handle this automatically (every side that calls socket.createChannel(name) participates), per-peer channels need both ends to register.

If two channel names hash-collide on the same peer (rare; ~0.08% probability for under 100 names), createChannel throws a clear error naming both. Pick a different name.

License

MIT