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

@enjoys/voice-widget

v0.2.0

Published

Embeddable click-to-call widget for Enjoys Voice — browser WebRTC, API-key gated, locked to a single destination.

Readme

@enjoys/voice-widget

Embeddable click-to-call widget for Enjoys Voice. A visitor clicks a button and talks to a pre-configured number directly in the browser over WebRTC — no phone, no login. Calls are gated by an API key bound to your allowed domains (and, optionally, IPs), and locked to a single destination.

Script tag (no build step)

<script
  src="https://your-voice-domain/widget.js"
  data-enjoys-key="pk_live_xxxxxxxx"
  defer
></script>

Optional attributes: data-position (bottom-right | bottom-left), data-accent (CSS color), data-label, data-title, data-api-base.

From a CDN (jsDelivr / unpkg)

The package is published to npm, so jsDelivr and unpkg serve the self-initializing dist/widget.js bundle directly. When loading from a third-party CDN you must set data-api-base to your voice API origin — otherwise the widget derives it from the script's origin (the CDN), which is wrong:

<script
  src="https://cdn.jsdelivr.net/npm/@enjoys/[email protected]/dist/widget.js"
  data-enjoys-key="pk_live_xxxxxxxx"
  data-api-base="https://voice.yourdomain.com"
  defer
></script>

unpkg is equivalent — https://unpkg.com/@enjoys/[email protected]/dist/widget.js. Pin a version (@0.2.0) rather than @latest so the CDN can cache aggressively.

npm

npm install @enjoys/voice-widget
import { CallWidget } from "@enjoys/voice-widget";

const widget = CallWidget.init({ publicKey: "pk_live_xxxxxxxx" });

// The widget renders only after the key is validated. To react yourself:
widget.ready
  .then(() => console.log("ready"))
  .catch((err) => console.warn("widget unavailable:", err.message));

Options

| Option | Type | Default | Notes | | ------------- | --------------------------------- | ---------------------- | ------------------------------------------------ | | publicKey | string | — | Required. Publishable key (pk_…). | | apiBase | string | page origin | Voice API origin, e.g. https://voice.acme.com. | | autoButton | boolean | true | Render the floating button. | | position | "bottom-right" \| "bottom-left" | "bottom-right" | Button anchor. | | accentColor | string | #4f46e5 | Button / action color. | | buttonLabel | string | "Call us" | Floating button aria-label. | | title | string | key label/destination | Panel heading. | | onState | (state) => void | — | State transitions. | | onError | (error) => void | — | Validation / call errors. |

Drive it yourself (autoButton: false)

const widget = CallWidget.init({ publicKey: "pk_live_xxxx", autoButton: false });
await widget.ready;
myButton.onclick = () => widget.startCall();

Server-to-server callback (PSTN↔PSTN)

Instead of a browser leg, your backend can ask the API to ring the key's locked destination, then ring a visitor's phone, and bridge the two over the PSTN — a classic "request a callback" flow. This uses the secret key (sk_…) and must run only from your server (never the browser).

// Node / backend only — keep the secret server-side.
import { requestCallback } from "@enjoys/voice-widget";

const { callId } = await requestCallback({
  apiBase: "https://voice.yourdomain.com",
  publicKey: "pk_live_xxxxxxxx",
  secret: process.env.ENJOYS_SECRET!, // sk_live_…
  customerNumber: "+15551234567",      // the visitor to call back
});

Or with plain curl:

curl -X POST https://voice.yourdomain.com/api/n/widget/callback \
  -H "Authorization: Bearer sk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"publicKey":"pk_live_xxxxxxxx","customerNumber":"+15551234567"}'

The API responds immediately with { callId, status: "originating", … }; the legs then ring asynchronously and bridge once both answer. The destination is always the one locked to the key (never taken from the request), the key must be trunk-routed, and the per-key daily cap applies the same as the browser flow. Both PSTN legs are billable.

Security

The publishable key is safe to ship in the browser. The widget only works from the Origins you allow for the key, and every call is authorized by a short-lived capability token the server issues per call. The server-to-server secret (sk_…) must never appear in browser code.

Build

npm install
npm run build        # dist/index.{js,cjs,d.ts} + dist/widget.js (IIFE)
npm run build:cdn    # also copies dist/widget.js into ../../web/public/