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

p2p

v0.12.0

Published

A lightweight library for creating peer-to-peer WebRTC conferencing with custom signaling drivers.

Readme

p2p

A tiny, signaling-agnostic library for building peer-to-peer WebRTC conferencing (media + data channels) with pluggable signaling drivers.

Key ideas:

  • Sender: creates outgoing RTCPeerConnections, publishes a local MediaStream, and optionally opens data channels to receivers.
  • Receiver: listens for offers, answers them, and exposes remote MediaStreams and incoming data messages.
  • Signaling driver: any object implementing on(namespace, handler), off(namespace, handler) and emit(namespace, message). This keeps the library transport-agnostic (WebSocket, pub/sub, in-memory, etc).

Why use p2p:

  • Minimal footprint and API surface for broadcasting a local stream and exchanging data.
  • Easy to test locally with an in-memory driver; swap to WebSocket or other drivers for production.
  • Handles ICE, offer/answer exchange, candidate buffering, and per-peer data channels.

Quickstart

Install:

npm install p2p

Run the demo (clone the repo if needed):

npm run dev

Open http://localhost:8000/demo/ in two browser tabs to see a simple video chat demo.

DEMO

Basic usage

Minimal in-memory signaling driver (useful for local testing):

// Minimal in-memory pub/sub driver
class MemoryDriver extends Map {
  constructor() { super(); }
  on(namespace, handler) {
    const k = namespace.join(':');
    if (!this.has(k)) {
      this.set(k, new Set());
    }
    this.get(k).add(handler);
  }
  off(namespace, handler) {
    const k = namespace.join(':');
    this.get(k)?.delete(handler);
  }
  emit(namespace, message) {
    const k = namespace.join(':');
    if (!this.has(k)) return;
    for (const h of this.get(k)) {
      try {
        h(message);
      } catch (e) {
        /* swallow errors */
      }
    }
  }
}

Signaling namespaces (contract)

  • Sender listens on: ['sender', room] and ['sender', room, senderId]
  • Sender emits to: ['receiver', room] and ['receiver', room, receiverId]
  • Receiver listens on: ['receiver', room] and ['receiver', room, receiverId]
  • Receiver emits to: ['sender', room] and ['sender', room, senderId]

Message types

  • invoke — discovery / request to connect (contains id, optional credentials)
  • offer — sender -> receiver with SDP offer and metadata
  • answer — receiver -> sender with SDP answer
  • candidate — ICE candidate exchange
  • dispose — end/tear-down

Receiver — listen for senders and attach incoming streams:

import { Receiver } from 'p2p';

const driver = new MemoryDriver();
const receiver = new Receiver({ driver });

receiver.addEventListener('stream', (e) => {
  const { id, stream, metadata } = e.detail;
  console.log('received stream from', id, metadata);

  // attach the received stream to a video element
  const video = document.createElement('video');
  video.autoplay = true;
  video.srcObject = stream;
  video.dataset.source = metadata.source || 'unknown';
  document.body.appendChild(video);
});

receiver.addEventListener('channel:message', (e) => {
  const { id, channel, data } = e.detail;
  console.log('msg from', id, channel.label, data);

  if (channel.label === 'chat') {
    console.log('chat message:', data);
    // respond to chat messages
    channel.send('ping');
  }
});

receiver.start({ room: 'demo-room' });
// receiver.stop();

Sender — capture local media, broadcast, and send messages:

import { Sender } from 'p2p';

const driver = new MemoryDriver();
const sender = new Sender({ driver });

sender.addEventListener('connect', (e) => {
  const { id } = e.detail;
  console.log('peer connected', id);
});

sender.addEventListener('channel:open', (e) => {
  const { id, channel } = e.detail;
  console.log('data channel opened', id, channel.label);

  if (channel.label === 'chat') {
    // send a message to the data channel
    channel.send('ping');
  }
});

sender.addEventListener('channel:message', (e) => {
  const { id, channel, data } = e.detail;
  console.log('msg from', id, channel.label, data);

  if (channel.label === 'chat') {
    // read a chat message from the data channel
    console.log('chat message:', data);
  }
});

navigator.mediaDevices.getUserMedia({ audio: true, video: true })
  .then((stream) => {
    sender.start({
      room: 'demo-room',
      stream,
      channels: {
        chat: { ordered: true }
      },
      metadata: { source: 'camera' },
    });
    // sender.stop();
  });

You can send a message to all connected peers via the 'chat' data channel:

sender.connections.forEach((conn) => {
  const channel = conn.channels.get('chat');
  if (channel && channel.readyState === 'open') {
    channel.send('hello peers!');
  }
});

API summary

Sender:

  • constructor(config: { driver, iceServers?, verify?, connectionTimeout?, audioBitrate?, videoBitrate? })
  • start({ room?, stream?, channels?, metadata? })
  • stop()

Events: connect, dispose, error, channel:open, channel:close, channel:error, channel:message

Receiver:

  • constructor(config: { driver, iceServers?, connectionTimeout?, pingInterval?, pingAttempts? })
  • start({ room?, credentials? })
  • stop()

Events: stream, connect, dispose, channel:open, channel:close, channel:error, channel:message

Troubleshooting & tips

  • Browser permissions: getUserMedia requires secure context (https or localhost) and user consent.
  • TURN servers: include TURN servers in iceServers for reliable connectivity across NATs.
  • Candidate buffering: the library buffers ICE candidates received before a connection is created.
  • Bitrate and codec hints: the library sets preferred codecs and bitrate where supported; browsers may ignore hints.
  • Debugging: use browser WebRTC internals and ICE/state events to diagnose connectivity issues.

Security & verification

  • Sender supports an optional verify() callback to accept/reject incoming invocations.
  • If you need authentication/authorization, implement it in your signaling layer and/or verify callback.
  • Always use secure signaling channels (e.g., WSS) to protect exchanged SDP and ICE candidates.