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

@tsgwarp/webrtc

v0.1.0

Published

WARP Voice browser calling SDK — add a phone to your web app in minutes. Wraps SIP-over-WSS + WebRTC behind a clean Device/Call API.

Readme

@tsgwarp/webrtc

Add a phone to your web app in ~10 minutes. @tsgwarp/webrtc is the WARP Voice browser calling SDK — the Twilio Voice JS / @telnyx/webrtc experience for the WARP platform. It hides SIP and WebRTC entirely behind a small WarpClient / Call API. Your code never touches SDP, ICE, digest auth, or a WebSocket.

Install. Until @tsgwarp/webrtc is published to your registry, install the hosted tarball:

npm install https://tsg-voice-artifacts-staging.s3.amazonaws.com/sdk/tsg@tsgwarp/webrtc-0.1.0.tgz

Once published it's the usual:

npm install @tsgwarp/webrtc

It ships as ESM (bundle it with Vite / webpack / esbuild) with a CommonJS build for tooling that needs it. The only runtime dependency is sip.js (the SIP-over-WSS engine — see Architecture).


The 5-line client

import { WarpClient } from '@tsgwarp/webrtc';

const phone = new WarpClient({ token, apiBase: 'https://api.warp.tsgglobal.net' });
phone.on('registered', () => phone.call('+15551234567'));
phone.on('incoming', (call) => call.answer());
await phone.register();

token is the short-lived envelope your backend fetched from WARP (below). That's the whole browser side of an outbound + inbound softphone.


The tiny server side (mint a token)

Your browser must never hold a long-lived API key. Your backend authenticates the logged-in user and asks WARP for a short-lived (default 5-minute), single-connection WebRTC token:

// POST /v1/connections/:id/tokens on the WARP Connections API.
app.get('/warp-token', async (req, res) => {
  const r = await fetch(
    `https://api.warp.tsgglobal.net/v1/connections/${CONNECTION_ID}/tokens`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${WARP_API_KEY}` },
      body: JSON.stringify({ identity: req.user.id, ttl_seconds: 300 }),
    },
  );
  res.json(await r.json()); // { token, identity, sip_domain, expires_in, wss, ice }
});

The browser fetches /warp-token and passes the whole JSON to new WarpClient({ token }). CONNECTION_ID is a Connection of type: "webrtc" (create one with POST /v1/connections { name, type: "webrtc" }). Full runnable version: examples/server-token.js.


What a token is

POST /v1/connections/:id/tokens returns an ephemeral SIP digest credential — a fresh web-<rand> username + random secret, minted per session, installed live on the WARP SBCs, and auto-expiring. Your account's real password never reaches the browser (the Twilio capability model):

{
  "identity":   "web-5f9475dabcdb",
  "password":   "10GGC6u8sdoIr2k_JO8TmNc9",
  "sip_domain": "acme.warp.tsgglobal.net",
  "expires_in": 300,
  "wss":        "wss://acme.warp.tsgglobal.net",
  "ice":        "/v1/webrtc/config"
}

The SDK registers with identity + password (SIP digest over WSS — the golden SBC validates the MD5(identity:sip_domain:password) HA1, not a JWT). Pass the whole envelope to WarpClient (recommended — nothing is re-derived); pass apiBase so the relative ice path resolves.


API

new WarpClient(options) — alias Device

| option | type | notes | | --- | --- | --- | | token | string \| object | required — JWT string or the token envelope | | apiBase | string | customer-api base URL; required only if the token's ice is a relative path | | wss | string | override the SBC WSS URL (else derived from the token realm) | | audioElement | HTMLAudioElement \| string | element (or id) for remote audio; auto-created if omitted | | autoAnswer | boolean | auto-answer inbound calls (kiosk/agent mode) | | logLevel | 'error'\|'warn'\|'log'\|'debug' | passed to sip.js |

Methods: register(), call(destination, { audio, video })Call, updateToken(token) (re-REGISTER with a fresh token before the old one expires), disconnect().

Events: registered, unregistered, incoming (→ Call), call (outbound Call created), disconnect, connected, state (idleconnectingregistered→…), ice, warn, error.

Call

Methods: answer({ audio, video }), hangup(), mute(on?) / unmute(), hold(on?) / resume(), sendDigits('123#').

Properties: direction ('inbound'|'outbound'), remoteIdentity, state, muted, held, remoteStream, endReason.

Events: state (connectingringingactiveheldended), plus a convenience event per state (call.on('active', …), call.on('ended', …)), track, mute, dtmf, error.

destination may be E.164 (+15551234567), a bare number, or a full sip: URI. Bare numbers are routed through the account realm; the SBC applies the dialplan / LCR.


React

import { useWarpPhone } from '@tsgwarp/webrtc/react';

function Dialer({ token }) {
  const { status, dial, hangup, incoming, activeCall, callState, muted, mute } =
    useWarpPhone({ token, apiBase: 'https://api.warp.tsgglobal.net' });

  if (incoming) return <button onClick={() => incoming.answer()}>Answer {incoming.remoteIdentity}</button>;
  if (activeCall) return (
    <>
      <span>{callState}</span>
      <button onClick={hangup}>Hang up</button>
      <button onClick={() => mute(!muted)}>{muted ? 'Unmute' : 'Mute'}</button>
    </>
  );
  return <button disabled={status !== 'registered'} onClick={() => dial('+15551234567')}>Call</button>;
}

The hook builds the client, registers on mount (autoRegister, default on), tears it down on unmount, and re-registers when the token changes. react is an optional peer dependency. Full component: examples/react-dialer.jsx.


Architecture

your code ──▶ WarpClient ──▶ sip.js UserAgent ──▶ WSS ──▶ WARP SBC (proto_wss :5067)
                  │                                             │
                  ├── GET /v1/webrtc/config (STUN/TURN)         └── registers as a SIP endpoint
                  │                                                 at <account>.warp.tsgglobal.net
                  └── getUserMedia + RTCPeerConnection ──▶ coturn TURN relay ──▶ RTP media

SIP-over-WS engine: sip.js (sip.js@^0.21.2). Chosen over JsSIP because it's the more actively maintained modern codebase, has a clean SessionDescriptionHandler extension point for injecting our ICE config, ships first-class TypeScript types, and is already the engine used by the WARP reference softphone (services/webrtc-sample/index.html) — so the SBC WSS behaviour is already exercised against it. We wrap it entirely; sip.js is an internal implementation detail and never appears in the public API.

On register() the SDK: resolves the token → fetches ICE/TURN from /v1/webrtc/config (falls back to public STUN if unreachable) → opens the WSS transport to the SBC → REGISTERs the AoR sip:<identity>@<realm> → emits registered. Inbound INVITEs become Call objects via the incoming event; call() creates an outbound Call.


Auth model (token → REGISTER handshake)

The token is an ephemeral SIP digest credential — a fresh random identity + password minted per session by the customer-api, installed live on the WARP SBCs at mint time, and auto-expiring. The SDK registers over WSS with:

  • authorizationUsername = identity
  • authorizationPassword = password (the short-TTL secret from the token envelope)

The SBC's auth_db validates it exactly like any digest login — it checks the client's digest response against the stored HA1 = MD5(identity : sip_domain : password) that the mint just installed. When the token expires, a reaper removes that row from the SBC, so the credential stops working. There is no JWT and no stored account password in the browser (the golden OpenSIPS build has no jwt.so; digest is what it actually validates). This is the Twilio capability-token model, implemented with the SBC's native digest auth.

This is LIVE — the SBC WebRTC path is un-stubbed and two-way audio is proven; register() + call() work end-to-end against wss://‹account›.warp.tsgglobal.net. Nothing further is required on the SBC side; the SDK needs no configuration beyond the token envelope.


Notes

  • Browsers require HTTPS (or localhost) for getUserMedia; serve your app over TLS.
  • Tokens are short-lived. For long sessions, fetch a fresh token before expires_in elapses and call phone.updateToken(newToken) (or, in React, pass the new token — the hook re-registers).
  • TURN credentials from /v1/webrtc/config are ephemeral (HMAC, ~1h). The SDK fetches them fresh on every register().