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

@lightningrodlabs/webrtc-peer

v0.1.0

Published

Managed WebRTC peer connection for the browser: W3C Perfect Negotiation, a connection-lifecycle state machine, and a pluggable reconnection engine.

Readme

@lightningrodlabs/webrtc-peer

Bring your own P2P transport. Get a managed RTCPeerConnection per peer.

Give the library two callbacks — one to send a signal, one that you call when a signal arrives — and it takes care of establishing the RTCPeerConnection, keeping it alive, and surfacing the media. No signal server required, no assumptions about how the bytes get there.

const manager = new ConnectionManager({
  myAgentId: myPeerId,
  signaling: (to, msg) => myP2P.send(to, JSON.stringify(msg)),
});

myP2P.onMessage((from, raw) =>
  manager.deliverSignal(from, JSON.parse(raw)),
);

manager.ensureConnection(otherPeerId);
manager.on('remote-stream', ({ remoteAgent, data }) => attachVideo(remoteAgent, data));

That is the complete integration. Works with Holochain remote signals, libp2p streams, a plain WebSocket — anything that can ship an opaque JSON blob from one peer to another.

Why this exists

Raw RTCPeerConnection gives you primitives, not a connection. To ship reliable P2P media you have to solve, yourself:

  • Offer collisions (glare). Both peers negotiating at once corrupts signaling state. The W3C Perfect Negotiation pattern handles this.
  • Reconnection. ICE paths drop. Knowing when to ICE-restart vs. full-reconnect, with backoff, is non-trivial — and tearing down too eagerly lands you back on the same broken path.
  • Lifecycle. ICE / DTLS / signaling / data-channel states each have their own machine; your UI needs one coherent answer to "what is this connection doing?"
  • Forensics. When a connection fails in the field, you need a structured trail, not scattered console logs.

This library solves these once, behind a small signaling-agnostic API. It does not do peer discovery, identity, authentication, or signal transport — those belong to your P2P substrate. The library cares only about WebRTC, and trusts your transport to deliver SDP and ICE messages between two known peers.

What you get

  • Perfect Negotiation — W3C polite/impolite pattern, glare handling, ICE-candidate queueing, trickle and non-trickle ICE.
  • Lifecycle FSM — one ConnectionPhase (idle → signaling → connecting → connected → reconnecting → disconnected → failed → closed) with guarded transitions. Subscribe to phases, not raw browser states.
  • Two-tier reconnection — fast ICE-restart first, then full reconnect, with quadratic backoff + jitter. Bring your own ReconnectPolicy to override.
  • Multi-peer ConnectionManager — one object owns every peer, routes signals, propagates local media, exposes an aggregate view model for room UI.
  • Reactive view models — phase, progress, retry context, connection quality (relayed? candidate type?), track flow, a composite healthy flag.
  • Structured forensics — every transition emits an FSMTransitionEntry with a full TransportSnapshot (ICE / DTLS / signaling / gathering / data-channel). TransitionRecorder captures a ring buffer you can dump on failure.
  • onPeerCreated hook — get the bare RTCPeerConnection before any tracks are attached, to install simulcast transceivers, codec preferences, etc.
  • Zero runtime dependencies. Browser WebRTC APIs only. Fully typed. Testable — inject a mock RTCPeerConnection via createPeerConnection.

Install

npm install @lightningrodlabs/webrtc-peer

Plugging into a P2P transport

The library accepts signaling in either of two forms. Pick the one that matches your transport's shape.

Send-callback form (recommended for most P2P transports)

P2P substrates typically deliver messages via "I receive a message, dispatch it" — not a subscription model. Pass a SignalSender function and call manager.deliverSignal(from, message) when an inbound message arrives:

import { ConnectionManager } from '@lightningrodlabs/webrtc-peer';

const manager = new ConnectionManager({
  myAgentId: 'my-stable-peer-id',
  signaling: (to, msg) => myP2P.send(to, JSON.stringify(msg)),
});

myP2P.onMessage((from, raw) => manager.deliverSignal(from, JSON.parse(raw)));

Holochain remote signals

const manager = new ConnectionManager({
  myAgentId: encodeHashToBase64(myAgentPubKey),
  signaling: (to, msg) =>
    roomClient.sendMessage([decodeHashFromBase64(to)], 'Sdp', JSON.stringify(msg)),
});

// in your AppSignal handler:
if (signal.type === 'Sdp') {
  manager.deliverSignal(encodeHashToBase64(signal.from), JSON.parse(signal.payload));
}

Adapter form

If your transport already exposes a clean subscription API, implement SignalingAdapter and pass that instead. The library will call onSignal itself.

const manager = new ConnectionManager({
  myAgentId,
  signaling: {
    sendSignal(to, msg) { /* ... */ },
    onSignal(handler) { /* return unsubscribe */ },
  },
});

What the library expects of your transport

Minimal. The wire format is a small SignalMessage JSON envelope (offer | answer | candidate | leave) that you ship opaquely between two peers.

  • Authenticated peer identity. You give the library a stable from per inbound signal. The library uses string comparison on peer ids to assign polite/impolite roles. Identity is your transport's job.
  • Agent-to-agent delivery. No broadcast required. The library only ever sends to one peer at a time.
  • Best-effort, not exactly-once. The library tolerates loss, reordering and duplicates. A connection-scoped peerSessionId filters stale signals from previous peer sessions.

You do not need:

  • A reliable ordered channel.
  • A signal server, relay, or rendezvous service.
  • Anything beyond "deliver this byte string to that peer."

Reconnection

DefaultReconnectPolicy uses quadratic backoff with jitter and a two-tier strategy: the first attempts use ICE restart (fast, preserves DTLS), then it switches to full reconnect; DTLS failures always go straight to full reconnect. Override by passing any ReconnectPolicy implementation to ConnectionManager.

Configuring the RTCPeerConnection

ConnectionConfig.iceServers carries STUN/TURN servers. ICE handles relay fallback automatically when host/srflx paths fail. Set iceTransportPolicy: 'relay' to force TURN-only.

For lower-level configuration (simulcast, codec preferences, custom transceivers), pass onPeerCreated — it fires once per peer session with the bare RTCPeerConnection, before any local tracks are attached:

const manager = new ConnectionManager({
  myAgentId,
  signaling: send,
  onPeerCreated: ({ pc, remoteAgent }) => {
    pc.addTransceiver('video', {
      direction: 'sendrecv',
      sendEncodings: [
        { rid: 'h', maxBitrate: 1_200_000 },
        { rid: 'm', maxBitrate: 300_000, scaleResolutionDownBy: 2 },
        { rid: 'l', maxBitrate: 100_000, scaleResolutionDownBy: 4 },
      ],
    });
  },
});

Forensics

onTransition is the firehose: one structured FSMTransitionEntry per transition, each carrying timestamp, connection id, from/to phase, trigger string, peer-session id, and a TransportSnapshot of all underlying browser states. TransitionRecorder keeps the last N entries; dump() / toJSON() produce a portable record for bug reports. The library never writes to console — pass a logger (Logger) if you want recovered errors and warnings surfaced.

const recorder = new TransitionRecorder({ capacity: 500 });
const manager = new ConnectionManager({
  myAgentId,
  signaling: send,
  onTransition: (entry) => recorder.record(entry),
});

window.onerror = () => navigator.clipboard.writeText(recorder.toJSON());

Lower-level API

ConnectionManager is the recommended entrypoint. For a single connection or custom orchestration, use PeerConnectionFSM (one peer, full lifecycle) or RTCPeer (a thin Perfect-Negotiation wrapper over RTCPeerConnection) directly.

Platforms

Targets the W3C RTCPeerConnection API. Works in any browser, in Electron's renderer process, and — via react-native-webrtc's globalThis polyfills — in React Native. The createPeerConnection factory lets you inject a non-global constructor explicitly if needed.

Testing

The library never constructs an RTCPeerConnection directly — it goes through an injectable createPeerConnection factory. Pass a mock to run the full FSM headless in Node, with no browser or DOM environment. See src/__tests__/ for the suite (160+ tests, including two-peer integration).

License

MIT