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

@telnyx/react-voice-commons-sdk

v0.4.3

Published

A high-level, state-agnostic, drop-in module for the Telnyx React Native SDK that simplifies WebRTC voice calling integration

Readme

Telnyx React Voice Commons SDK

A high-level, state-agnostic, drop-in module for the Telnyx React Native SDK that simplifies WebRTC voice calling integration. This library provides a comprehensive solution for building VoIP applications with native call UI support, push notifications, and seamless background handling.

Key Features

  • TelnyxVoiceApp Integration: Automatic lifecycle management and push notification handling
  • Native Call UI: CallKit (iOS) and ConnectionService (Android) integration
  • Background Handling: Seamless app state transitions and background call processing
  • Push Notifications: Firebase (Android) and APNs (iOS) integration
  • Reactive State Management: RxJS-based state streams for real-time UI updates
  • TypeScript Support: Full TypeScript definitions for better developer experience
  • Cross-Platform: Built for both iOS and Android with React Native
  • Framework-agnostic: Works in both Expo and bare React Native projects. See the bare RN reference demo for non-Expo integration.

About @telnyx/react-voice-commons-sdk

The @telnyx/react-voice-commons-sdk library provides:

  • Headless Operation: Core library that can establish and manage call state programmatically
  • Reactive Streams: All state exposed via RxJS observables for easy integration
  • Simplified API: Single facade class (TelnyxVoipClient) that hides underlying complexity
  • Automatic Lifecycle Management: Background/foreground handling with TelnyxVoiceApp component
  • Call State Management: Central state machine for managing multiple calls
  • Session Management: Automatic connection lifecycle with reconnection logic
  • Push Notification Support: Built-in handling for background push notifications
  • TypeScript Support: Full TypeScript definitions for better developer experience

Integration Guide

Basic Setup

Integrate the library using the TelnyxVoiceApp component for automatic lifecycle management:

import {
  TelnyxVoiceApp,
  TelnyxVoipClient,
  createTelnyxVoipClient,
} from '@telnyx/react-voice-commons-sdk';

// Create the VoIP client instance (singleton — safe to call inside a component body)
const voipClient = createTelnyxVoipClient({
  enableAppStateManagement: true, // Optional: Enable automatic app state management (default: true)
  debug: true, // Optional: Enable debug logging
});

export default function App() {
  // Skip auto-login if the app was launched from a push notification —
  // the SDK handles login internally via the push notification flow.
  React.useEffect(() => {
    TelnyxVoipClient.isLaunchedFromPushNotification().then((isFromPush) => {
      if (!isFromPush) {
        // Safe to auto-login
        voipClient.loginFromStoredConfig();
      }
    });
  }, []);

  return (
    <TelnyxVoiceApp voipClient={voipClient} enableAutoReconnect={false} debug={true}>
      <YourAppContent />
    </TelnyxVoiceApp>
  );
}

Core Components

1. VoIP Client Configuration

// createTelnyxVoipClient is a singleton — repeated calls return the same instance.
// This makes it safe to call inside a React component body without re-creating on every render.
const voipClient = createTelnyxVoipClient({
  enableAppStateManagement: true, // Optional: Enable automatic app state management (default: true)
  debug: true, // Optional: Enable debug logging
});

// If you need to tear down and recreate the client (e.g., on logout):
import { destroyTelnyxVoipClient } from '@telnyx/react-voice-commons-sdk';
destroyTelnyxVoipClient(); // Disposes the singleton; next createTelnyxVoipClient() call creates a fresh instance

Configuration Options Explained:

  • enableAppStateManagement: true - Optional (default: true): Enables automatic background/foreground app state management. When enabled, the library automatically disconnects when the app goes to background (unless there's an active call) and handles reconnection logic. Set to false if you want to handle app lifecycle manually.
  • debug: true - Optional (default: false): Enables detailed logging for connection states, call transitions, and push notification processing. Useful for development and troubleshooting.

2. TelnyxVoiceApp Wrapper

The TelnyxVoiceApp component handles:

  • Automatic background/foreground lifecycle management
  • Push notification processing from terminated state
  • Login state management with automatic reconnection
  • Background client management for push notifications
  • Automatic CallKit coordinator wiring — the voipClient is set on the CallKit coordinator on mount, so you don't need to call setVoipClient() manually

3. Reactive State Management

// Listen to connection state
voipClient.connectionState$.subscribe((state) => {
  console.log('Connection state:', state);
});

// Listen to active calls
voipClient.calls$.subscribe((calls) => {
  console.log('Active calls:', calls);
});

// Handle individual call state
call.callState$.subscribe((state) => {
  console.log('Call state:', state);
});

Navigation

As of v0.3.0, the SDK no longer navigates the host app. Routing on state transitions (e.g. redirecting to a login screen on disconnect, surfacing an in-call screen when a call arrives via push) is entirely the host app's responsibility. Subscribe to the observables below and invoke your own navigator.

What to observe:

| Observable | Emits | Use it for | | ----------------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | voipClient.connectionState$ | TelnyxConnectionState (CONNECTING, CONNECTED, RECONNECTING, DISCONNECTED, ERROR) | Redirect to login on DISCONNECTED; gate outbound-call UI on CONNECTED. There is no separate loginState$CONNECTED means the socket is up and authenticated. | | voipClient.activeCall$ | Call \| null | Navigate to your in-call screen when a call appears. Primary signal for push-launched cold starts — when the SDK finishes the push-driven login and the call arrives, this emits. | | voipClient.calls$ | Call[] | Multi-call UIs (call waiting, conference). | | call.callState$ | TelnyxCallState | Per-call transitions (ringing / active / held / ended). |

Redirect to login on disconnect

import { router } from 'expo-router';
import { useEffect } from 'react';
import { TelnyxConnectionState } from '@telnyx/react-voice-commons-sdk';

useEffect(() => {
  const sub = voipClient.connectionState$.subscribe((state) => {
    if (state === TelnyxConnectionState.DISCONNECTED) {
      router.replace('/');
    }
  });
  return () => sub.unsubscribe();
}, []);

Navigate to in-call screen when a call arrives (push-launched or otherwise)

This is the piece that pairs with the push flow: after isLaunchedFromPushNotification() tells you to skip manual login, the SDK drives login internally and the call shows up on activeCall$. The same subscription handles foreground pushes and outbound calls, so you only need one:

import { router } from 'expo-router';
import { useEffect } from 'react';

useEffect(() => {
  // Handle the cold-start race: if the call was already delivered before this
  // component mounted (common on push-launched cold starts), read it synchronously.
  if (voipClient.currentActiveCall) {
    router.replace('/call');
  }

  const sub = voipClient.activeCall$.subscribe((call) => {
    if (call) router.replace('/call');
    else router.replace('/dialer');
  });
  return () => sub.unsubscribe();
}, []);

Note on CallKit / ConnectionService: the native call UI (ringtone, answer/decline) is shown by the OS regardless — your RN screen is only visible once the user taps into the app. If you only need native UI, you can skip the navigation step entirely.

Optional: gate outbound-call UI on CONNECTED

import { canMakeCalls } from '@telnyx/react-voice-commons-sdk';

const [canCall, setCanCall] = useState(false);
useEffect(() => {
  const sub = voipClient.connectionState$.subscribe((s) => setCanCall(canMakeCalls(s)));
  return () => sub.unsubscribe();
}, []);

The same pattern works with react-navigation, React Router, or any other navigator — the SDK is agnostic.

4. Call Management

// Make a call
const call = await voipClient.newCall('1234567890');

// Answer incoming call
await call.answer();

// Control call
await call.mute();
await call.hold();
await call.hangup();

// Send DTMF tones (for IVRs, conference pins, etc.)
await call.dtmf('1'); // single digit
await call.dtmf('1234#'); // whole string

DTMF: call.dtmf(digits) sends each character as a Verto INFO message to the Telnyx platform. Valid characters are 0-9, A-D, *, and #; any other characters are silently dropped by the underlying SDK. The call must be in the ACTIVE state — calling dtmf() in any other state throws. Safe to call with a single digit for dialpad presses or with a whole string for pre-recorded sequences.

Push Notification Flow

You do not wire push handlers in JS. The native layer does the work:

  • Android: TelnyxFirebaseMessagingService receives the FCM message, posts the call notification, and launches TelnyxMainActivity with the intent on Answer/Decline.
  • iOS: TelnyxVoipPushHandler receives the PushKit payload and reports the call to CallKit.

The SDK then connects the socket and restores the call internally. You observe voipClient.calls$ to render UI.

Detecting a Push-Launched Cold Start

When the OS wakes the app from a terminated state to deliver a call, the SDK is already handling login via the push flow. If your app also triggers a login on mount, you get two competing sessions (double-login), which causes the call to fail or the socket to churn.

Use the static TelnyxVoipClient.isLaunchedFromPushNotification() to guard your login:

import { TelnyxVoipClient } from '@telnyx/react-voice-commons-sdk';

React.useEffect(() => {
  TelnyxVoipClient.isLaunchedFromPushNotification().then((isFromPush) => {
    if (isFromPush) return; // SDK is handling login via the push flow
    voipClient.loginFromStoredConfig(); // Normal cold start
  });
}, []);

Returns true when a pending FCM intent (Android) or PushKit payload (iOS) has not yet been consumed.

Common Mistake: Manual or Automatic Login on Push

The single most common integration bug is re-logging in while the SDK is already handling a push:

// WRONG — runs on every mount, including push-launched cold starts
React.useEffect(() => {
  voipClient.login(config); // or loginFromStoredConfig(), or loginWithToken()
}, []);
// Right — guard the login on push-launched cold starts
React.useEffect(() => {
  TelnyxVoipClient.isLaunchedFromPushNotification().then((isFromPush) => {
    if (!isFromPush) voipClient.loginFromStoredConfig();
  });
}, []);

Symptoms of getting this wrong: the incoming call rings briefly then disappears, the socket disconnects mid-call, or CallKit shows a call that immediately ends.

Authentication & Persistent Storage

The library supports both credential-based and token-based authentication with automatic persistence for seamless reconnection.

Authentication Methods

1. Credential-Based Authentication

import { createCredentialConfig } from '@telnyx/react-voice-commons-sdk';

const config = createCredentialConfig('your_sip_username', 'your_sip_password', {
  debug: true,
  pushNotificationDeviceToken: 'your_device_token',
});

await voipClient.login(config);

2. Token-Based Authentication

import { createTokenConfig } from '@telnyx/react-voice-commons-sdk';

const config = createTokenConfig('your_jwt_token', {
  debug: true,
  pushNotificationDeviceToken: 'your_device_token',
});

await voipClient.loginWithToken(config);

Automatic Storage & Reconnection

The library automatically stores authentication data securely for seamless reconnection. You don't need to manually manage these storage keys - the library handles everything internally.

Internal Storage (Managed Automatically)

The library uses these AsyncStorage keys internally:

  • @telnyx_username - SIP username (credential auth)
  • @telnyx_password - SIP password (credential auth)
  • @credential_token - JWT authentication token (token auth)
  • @push_token - Push notification device token

Note: These are managed automatically by the library. You only need to call login() once, and the library will handle storage and future reconnections.

Auto-Reconnection

// Automatically reconnects using internally stored credentials or token
const success = await voipClient.loginFromStoredConfig();

if (!success) {
  // No stored authentication data found, show login UI
  console.log('Please log in again');
}

When Auto-Reconnection Happens:

  • App launches from background/terminated state
  • Push notification received while disconnected
  • Network reconnection after connectivity loss
  • App state changes (foreground/background transitions)

Demo App Note: When using the library in a demo application, the TelnyxLoginForm component may do additional storage for UI convenience (pre-filling login forms). This is separate from the library's internal authentication storage and is not required for production apps.

Manual Storage Management (Advanced Use Only)

If you need to clear stored authentication data manually:

import AsyncStorage from '@react-native-async-storage/async-storage';

// Clear all Telnyx authentication data
await AsyncStorage.multiRemove([
  '@telnyx_username',
  '@telnyx_password',
  '@credential_token',
  '@push_token',
]);

Important: This is typically not needed. The library manages authentication storage automatically. Only use this for advanced scenarios like implementing a "logout" feature that clears all stored data.

Native Integration

The library provides complete native integration for both platforms. These integrations are required for production apps using the library.

Android Integration

1. MainActivity Setup

Your app's MainActivity should extend TelnyxMainActivity for automatic push notification handling:

// In your app's MainActivity.kt
import com.telnyx.react_voice_commons.TelnyxMainActivity

class MainActivity : TelnyxMainActivity() {
    // Your app-specific code

    override fun onHandleIntent(intent: Intent) {
        super.onHandleIntent(intent)
        // Handle any additional intent processing
    }
}

The TelnyxMainActivity provides:

  • Automatic push notification intent handling
  • Call action processing (Answer/Decline from notifications)
  • Proper lifecycle management for VoIP functionality
  • Integration with VoicePnManager for push notification state

2. Push Notification Setup

  1. Place google-services.json in the project root.
  2. Create an FCM service that extends TelnyxFirebaseMessagingService and a notification action receiver that extends TelnyxNotificationActionReceiver, then register both in AndroidManifest.xml. See the push notification app setup guide for the full boilerplate.

Do not register messaging().setBackgroundMessageHandler(...) from JS. Android push is handled entirely by the native TelnyxFirebaseMessagingService — adding a JS handler will fight the native layer and can double-process calls. There is no TelnyxVoiceApp.handleBackgroundPush step required on Android.

See Push Notification Flow below for how to detect push-launched cold starts and avoid double-login.

iOS Integration

1. AppDelegate Setup

Your AppDelegate only needs to implement PKPushRegistryDelegate for VoIP push notifications. CallKit integration is automatically handled by CallBridge:

import PushKit
import TelnyxVoiceCommons

@UIApplicationMain
public class AppDelegate: ExpoAppDelegate, PKPushRegistryDelegate {

  public override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    // Your existing setup...

    // Initialize VoIP push registry via react-voice-commons
    TelnyxVoipPushHandler.initializeVoipRegistration()

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  // MARK: - VoIP Push Notifications
  public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
    TelnyxVoipPushHandler.shared.handleVoipTokenUpdate(pushCredentials, type: type)
  }

  public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    TelnyxVoipPushHandler.shared.handleVoipPush(payload, type: type, completion: completion)
  }
}

Note: CallKit integration (CXProvider, CXProviderDelegate, audio session management) is automatically handled by the internal CallBridge component. You don't need to implement any CallKit delegate methods manually.

2. VoIP Push Certificate Setup

  • Configure VoIP push certificates in your Apple Developer account
  • The TelnyxVoipPushHandler automatically handles token registration and push processing
  • CallKit integration is automatically managed by CallBridge - no manual setup required

Key Native Features Integrated

  1. Push Notification Handling: Both platforms handle background push notifications properly
  2. Native Call UI: CallKit (iOS, managed by CallBridge) and ConnectionService (Android) integration
  3. Audio Session Management: Automatic audio session handling for VoIP calls via CallBridge
  4. Background Processing: Seamless app state transitions and background call handling
  5. Intent Processing: Android intent handling for notification actions (Answer/Decline)
  6. Token Management: Automatic push token registration and updates

Running the Demo Application

Prerequisites

  • Node.js and npm
  • Expo CLI
  • iOS development: Xcode
  • Android development: Android Studio

Installation and Setup

  1. Remove global Expo CLI (if previously installed):

    npm uninstall -g expo
  2. Install dependencies (including local Expo):

    npm install
  3. Run Expo install to ensure all dependencies are properly configured:

    npx expo install
  4. Configure Firebase for Android:

    • Download the google-services.json file from your Firebase project
    • Place it in the root directory of the project
  5. Prebuild the project:

    npx expo prebuild
  6. Install iOS dependencies (required for iOS):

    cd ios && pod install && cd ..

Running the Application

  1. Start Metro bundler (in a separate terminal):

    npx expo start --clear --reset-cache
  2. Run on iOS:

    npx expo run:ios
  3. Run on Android:

    npx expo run:android

Note: Make sure you have the necessary development environment set up for iOS (Xcode) or Android (Android Studio) before running the respective commands.

Demo App Features

Once running, the demo app provides:

  • Login Form: Enter your Telnyx SIP credentials or token
  • Dialer Interface: Make outgoing calls with a native dialer UI
  • Call Management: Answer, decline, hold, mute, and transfer calls
  • Native Call UI: Integrated CallKit (iOS) and ConnectionService (Android)
  • Push Notifications: Receive calls when app is in background or terminated
  • Multiple Call Support: Handle multiple simultaneous calls
  • Background Handling: Seamless app state transitions

Configuration

The demo app includes debug logging enabled by default:

// Enable debug logging for the Telnyx SDK
if (__DEV__) {
  log.setLevel('debug');
  (global as any).__TELNYX_DEBUG__ = true;
}

This provides detailed logging for:

  • Connection state changes
  • Call state transitions
  • Push notification processing
  • Background lifecycle events

Troubleshooting

iOS Connection Issues (First Run)

If you encounter network connection errors on iOS:

# Try clearing cache and resetting
npx expo start --clear --reset-cache

# Or use tunnel mode
npx expo start --tunnel

# Or specify host explicitly
npx expo start --host lan

iOS VoIP Push Notification Issues

If you see VoIP-related crashes on iOS, ensure your AppDelegate.swift includes the VoIP push notification delegate methods. The project includes these by default, but if you encounter issues, rebuild the iOS project:

npx expo prebuild --platform ios --clean
npx expo run:ios

Common Integration Issues

Double Login on Cold-Start

When the app is launched from a push notification, the SDK handles login internally. If your app also auto-logs in on mount, both will race and the push flow breaks. Use isLaunchedFromPushNotification() to guard your auto-login:

const isFromPush = await TelnyxVoipClient.isLaunchedFromPushNotification();
if (!isFromPush) {
  voipClient.loginFromStoredConfig();
}

Also ensure you're not calling login methods manually when using TelnyxVoiceApp with auto-reconnection enabled.

Background Disconnection

Check if enableAutoReconnect is set appropriately for your use case in the TelnyxVoiceApp configuration.

Push Notifications Not Working

  • Android:
    • Verify google-services.json is in the correct location and Firebase is properly configured
    • Ensure your MainActivity extends TelnyxMainActivity
    • Check that VoicePnManager is properly handling push actions
  • iOS:
    • Ensure VoIP push certificates are configured in your Apple Developer account
    • Verify AppDelegate implements PKPushRegistryDelegate and delegates to TelnyxVoipPushHandler
    • Check that TelnyxVoipPushHandler.initializeVoipRegistration() is called in didFinishLaunchingWithOptions
  • Both: Check that background message handlers are properly registered

Native Integration Issues

  • Android: Ensure MainActivity extends TelnyxMainActivity for proper intent handling
  • iOS: Verify AppDelegate implements PKPushRegistryDelegate and delegates to TelnyxVoipPushHandler
  • CallKit: On iOS, CallKit integration is automatically handled by CallBridge - no manual setup required

Audio Issues

  • iOS: Audio session management is automatically handled by CallBridge
  • Android: Verify ConnectionService is properly configured for audio routing
  • Both: Ensure proper audio permissions are granted

Memory Leaks

Ensure you're unsubscribing from RxJS observables in your React components:

useEffect(() => {
  const subscription = voipClient.connectionState$.subscribe(handleStateChange);
  return () => subscription.unsubscribe();
}, []);

Documentation

For complete API documentation and advanced usage patterns, see the TelnyxVoiceApp Documentation.

License

MIT License - see LICENSE file for details.