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

@didlogic/voice-sdk

v1.0.1

Published

DIDLogic Voice SDK — drop-in WebRTC dialer widget and headless SIP client for browser-based softphone integration.

Readme

@didlogic/voice-sdk

Drop-in WebRTC dialer widget and headless SIP client for embedding on a DIDLogic partner's website. The partner's customers place calls straight from the page; the SIP infrastructure and billing live on the DIDLogic side, under the partner's account.

Contents

Architecture

   ┌──────────┐   POST {command:"call"}   ┌────────────────┐   POST + api_token   ┌──────────────────┐
   │ Browser  │ ────────────────────────► │ Your backend   │ ───────────────────► │   DIDLogic API   │
   │ <dl-...> │                           │  (proxy)       │                      │  /mobile/api/sdk │
   └──────────┘ ◄──────────────────────── └────────────────┘ ◄─────────────────── └──────────────────┘
                {session_id, session_pwd,                     {session_id, session_pwd,
                 wss_url, expires_in}                          wss_url, expires_in}

The key principle: the api_token (a.k.a. SdkApiToken) lives on your backend only. The widget talks exclusively to your proxy; the proxy adds the token and forwards the request to DIDLogic. The widget gets back ephemeral SIP credentials (TTL ~1 hour), registers over WSS, and places the call.

Install

npm install @didlogic/voice-sdk

Quickstart — ESM

import '@didlogic/voice-sdk'

document.body.innerHTML = `
  <dl-dialer
    proxy-url="https://your-backend.example.com/webrtc"
    brand-name="Acme Phone"
  ></dl-dialer>
`

The side-effect import registers every custom element. After that, <dl-dialer> renders from any framework as a regular web component. The proxy-url attribute is required — it points to your backend endpoint that issues short-lived SIP credentials (see Setup on your backend).

Quickstart — React

import '@didlogic/voice-sdk'

export function PhoneWidget() {
  return (
    <dl-dialer
      proxy-url="https://your-backend.example.com/webrtc"
      brand-name="Acme Phone"
    />
  )
}

The package automatically augments JSX.IntrinsicElements for React 18 (global JSX) and React 19 (the react module's JSX), so <dl-dialer> is recognised in TSX with no extra declaration.

For other JSX runtimes (Vue / Svelte / Solid / Preact), add your own augmentation against the widget's exported classes.

Setup on the DIDLogic portal

To let the widget place calls, you need to create an SdkApiToken in your DIDLogic account — a long-lived bearer token your backend will inject into requests.

What SdkApiToken is

  • 1 token = 1 DIDLogic user (the user calls are placed and billed against).
  • Bound to the SIP friend / DID the widget registers under.
  • Can be enabled / disabled / revoked at any time — deactivation tears down active sessions immediately.

How to get one

TL;DR: contact DIDLogic support with your account login — we'll create the token and deliver it through a secure channel. A self-service UI is on the roadmap but not available in v1.0.

Once you have the token:

  1. Put it in your backend secrets (env, vault — anything, as long as it's not in the browser and not in git).
  2. Make sure the user the token is bound to has a SIP friend and an active DID with outbound calling enabled.
  3. DIDLogic-side rate limits:
    • 60 requests per minute per token
    • 120 requests per minute per IP Exceeding either → 429 Too Many Requests.
  4. If the user is blocked (negative balance, suspended), DIDLogic returns 403 Forbidden.

How API errors surface in the widget

| HTTP from DIDLogic | What <dl-dialer> shows | | ------------------------ | -------------------------------------------------------------- | | 401 Unauthorized | "Authentication failed" (token invalid / revoked) | | 403 Forbidden | "Access denied" (user blocked, limits) | | 429 Too Many Requests | "Too many requests, please retry later" | | Any 5xx | Auto-retry with exponential backoff (cap 5 min, up to 10 tries) |

Setup on your backend (proxy)

The proxy is a single HTTP endpoint on your side that:

  1. Accepts a POST from the widget with JSON { "command": "call" }.
  2. Adds an api_token field (value from env) to the body.
  3. Forwards the request to https://<DIDLogic-host>/mobile/api/sdk/command.
  4. Returns the DIDLogic response as is.

Contract

From the widget to your proxy:

POST /webrtc        HTTP/1.1
Content-Type: application/json

{ "command": "call" }

From your proxy to DIDLogic:

POST /mobile/api/sdk/command        HTTP/1.1
Content-Type: application/json

{ "command": "call", "api_token": "<SDK_API_TOKEN>" }

DIDLogic response (forwarded to the widget verbatim):

{
  "session_id":  "1001@your-tenant",
  "session_pwd": "<short-lived-secret>",
  "wss_url":     "wss://sip.didlogic.com:7443",
  "expires_in":  3600
}

Reference proxy in Node.js (zero dependencies)

// proxy.mjs — node 18+
import { createServer } from 'node:http'

const PORT = process.env.PORT ?? 3000
const SDK_API_TOKEN = process.env.SDK_API_TOKEN
const UPSTREAM = process.env.UPSTREAM_URL ?? 'https://didlogic.com/mobile/api/sdk/command'
const ALLOW_ORIGIN = process.env.ALLOW_ORIGIN ?? '*'

if (!SDK_API_TOKEN) {
  console.error('SDK_API_TOKEN env is required')
  process.exit(1)
}

const cors = {
  'Access-Control-Allow-Origin': ALLOW_ORIGIN,
  'Access-Control-Allow-Headers': 'Content-Type',
  'Access-Control-Allow-Methods': 'POST, OPTIONS',
}

createServer(async (req, res) => {
  if (req.method === 'OPTIONS') return res.writeHead(204, cors).end()
  if (req.method !== 'POST' || req.url !== '/webrtc') {
    return res.writeHead(404, cors).end()
  }

  const chunks = []
  for await (const chunk of req) chunks.push(chunk)
  const body = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}')

  const upstream = await fetch(UPSTREAM, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ...body, api_token: SDK_API_TOKEN }),
  })

  const text = await upstream.text()
  res.writeHead(upstream.status, {
    'Content-Type': upstream.headers.get('content-type') ?? 'application/json',
    ...cors,
  })
  res.end(text)
}).listen(PORT, () => console.log(`proxy on :${PORT}/webrtc`))

Run it:

SDK_API_TOKEN=<token> ALLOW_ORIGIN=https://your-app.example.com node proxy.mjs

⚠️ Never put SDK_API_TOKEN in the browser. Not in an HTML attribute, not in a JS bundle, not in a frontend env file. Any leak of the token into client code = compromise. Keep it in server-side env only.

What the proxy must do (minimum)

  • Accept only POST on its public path.
  • Handle the CORS preflight (OPTIONS) for the domain hosting the widget.
  • Inject api_token from a server-side secret.
  • Forward DIDLogic's body and status code as is — otherwise the widget can't handle 401/403/429 correctly.

What the proxy may do (optional)

  • Authenticate the call via your existing session cookie / JWT — to issue SIP credentials only to logged-in users.
  • Log, rate-limit per your own users, or rewrite command based on business rules.
  • Audit who's hitting the widget and how often.

<dl-dialer> attributes

| Attribute | Type | Default | Description | | ------------------------ | --------- | ------------ | ----------------------------------------------------------------- | | proxy-url | string | (required) | URL of your proxy endpoint (see above) | | brand-name | string | "Phone" | Title shown in the widget header | | number | string | "" | Optional initial number to prefill the dialpad on first paint | | click-to-call | boolean | false | Scan the host page and intercept clicks on tel: links | | click-to-call-selector | string | "" | CSS selector limiting the click-to-call scan area |

For per-element autocomplete in IDEs that consume the Custom Elements Manifest (VS Code + lit-plugin, WebStorm, Storybook), use the custom-elements.json shipped with the package.

Events

<dl-dialer> is self-contained: it owns its UI state and talks to the SIP stack internally. No custom events are emitted on the element itself.

If you need call lifecycle events (registered, accepted, ended, failed, …), use the Headless API. Device and VoiceCall expose typed event emitters.

Theming

CSS custom properties applied to <dl-dialer> or any of its ancestors:

| Variable | Default | Purpose | | ---------------------- | ---------------------------------------- | -------------------------------- | | --dl-primary | #447bf2 | Accent (digits on the display) | | --dl-success | #66d391 | Call button background | | --dl-danger | #ed2643 | Hangup button background | | --dl-danger-hover | #d41e38 | Hangup hover state | | --dl-bg | #ffffff | Widget background | | --dl-surface | #f0f0f0 | Inner surface tint | | --dl-surface-hover | #e4e4e4 | Inner surface hover | | --dl-text | #121011 | Primary text | | --dl-text-secondary | #9298a3 | Secondary text | | --dl-border | #e7eaf0 | Borders, separators | | --dl-font-family | KlavikaBasicRegular, Arial, sans-serif | Default UI font | | --dl-font-mono | PTMonoRegular, Arial, serif | Monospace (digits) | | --dl-radius | 6px | Default corner radius | | --dl-radius-round | 50% | Round button radius | | --dl-width | 360px | Widget width | | --dl-height | 680px | Widget height | | --dl-sheet-height | 100% | Bottom-sheet (DTMF) height |

dl-dialer {
  --dl-primary: #ff6b00;
  --dl-success: #00aa44;
  --dl-width: 320px;
  --dl-height: 600px;
}

The widget ships with its own fallback fonts. To restyle, override --dl-font-family / --dl-font-mono with your own stack.

Headless API

For non-widget integrations the SDK exposes the underlying classes:

import { Device, CallCredentialsManager } from '@didlogic/voice-sdk'

const credentials = new CallCredentialsManager({
  proxyUrl: 'https://your-backend.example.com/webrtc',
})
const device = new Device()

credentials.onCredentials = (creds) => device.applyCredentials(creds)
credentials.onError = (err) => console.error('credentials error', err)
await credentials.fetch()

device.on('registered', () => console.log('SIP registered'))
device.on('registrationFailed', (e) => console.error('register failed:', e.cause))

const call = await device.call('+15551234567')
call.on('accepted', () => console.log('connected'))
call.on('ended',    () => console.log('hung up'))
call.on('failed',   () => console.log('call failed'))

call.mute()
call.sendDTMF('5')
call.hangup()

CallCredentialsManager on its own:

  • caches the current credentials and refreshes them automatically refreshBeforeExpirySec (default 60) before expires_in;
  • retries refresh failures with exponential backoff (cap 5 min, up to 10 attempts);
  • supports pauseRefresh() / resumeRefresh() (the widget pauses while a call is active so a re-register does not drop the line).

Full export list

Runtime classes:

  • Device, VoiceCall, AudioManager — the low-level SIP/WebRTC stack (re-exported from @didlogic/voice-core)
  • CallCredentialsManager — proxy-backed authentication
  • Dialer — the widget class itself, in case you want to subclass it
  • Screen classes: Dialpad, Keypad, CallScreen, CallControls, CallerInfo, SettingsPanel
  • Reusable components: AppBar, Select, IconButton, BottomSheet
  • ClickToCallScanner — programmatic page scanning

Type-only:

DeviceOptions, DeviceCredentials, AudioDeviceInfo, DeviceEventMap, CallEventMap, CallCredentials, CallCredentialsError, CallCredentialsManagerOptions, CallStatus, SelectOption, IconButtonVariant, ClickToCallOptions.

Click-to-Call

The click-to-call attribute turns on host-page scanning and intercepts clicks on tel: links and elements carrying data-phone / data-tel / data-number:

<dl-dialer
  proxy-url="https://your-backend.example.com/webrtc"
  click-to-call
  click-to-call-selector=".article-body"
></dl-dialer>

<a href="tel:+15551234567">Call us</a>
<span data-phone="+15551234567">+1 555 123-4567</span>

The scanner:

  • catches tel: hrefs and elements with data-phone | data-tel | data-number;
  • finds bare phone numbers in text nodes via a regex;
  • uses MutationObserver — works in SPAs without page reloads;
  • injects a <style id="dl-click-to-call-styles"> with a CSS class .dl-click-to-call (with a hover state). Colors are configurable via --dl-click-to-call-color / --dl-click-to-call-hover-color.

For programmatic use, see ClickToCallScanner in the Headless API.

Browser support

Modern evergreen browsers with WebRTC and Web Components:

  • Chrome / Edge ≥ 100
  • Firefox ≥ 100
  • Safari ≥ 15

WebRTC requires HTTPS (or localhost). Microphone access is requested on the first call.

Versioning

Semantic Versioning. Breaking changes ship in major releases only. The documented attributes, events and CSS variables are the public API; anything not described here is internal and may change at any time.

License

MIT — see LICENSE.