@nativetalkcommunications/react-native-call-sdk
v0.1.1
Published
Plug-and-play SIP/VoIP calling SDK for React Native, powered by Linphone. Provides registration, dial, answer, hold, mute, speaker, DTMF, and call logs — plus optional UI components.
Downloads
250
Maintainers
Readme
@nativetalkcommunications/react-native-call-sdk
Plug-and-play SIP / VoIP calling for React Native.
Drop-in
<CallProvider>, auseCall()hook, optional UI screens — and a native Android + iOS layer that handles backgrounding, foreground services, notifications, and CallKit/Push integration so you don't have to.
Highlights
| Feature | Notes |
|---|---|
| Plug-and-play | One provider, one hook. No coupling to your auth, navigation, or HTTP client. |
| Backgrounded calls | Android foreground service keeps the registration warm. iOS supports VoIP push. |
| Bundled UI | Optional <Dialer />, <IncomingCallView />, <OutgoingCallView />. Use them or roll your own. |
| Typed | First-class TypeScript types throughout. |
| No coupling | No useAuth, no navigation lib, no axios. You inject everything. |
Requirements
- React Native ≥ 0.73
- iOS ≥ 13.0
- Android
minSdkVersion≥ 24 (Android 7.0) - React Native < 0.82: set
newArchEnabled=falseinandroid/gradle.properties. React Native ≥ 0.82 runs New Architecture by default and the SDK works via the interop layer — the flag is not needed.
Installation
npm install @nativetalkcommunications/react-native-call-sdk
# or
yarn add @nativetalkcommunications/react-native-call-sdkExpo setup
If your app uses Expo, the config plugin handles all native configuration automatically.
Installing from npm
1. Add the plugin to app.json
{
"expo": {
"plugins": [
"@nativetalkcommunications/react-native-call-sdk"
]
}
}2. Run prebuild
npx expo prebuildInstalling from a local path (development only)
Use this when installing via npm install file:../nativetalk-call-sdk during SDK development.
1. Add the plugin to app.json
{
"expo": {
"plugins": [
"@nativetalkcommunications/react-native-call-sdk"
]
}
}2. Update metro.config.js
If your project does not have a metro.config.js, create one at the project root. If it already exists, merge the SDK entries into your existing config:
const { getDefaultConfig } = require('expo/metro-config');
const { mergeConfig } = require('@react-native/metro-config');
const path = require('path');
const sdkPath = path.resolve(__dirname, '../nativetalk-call-sdk'); // adjust path as needed
const sdkConfig = {
watchFolders: [sdkPath],
resolver: {
unstable_enableSymlinks: true,
extraNodeModules: {
'@nativetalkcommunications/react-native-call-sdk': sdkPath,
'react': path.resolve(__dirname, 'node_modules/react'),
'react-native': path.resolve(__dirname, 'node_modules/react-native'),
},
nodeModulesPaths: [
path.resolve(__dirname, 'node_modules'),
path.resolve(sdkPath, 'node_modules'),
],
},
};
module.exports = mergeConfig(getDefaultConfig(__dirname), sdkConfig);3. Remove duplicate react/react-native from the SDK
Check if duplicates exist and delete them:
ls ../nativetalk-call-sdk/node_modules | grep react # check first
rm -rf ../nativetalk-call-sdk/node_modules/react
rm -rf ../nativetalk-call-sdk/node_modules/react-native4. Run prebuild
npx expo prebuildSame as the npm install path — the plugin configures Android and iOS automatically.
Plugin options
{
"expo": {
"plugins": [
["@nativetalkcommunications/react-native-call-sdk", {
"microphonePermission": "This app needs microphone access for voice calls."
}]
]
}
}| Option | Default | Description |
|---|---|---|
| microphonePermission | "Microphone access is required for calls." | iOS microphone permission dialog text |
Quick start
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import { CallProvider, useCall } from '@nativetalkcommunications/react-native-call-sdk';
import { Dialer } from '@nativetalkcommunications/react-native-call-sdk/ui';
function LoginScreen({ onLogin }) {
const [username, setUsername] = useState('100');
const [password, setPassword] = useState('secret');
const [domain, setDomain] = useState('pbx.example.com');
const [transport, setTransport] = useState('tcp');
const handleLogin = () => {
if (!username || !password || !domain) {
Alert.alert('Error', 'Please fill in all fields');
return;
}
onLogin({ username, password, domain, transport });
};
return (
<View style={styles.loginContainer}>
<Text style={styles.title}>SIP Credentials</Text>
<TextInput
style={styles.input}
placeholder="Username"
value={username}
onChangeText={setUsername}
/>
<TextInput
style={styles.input}
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<TextInput
style={styles.input}
placeholder="Domain"
value={domain}
onChangeText={setDomain}
/>
<TextInput
style={styles.input}
placeholder="Transport (tcp/udp)"
value={transport}
onChangeText={setTransport}
/>
<TouchableOpacity style={styles.button} onPress={handleLogin}>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
</View>
);
}
function DialerWithSignout({ onLogout }) {
const { isRegistered } = useCall();
return (
<View style={styles.dialerContainer}>
{isRegistered && (
<TouchableOpacity style={styles.signoutButton} onPress={onLogout}>
<Text style={styles.signoutText}>Sign Out</Text>
</TouchableOpacity>
)}
<Dialer />
</View>
);
}
export default function App() {
const [config, setConfig] = useState(null);
const handleLogin = (credentials) => {
setConfig(credentials);
};
const handleLogout = () => {
setConfig(null);
};
if (!config) {
return <LoginScreen onLogin={handleLogin} />;
}
return (
<CallProvider
config={config}
onIncomingCall={(info) => console.log('Incoming from', info.phone)}
onRegistrationStateChanged={(r) => console.log('SIP:', r.state)}
onError={(e) => Alert.alert('SDK Error', e.message)}
>
<DialerWithSignout onLogout={handleLogout} />
</CallProvider>
);
}
const styles = StyleSheet.create({
loginContainer: {
flex: 1,
padding: 16,
justifyContent: 'center',
},
dialerContainer: {
flex: 1,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 24,
textAlign: 'center',
},
input: {
backgroundColor: '#fff',
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
marginBottom: 12,
borderWidth: 1,
borderColor: '#ddd',
},
button: {
backgroundColor: '#2196F3',
paddingVertical: 12,
borderRadius: 8,
alignItems: 'center',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
signoutButton: {
backgroundColor: '#F44336',
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 6,
alignItems: 'center',
margin: 12,
},
signoutText: {
color: '#fff',
fontSize: 14,
},
});Error handling — all SDK errors are surfaced through onError. The callback receives { code: string, message: string }. Common codes:
| Code | When |
|---|---|
| INVALID_DOMAIN | The SIP domain is not a *.nativetalk.io domain |
| REGISTRATION_FAILED | SIP server rejected the REGISTER request |
| NO_CONFIG | register() was called with no config available |
| DIAL_FAILED | dial() was called with no domain configured |
The hook gives you everything else:
function CallControls() {
const {
callStatus,
formattedDuration,
isMuted, isSpeaker, isHeld,
dial, answer, hangup, decline,
toggleMute, toggleSpeaker, toggleHold,
sendDtmf,
} = useCall();
return (
<View>
<Text>{callStatus} — {formattedDuration}</Text>
<Button onPress={() => dial('+2348012345678')} title="Dial" />
<Button onPress={hangup} title="Hang up" />
</View>
);
}Documentation
| Topic | Where |
|---|---|
| Android setup deep-dive — services, channels, manifest, Maven repo | docs/android-setup.md |
| iOS setup deep-dive — CallKit, PushKit | docs/ios-setup.md |
| Configuration — every prop on <CallProvider> | docs/configuration.md |
| API reference — every export, every type | docs/api-reference.md |
| Bundled UI components — props, theming, customization | docs/ui-components.md |
| Push notifications — VoIP push wakeup, FCM data messages | docs/push-notifications.md |
| Architecture — what's in the box and why | docs/architecture.md |
| Troubleshooting — common errors and fixes | docs/troubleshooting.md |
API surface
// Main provider + hook
import { CallProvider, useCall } from '@nativetalkcommunications/react-native-call-sdk';
// Types
import type {
SipConfig, SipTransport,
CallApi, CallState, CallLogEntry,
IncomingCallInfo, RegistrationEvent, RegistrationState,
DeclineReason,
CallProviderProps, CallProviderEvents,
} from '@nativetalkcommunications/react-native-call-sdk';
// Helpers
import {
formatDuration, // 65 → "1:05"
callStatusLabel, // "StreamsRunning" → "In progress"
parseSipUser, // "sip:100@x" → "100"
sanitizeDial, // strips non-dial chars
formatTenantDomain, // strips http(s):// and trailing /
destinationToSipUri, // "100" + "sip.example.com" → "sip:[email protected]"
} from '@nativetalkcommunications/react-native-call-sdk';
// Escape hatch: drive the native module directly (e.g. from headless tasks)
import { CallEngine } from '@nativetalkcommunications/react-native-call-sdk';
// Optional UI
import {
Dialer,
IncomingCallView,
OutgoingCallView,
Avatar,
defaultTheme,
mergeTheme,
type CallTheme,
} from '@nativetalkcommunications/react-native-call-sdk/ui';License
MIT — see LICENSE.
