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

@decentnetwork/peer-webrtc

v0.2.10

Published

Realtime audio/video call signaling over Elastos Carrier — a reusable WebRTC signaling layer wire-compatible with the iOS/Android Beagle apps. Bring your own RTCPeerConnection (browser or node) and signaling transport (Carrier invite via @decentnetwork/pe

Readme

@decentnetwork/peer-webrtc

Realtime audio / video call signaling over Elastos Carrier — a small, reusable WebRTC signaling layer that is wire-compatible with the iOS / Android Beagle apps. Bring your own RTCPeerConnection (browser-native or a Node wrtc/werift) and your own signaling transport; this package owns the call state machine and the RtcSignal protocol.

  • Interops with native Beagle — same RtcSignal JSON, carried over the Carrier "carrier" friend-invite extension, offer-first handshake.
  • No hard WebRTC dependency — the RTCPeerConnection is injected, so the core runs in a browser (window.RTCPeerConnection) or in Node (a wrtc lib).
  • No hard Carrier dependency — the signaling transport is injected too. Use the bundled CarrierSignaling (wraps @decentnetwork/peer) for real calls, or BroadcastChannelSignaling for a zero-infra two-tab demo, or write your own.

Install

npm i @decentnetwork/peer-webrtc
# for real cross-network calls you also want the Carrier peer:
npm i @decentnetwork/peer

The call engine

import { CallEngine } from "@decentnetwork/peer-webrtc";
import { CarrierSignaling } from "@decentnetwork/peer-webrtc/carrier";
import { Peer } from "@decentnetwork/peer";

const peer = await Peer.create({ /* ... */ });
await peer.start();

const engine = new CallEngine({
  signaling: new CarrierSignaling(peer),                 // Carrier friend-invite bridge
  createPeerConnection: (config) => new RTCPeerConnection(config),
  getLocalMedia: (kinds) => navigator.mediaDevices.getUserMedia(kinds),
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" },
    { urls: "turn:tokyo.fi.chat:3478", username: "allcom", credential: "allcompass" },
  ],
});

// Receiving
engine.on("incomingCall", (info) => {
  if (confirm(`Call from ${info.peerId} — accept?`)) engine.accept(info.callId);
  else engine.reject(info.callId);
});
engine.on("remoteStream", (callId, stream) => { remoteVideo.srcObject = stream; });
engine.on("localStream",  (callId, stream) => { localVideo.srcObject  = stream; });
engine.on("ended", (callId, reason) => { /* teardown UI */ });

// Placing a call to a Carrier friend (their carrier id / user id)
const callId = await engine.call(friendCarrierId, { audio: true, video: true });
// ... later
engine.hangup(callId);

That's the whole surface: call(), accept(), reject(), hangup(), plus the incomingCall / stateChanged / localStream / remoteStream / ended events.

Browser demo (no server, no Carrier)

demo/index.html places a real audio/video call between two browser tabs using BroadcastChannelSignaling — it proves the engine + media path end-to-end with zero infrastructure:

npm run build
npx serve packages/peer-webrtc     # or any static server
# open demo/index.html in two tabs; copy one tab's id into the other and Call.

How it maps to the native protocol

| RtcSignal type | meaning | who sends | |---|---|---| | offer (with options: ["audio","video"]) | start a call | caller (call()) | | answer | accept + SDP | callee (accept()) | | candidate | trickle ICE | both, after the remote SDP is applied | | remove-candidates | prune ICE | either (ignored by browsers) | | bye (with reason) | hang up / decline / busy | either | | event (ringing/online) · action (accept/reject) | optional ring-first hints | either |

Signals are JSON, sent as the data of a Carrier friend-invite under the "carrier" extension — exactly what CarrierExtension.sendInviteFriendRequest / registerExtension do on iOS/Android. On the JS side that is peer.sendInvite(id, data, { ext: "carrier" }) / peer.onInvite(...) (added in @decentnetwork/peer ≥ 0.1.84).

Local ICE candidates are buffered until the remote description is applied (hasReceivedSdp), matching the native SDK so candidates never race ahead of the call setup.

Daemon ↔ browser split

The AgentNet desktop app runs Peer in a daemon and the UI (and therefore RTCPeerConnection) in a browser. Wire it by running CallEngine in the browser with a thin SignalingChannel that relays RtcSignals to the daemon over your existing IPC, and let the daemon hold the CarrierSignaling/Peer. The signaling payloads are plain JSON, so the bridge is just two postMessage-style hops — the same pattern used for text and inline files.

Offline calls (socket.io + push)

Carrier signaling needs the peer online on the Carrier network. To reach a peer whose app is backgrounded/offline (a phone), Beagle uses a second path — a socket.io signaling gateway plus a push notification that wakes the app. Both are provided here, byte-compatible with the Beagle apps:

  • SocketIoSignaling (@decentnetwork/peer-webrtc/socketio) — a SignalingChannel over the Beagle socket.io gateway (io.beagle.chat). Each call is a room keyed by callId; it emits new-channel to join and message to signal, exactly as WebRtcManager+SocketIO.swift does.
  • BeaglePushClient (@decentnetwork/peer-webrtc/push) — POSTs /push-api/push-message to ring an offline peer (VoIP push → CallKit / FCM).
import { io } from "socket.io-client";
import { CallEngine } from "@decentnetwork/peer-webrtc";
import { SocketIoSignaling } from "@decentnetwork/peer-webrtc/socketio";
import { BeaglePushClient } from "@decentnetwork/peer-webrtc/push";

const socket = io("https://io.beagle.chat", { transports: ["websocket"], reconnection: false });
const signaling = new SocketIoSignaling(socket, { userId: myCarrierUserId });
const engine = new CallEngine({ signaling, createPeerConnection, getLocalMedia });

// To call an OFFLINE peer: join the room, wake them, then place the call.
const push = new BeaglePushClient({ appKey: "<yourAppKey>", appName: "<yourAppName>" });
const callId = crypto.randomUUID();
signaling.joinCall(callId);
await push.ring(calleeUserId, { callerUserId: myCarrierUserId, callId, callerName: "Me", hasVideo: true });
await engine.call(calleeUserId, { audio: true, video: true, callId });

To RECEIVE an offline call, the app is woken by the push (payload carries the callId); call signaling.joinCall(pushedCallId) before answering so inbound signals for that room arrive.

Prefer Carrier when the peer is online (direct, no server); fall back to socket.io+push when it isn't. A simple strategy: try CarrierSignaling, and if the invite isn't acknowledged within a short timeout, ring over socket.io+push.

License

GPL-3.0-or-later