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

authkey.io

v1.3.1

Published

Unified voice calling SDK for the Authkey platform — one authkey, one call() API, browser (WebRTC) and Node.js (SIP/WebRTC via werift) auto-detected.

Readme

authkey.io

Unified voice calling SDK for the Authkey platform. One authkey, one call() API, browser and Node.js auto-detected.

const { voice } = require("authkey.io"); // or: import { voice } from "authkey.io"

const client = new voice({ authkey: "YOUR_AUTHKEY" });

client.on("incoming", async (call) => {
  await call.answer();
});

await client.connect();

const call = await client.call({ channel: "direct", from: "+9188xxxxxxx", to: "+919999999999" });

Install

npm install authkey.io

Bundlers that respect the package exports "browser" condition (Webpack, Rollup, Vite, esbuild) pull in the browser build automatically for a browser target; plain require("authkey.io") in Node resolves to the Node build.

Channels

Only "direct" is available in this release — a real phone number with a real backend behind it. "whatsapp" is a recognized constant but calling client.call({channel: "whatsapp", ...}) throws ChannelNotAvailableError: WhatsApp calling isn't available for external accounts yet.

How calls work

  • Inbound: client.on("incoming", call => ...) fires as soon as someone dials your number. Call call.answer() to pick up, call.reject() to decline. If the call reached your number because it was forwarded there by an upstream carrier/PBX, call.diversion is { from, reason } (the diverting party's SIP URI and the forwarding reason, e.g. "unconditional"/"no-answer"/"user-busy"); otherwise it's null.
  • Outbound: await client.call({ from, to }) places the call and resolves once it connects. Pass ringTimeoutMs (default 30000) to control how long to wait.

Once a call is answered/connected, use call.hangup(), call.hold(), call.resume(), call.mute(), call.unmute(), and call.sendDTMF("1234") the same way on either side.

Playing and receiving audio (Node.js)

Node has no microphone or speaker, so instead of native browser audio, this SDK exposes calls as raw PCM: 16-bit, mono, 16kHz, in 20ms frames (640 bytes each). You send audio by writing PCM buffers to the call; you receive audio by listening for PCM buffers as they arrive.

Requires ffmpeg on your system PATH (used to transcode your PCM to the codec the call actually negotiated, and back). If your ffmpeg binary isn't on PATH, set AUTHKEY_FFMPEG_PATH to its full path.

Echo cancellation is on by default. If you're building something that listens to on('audio') while also calling sendAudio() (e.g. an AI voice agent doing STT while it talks), the far end's own line/handset can echo your played audio back — this SDK adaptively cancels that out before you ever see it. Set AUTHKEY_ECHO_CANCEL=false to disable it, or tune AUTHKEY_ECHO_REF_DELAY_MS (default 300) / AUTHKEY_ECHO_FILTER_MS (default 60) if you're still seeing bleed-through — the right values depend on your actual call path's round-trip latency, so some tuning against a real call is expected. It also self-protects: it measures its own CPU cost against real time and permanently falls back to plain passthrough for that call if it can't keep up (tune the threshold with AUTHKEY_ECHO_MAX_LOAD, default 0.4) — so a slower deployment or a lot of concurrent calls degrades to "no echo cancellation" for that call, never to one-way or dropped audio.

Inbound audio is decoded with wallclock-based timestamps (ffmpeg's -use_wallclock_as_timestamps) rather than trying to derive timing from raw RTP-over-UDP alone, since the latter can sound robotic or drift out of sync.

Long-running calls and SIP re-INVITEs: a SIP session-timer refresh can arrive as a re-INVITE partway through a call. The SDK now safely tears down and restarts its own inbound pipeline if that happens (no leaked ffmpeg processes/sockets across restarts). If your deployment's re-INVITEs also change the RTP SSRC (FreeSWITCH does this in some configurations) and you see audio go dead partway through long calls specifically, see autoLatchSsrcOnReinvite under Node.js constructor options below.

Playing a sound file into a call

The simplest way to play an audio file (a greeting, an IVR prompt, hold music) is to convert it to raw PCM once with ffmpeg, then stream it in 20ms chunks:

# One-time conversion — any input format ffmpeg supports (mp3, wav, etc.)
ffmpeg -i greeting.mp3 -f s16le -ar 16000 -ac 1 greeting.pcm
const fs = require("fs");

async function playFile(call, pcmPath) {
  const pcm = fs.readFileSync(pcmPath);
  const FRAME_BYTES = 640; // 20ms @ 16kHz, 16-bit, mono
  for (let offset = 0; offset < pcm.length; offset += FRAME_BYTES) {
    if (call.isTerminal) break; // caller hung up mid-playback
    call.sendAudio(pcm.subarray(offset, offset + FRAME_BYTES));
    await new Promise((r) => setTimeout(r, 20)); // pace to real time, don't burst
  }
}

client.on("incoming", async (call) => {
  await call.answer();
  await playFile(call, "./greeting.pcm");
});

The 20ms pacing is important — sendAudio() expects frames roughly every 20ms, matching real-time playback speed. Sending everything at once will not work.

Receiving what the caller says

call.on("audio", (pcmChunk) => {
  // pcmChunk is raw 16-bit/mono/16kHz PCM — write it to a file, feed it to
  // a speech-to-text engine, whatever you need.
});

Text-to-speech on the fly

Any TTS engine that can output 16-bit/16kHz/mono PCM (or anything ffmpeg can read) works the same way — generate the audio, convert it with ffmpeg if needed, then stream it with the same chunking loop as above.

API reference

client.on("incoming", (call) => {});
client.on("connected" | "connecting" | "disconnected" | "reconnecting" | "reconnected" | "authenticationFailed", () => {});

const call = await client.call({ channel: "direct", from, to, ringTimeoutMs: 30000 });

call.id; call.channel; call.from; call.to; call.status; call.startTime; call.endTime;
await call.answer();
await call.reject();
await call.hangup();
await call.hold();
await call.resume();
await call.mute();
await call.unmute();
await call.sendDTMF("1234");

// Node.js only:
call.on("audio", (pcm) => {});
call.sendAudio(buffer);

Reporting

// same authkey, REST only, works from any runtime
const { RestClient } = require("authkey.io");
const rest = new RestClient("YOUR_AUTHKEY");
await rest.getCdrReport({ from: "2026-08-01", to: "2026-08-10" });
await rest.getSummaryReport();
await rest.getRecordingsReport();
await rest.getUsageReport();

Node.js constructor options

const client = new voice({
  authkey: "YOUR_AUTHKEY",
  iceServers: [{ urls: "stun:stun.l.google.com:19302" }], // or your own TURN server
  autoLatchSsrcOnReinvite: false, // opt-in, see below
});
  • iceServers — standard WebRTC RTCIceServer[]. If your Node process runs behind a NAT/firewall (most cloud deployments), pass at least a STUN server here — without it, ICE can only gather host candidates, which breaks inbound audio delivery for exactly that deployment shape. Defaults to [].
  • autoLatchSsrcOnReinvite — opt-in, defaults to false. Some SIP session-timer re-INVITEs (FreeSWITCH's included) change the RTP stream's SSRC mid-call; werift silently drops every packet on an SSRC it hasn't seen before, which can make a call go one-way-silent partway through with no error. Turning this on skips renegotiating on any re-INVITE that arrives after the call is already established, and instead just registers the new SSRC against the existing audio receiver. This is the right fix for a pure session-timer refresh, but wrong for a genuine hold/resume or codec change riding on a re-INVITE — this SDK can't reliably tell those apart from the SDP alone, so it's off by default. Only turn it on if you've confirmed your deployment's re-INVITEs are pure timer refreshes.

Known limitations (Node.js path)

  • DTMF send is not implemented. call.sendDTMF() emits the dtmf event locally but doesn't send tones over the wire yet.
  • Hold/resume are local-only. call.hold()/.resume() stop and restart your own audio flow; they don't yet renegotiate the call itself.
  • Each active Node.js call spawns ffmpeg processes for audio transcoding. This is fine at moderate call volumes; very high concurrent call counts will need a lower-overhead audio path in a future release.

Testing

npm test