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

@forinda/video-sdk-signaling-protocol

v0.2.0

Published

Pure protocol engine and wire-format zod schemas for the Forinda video SDK signaling layer

Readme

@forinda/video-sdk-signaling-protocol

Pure, transport-agnostic WebRTC signaling protocol engine + canonical wire-format zod schemas. Used by @forinda/video-sdk-core (browser) and every @forinda/video-sdk-signaling-adapter-* (server) as the single source of truth for messages and routing.

Install

pnpm add @forinda/video-sdk-signaling-protocol

Usage

import { defineSignalingEngine } from "@forinda/video-sdk-signaling-protocol";

const engine = defineSignalingEngine({
  authenticate: async (token, room) => verifyJwt(token),
  maxPeersPerRoom: 50,
});

const session = engine.openSession();
session.onSend((peerId, message) => {
  // your transport (WebSocket, EventSource, etc.) delivers `message` to the socket bound to `peerId`
});

// when a socket connects:
await session.handleConnection(socketId, { token: extractedToken });

// when a raw message arrives:
await session.handleMessage(socketId, rawJsonString);

// when a socket closes:
await session.handleDisconnect(socketId);

Wire format

A zod discriminated union covering join/leave, peer notifications, SDP/ICE, presence, chat, and EPIC-12 moderation. Validate any inbound message with SignalingMessage.parse(raw).

| type | direction | purpose | | ------------------- | ----------------- | ------------------------------------------------------------------------------ | | join | client → server | enter a room as publisher, viewer, presence, or director | | leave | client → server | exit a room voluntarily | | peer-joined | server → client | another peer arrived | | peer-left | server → client | another peer departed | | sdp | peer → peer (s) | offer / answer SDP exchange | | ice | peer → peer (s) | ICE candidate (or null end-of-candidates) | | presence-update | client → server | set / replace / delete this peer's presence attributes (null = delete) | | presence-state | server → client | broadcast on any peer's presence change; empty attributes = cleared | | presence-snapshot | server → joiner | one-shot full snapshot sent right after join | | chat | client ↔ peer(s) | broadcast text (no to) or DM (to: peerId) | | chat-history | server → joiner | one-shot replay (when joiner sets replayHistory: true and engine has buffer) | | mute | director → server | mute the target's audio or video (relayed + presence-state broadcast) | | unmute | director → server | clear a previous mute | | kick | director → server | force-remove the target (engine-enforced when enforcement is on) | | kicked | server → target | one-shot notification right before the engine drops the binding | | promote | director → server | add target to the room's director set | | demote | director → server | remove target from the room's director set | | set-bitrate | director → server | bandwidth ceiling hint relayed to the target |

Presence attribute values are JsonValue (recursive JSON shape). Use null to delete a key.

The engine maintains a per-room presence map; joiners always receive a presence-snapshot (empty peers: {} when nobody has set anything yet) and any subsequent presence-state events as they arrive.

Errors

All thrown errors extend SignalingProtocolError and carry a stable code:

| Class | code | When thrown | | -------------------------------- | ---------------------- | ----------------------------------------------------------------------------------- | | SignalingValidationError | signaling_validation | Invalid JSON or schema-failing message | | SignalingAuthError | signaling_auth | authenticate callback returned false | | RoomFullError | room_full | Room already at maxPeersPerRoom | | PeerNotFoundError | peer_not_found | SDP/ICE target peer is not registered | | SignalingRateLimitError | rate_limited | Per-peer chat / presence-update rate-limit bucket is exhausted | | SignalingDirectorConflictError | director_conflict | A second peer tried to join with role: "director" while a director already exists | | SignalingPermissionError | not_authorized | A non-director sent a moderation command and enforceModerationCommands is on |

Per-peer rate limits

const engine = defineSignalingEngine({
  rateLimit: { chatPerSec: 5, presenceUpdatesPerSec: 10 },
});

Each option enables a per-peer token bucket of size = capacity = refill rate. Burst allowance equals the per-second cap. Over-budget messages reject with SignalingRateLimitError(code: "rate_limited"); the message is NOT relayed. Buckets are released when the peer disconnects.

undefined (or 0) on either field disables that limiter independently — the chat budget can be capped without touching presence and vice versa.

Chat-history replay

const engine = defineSignalingEngine({ chatHistoryPerRoom: 50 });

Each room keeps a ring buffer of its last N chats. Joiners that send replayHistory: true on their join receive a one-shot chat-history message right after presence-snapshot — empty messages array if no history has accumulated yet.

Old clients omit the flag; they never see the new message type and continue working unchanged.

Director / moderation

const engine = defineSignalingEngine({ enforceModerationCommands: true });

The Role enum gains "director". The first peer to join a room with role: "director" claims it; subsequent claims throw SignalingDirectorConflictError. Co-directors are added at runtime via the promote command.

Six director-only commands ride the wire: mute, unmute, kick, promote, demote, set-bitrate.

  • Honor mode (default): the engine relays each command to the target and (for mute/unmute) updates the target's presence attributes so every peer sees the state change. The target's client decides whether to obey. Non-director senders are accepted.
  • Enforced mode (enforceModerationCommands: true): non-director senders are rejected with SignalingPermissionError(code: "not_authorized"). kick additionally removes the target from the room (forced disconnect on the host side).

mute / unmute encode state as presence attributes (director-muted-audio: true, director-muted-video: true) so late joiners see the current state via the existing presence-snapshot. No new state-snapshot wire type was added.

License

MIT — © 2026 Felix Orinda.