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

@dialstack/sdk-native

v0.2.0

Published

React Native softphone components for the DialStack SDK

Readme

@dialstack/sdk-native

The React Native softphone components for the DialStack SDK — a headless SoftphoneProvider, a batteries-included <Softphone>, and the composable pieces (<DialPad> / <IncomingCall> / <OngoingCall>).

import { Softphone, SoftphoneProvider } from '@dialstack/sdk-native';

React components for the web live in @dialstack/sdk-react. This package is self-contained: install it alone, and nothing from the web SDK comes with it.

Install

npm install @dialstack/sdk-native \
  'github:dialstack/react-native-webrtc#7e9a0eb55e068b2ba452f22e02e4b789aff9e4ed' \
  react-native-incall-manager react-native-svg libphonenumber-js

These are peer dependencies — install the versions that match your app.

Install exactly one WebRTC package. Two of them autolink two copies of the native library and the Android build fails with Duplicate class org.webrtc.*.

We recommend DialStack's fork at the pinned commit above: it is the only build that bridges DTMF (see below). If your app already ships a different WebRTC package — LiveKit's, Stream's, or stock react-native-webrtc — keep that one instead and skip ours. The SDK never imports a WebRTC package; it reads the globals registerGlobals() installs, so any spec-compliant build works. The react-native-webrtc peer is optional precisely so a differently-named package can satisfy it without npm installing a second copy alongside.

Installing our fork from GitHub: its entry points are generated by its prepare script at install time. If your npm or CI blocks install scripts, the package installs without them and the failure surfaces later as Cannot find module, not as an install error — allow install scripts for that dependency.

Install the WebRTC globals

The SDK core is written to the standard browser WebRTC surface (RTCPeerConnection, MediaStream, navigator.mediaDevices), so call registerGlobals() from your WebRTC package at your app's entry point, before anything imports the SDK:

// index.js
import { registerGlobals } from 'react-native-webrtc';
registerGlobals();

Two RN-only gaps — outbound ringback audio and E911 persistence — are supplied by the softphone provider (an InCallManager-backed ringback and your storage adapter).

Permissions (required)

Without microphone permission a call fails with Call failed and no other symptom — getUserMedia never runs, so there is nothing else to see. Declare these before your first call.

Expo

{
  "expo": {
    "plugins": ["@config-plugins/react-native-webrtc"],
    "android": {
      "permissions": [
        "android.permission.RECORD_AUDIO",
        "android.permission.MODIFY_AUDIO_SETTINGS",
        "android.permission.INTERNET",
        "android.permission.ACCESS_NETWORK_STATE",
        "android.permission.VIBRATE"
      ]
    },
    "ios": {
      "infoPlist": {
        "NSMicrophoneUsageDescription": "… needs the microphone to place and receive calls.",
        "UIBackgroundModes": ["audio", "voip"]
      }
    }
  }
}

react-native-webrtc is a native module, so it needs a development build — it cannot run in Expo Go. Re-run expo prebuild after changing any of the above.

The config plugin also injects permissions of its own that this SDK does not use — including CAMERA, SYSTEM_ALERT_WINDOW and BLUETOOTH — and writes a default camera-usage string. If a store listing or privacy review asks about them, drop the ones you don't need with Expo's android.blockedPermissions.

Bare React Native

android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />

VIBRATE is what gives the incoming call its haptic buzz: Android enforces it by throwing rather than no-op'ing, so without it the call still rings but never vibrates. The SDK catches that throw, so omitting the permission degrades gracefully — it does not break the call. Neither react-native nor react-native-incall-manager declares it, so your app must.

ios/<YourApp>/Info.plist:

<key>NSMicrophoneUsageDescription</key>
<string>… needs the microphone to place and receive calls.</string>
<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
  <string>voip</string>
</array>

DTMF

DTMF (RFC 4733 telephone-event over the media path) requires DialStack's fork of react-native-webrtc, which adds the RTCRtpSender.dtmf bridge that no published build ships:

npm install 'github:dialstack/react-native-webrtc#7e9a0eb55e068b2ba452f22e02e4b789aff9e4ed'

With any other WebRTC package — including LiveKit's and Stream's forks, neither of which exposes RTCRtpSender.dtmf — Call.canSendDtmf is false, the in-call keypad stays hidden, Call.sendDtmf() throws call_failed, and the SDK logs [dialstack] DTMF is unavailable … once per app process. Calls are otherwise unaffected, but there is no way to navigate an IVR or phone tree. That is the tradeoff to weigh if another library forces a particular WebRTC package on you.

Storage (required)

<SoftphoneProvider> requires a storage prop — a small PlatformStorage adapter used to persist the selected E911 address id across launches. The SDK takes no persistence dependency of its own (react-native-mmkv and AsyncStorage vary across versions/architectures and can't be defaulted safely), so you supply one:

import type { PlatformStorage } from '@dialstack/sdk-native';
import { createMMKV } from 'react-native-mmkv';

const mmkv = createMMKV();
const storage: PlatformStorage = {
  getItem: (k) => mmkv.getString(k) ?? null,
  setItem: (k, v) => mmkv.set(k, v),
  removeItem: (k) => mmkv.remove(k),
};

<SoftphoneProvider token={token} storage={storage}>
  <Softphone />
</SoftphoneProvider>;

storage just needs getItem/setItem/removeItem.

Call audio

By default <SoftphoneProvider> owns call audio through react-native-incall-manager: it rings inbound calls, plays the outbound ringback, and holds the audio session (which also turns on the proximity sensor) while a call is connected. The ringback goes only to a phone the provider creates from token: a phone passed as existingPhone keeps the ringback it was created with, so pass one to createPhone({ ringback }) yourself.

When the operating system owns the call instead, because you report calls to CallKit or Telecom, the provider must stay out of it: the OS rings the phone and activates the session itself, and ringing on top of it costs the answered call its microphone. Set callAudio="host" and the provider does none of the above. With a bridge it is always "host". Anything you still want, such as the proximity sensor, is then yours to drive (InCallManager.startProximitySensor() does not touch the audio session).

The output route is yours too. Without an audioOutput controller the in-call speaker button is disabled. To enable it, pass one built over your OS integration:

const audioOutput: AudioOutputController = {
  current: () => lastReportedRoute, // what the OS reports, same object until it changes
  onChange: (listener) => subscribeToRouteChanges(listener),
  setSpeaker: (on) => requestSpeaker(on),
};

<SoftphoneProvider bridge={bridge} audioOutput={audioOutput}>
  …
</SoftphoneProvider>;

The button shows only the route the OS reports. A switch made from the CallKit or Telecom UI, a headset, or a car therefore shows up in-app as well. useAudioOutput() exposes the same state if you build your own UI.

Example apps

Three runnable apps, all wired with the permissions and plugin above:

  • Expo — ships the MMKV storage adapter
  • Bare React Native — ships an AsyncStorage adapter
  • OS integration — the worked example for the OS call surface: calls on a locked or killed phone via push wake, OsCallAdapter over expo-callkit-telecom. Built and verified on both Android (FCM + Telecom) and iOS (PushKit + CallKit).

Scope

This package provides the in-app calling surface — the provider, the call state, and the React Native UI — plus the bridge between the SDK and the operating system's own call experience.

Binding the two remains the integrator's work, but not from scratch: NativeCallBridge keeps an OS call session and an SDK Call in step in both directions, and OsCallAdapter is the interface it drives, so the part you write is one adapter over your chosen call library rather than the whole state machine. A contract suite in @dialstack/sdk-native/testing exercises an adapter against the behaviours the bridge depends on.

What the package does not ship is the platform wiring itself: the native call library (iOS PushKit + CallKit, Android FCM + Telecom/ConnectionService) and the push delivery. DialStack stores no device tokens — arranging the push that wakes your app is yours. The OS-integration example above is a complete worked wiring of both on Android.

The lower-level seams stay public too: useSoftphone() exposes calls, incomingCalls, activeCall and actions.callActionsFor(call), and every Call carries a stable id and state, so an integrator who wants to drive the OS session directly can still do so.