react-native-ussd
React Native Library to handle USSD calls, with support for SIM selection on Android. The Android module is written in Kotlin and the iOS module is written in Swift. Basic dialing support for iOS is available. USSD response handling and SIM selection for dialing are Android-specific features.
Getting started
$ npm install react-native-ussd --save
Installation
After installing with npm, React Native's auto-linking mechanism will handle linking the Android module. For iOS, this library is distributed as a Swift Package. Add it to your application project using Xcode:
- Go to File > Add Packages...
- Enter the repository URL for this package (
https://github.com/isharaux/react-native-ussd.git
or your fork). - Select the package and add it to your app's target.
API Documentation
Ussd.getSimInfo(): Promise<SimInfo[]>
Retrieves a list of available and active SIM cards on the device.
- Returns:
Promise<SimInfo[]>
- A promise that resolves to an array ofSimInfo
objects. SimInfo
Object Structure:interface SimInfo { slotIndex: number; // 0-based logical index of the SIM slot. subscriptionId: number; // Actual subscription ID on Android. On iOS, this is the same as slotIndex for consistency. carrierName?: string; // Name of the network carrier (e.g., "Verizon", "T-Mobile"). phoneNumber?: string; // Phone number associated with the SIM. // Android: Requires READ_PHONE_NUMBERS, READ_SMS, or carrier privileges. May return null. // iOS: Generally not available; will be null. countryIso?: string; // ISO country code for the SIM's network (e.g., "us", "gb"). mobileCountryCode?: string; // Mobile Country Code (MCC) - Android only. mobileNetworkCode?: string; // Mobile Network Code (MNC) - Android only. }
- Permissions:
- Android: Requires
android.permission.READ_PHONE_STATE
. To retrievephoneNumber
, additionally requiresandroid.permission.READ_PHONE_NUMBERS
orandroid.permission.READ_SMS
, or carrier privileges. - iOS: No special permissions are required for basic carrier details (name, ISO).
phoneNumber
is typically not accessible.
- Android: Requires
Ussd.dial(ussdCode: string, options?: DialOptions): Promise<void>
Initiates a USSD call.
- Parameters:
ussdCode: string
- The USSD code to dial (e.g.,"*123#"
).options?: DialOptions
- Optional parameters for dialing.
DialOptions
Interface:interface DialOptions { subscriptionId?: number; // The subscriptionId (obtained from getSimInfo) of the SIM to use for dialing. }
- Platform-specific Behavior:
- Android: If
subscriptionId
is provided, the library attempts to send the USSD request via the specified SIM. If not provided or invalid, it uses the default SIM or system-defined behavior. The promise resolves upon successful initiation of the USSD request or rejects if initiation fails (e.g., permission issues). USSD responses are delivered via events. - iOS: The
subscriptionId
parameter is ignored. iOS does not allow programmatic selection of the SIM for dialingtel:
URLs; the OS or user controls this. The promise resolves if thetel:
URL is successfully passed to the OS, or rejects if it cannot be opened.
- Android: If
- Returns:
Promise<void>
- A promise that resolves if the USSD dial process was successfully initiated, or rejects if there was an error before dialing (e.g., invalid URL, permissions). Note that this does not indicate the success or failure of the USSD session itself. - Permissions:
- Android: Requires
android.permission.CALL_PHONE
. If usingsubscriptionId
obtained fromgetSimInfo
,android.permission.READ_PHONE_STATE
is also implicitly needed. - iOS: No special permissions are required for opening
tel:
URLs, but the app must be able to make calls.
- Android: Requires
Event Listener: ussdEventEmitter
Listens for USSD responses or errors from the native module. Primarily for Android.
ussdEventEmitter.addListener('ussdEvent', (event: { ussdReply: string }) => { ... });
- Triggered on Android when a USSD response is received.
event.ussdReply
: The string response from the USSD session.
ussdEventEmitter.addListener('ussdErrorEvent', (event: { error: string, failureCode?: number }) => { ... });
- Triggered on Android when a USSD request fails during the session or if an error occurs in processing.
event.error
: A string describing the error.event.failureCode
(optional): The specific failure code from the Android TelephonyManager, if available.
Important iOS Limitation: iOS does not provide a way for applications to intercept or read USSD responses. Therefore, the ussdEventEmitter
will not fire ussdEvent
or ussdErrorEvent
related to USSD session results on iOS. The dial
promise on iOS only indicates if the call was initiated, not the outcome of the USSD session.
Platform Specific Configurations
Android
Minimum Android Version: 12 (API 31)
Permissions:
Add the following permissions to your AndroidManifest.xml
:
<manifest ...>
<!-- Required to make USSD calls -->
<uses-permission android:name="android.permission.CALL_PHONE"/>
<!-- Required for Ussd.getSimInfo() to read SIM details and for selecting a specific SIM for dialing -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!-- Optional: To access the phone number via Ussd.getSimInfo().
Alternatively, READ_SMS or carrier privileges also grant this. -->
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
<application ...>
...
</application>
</manifest>
The library has been updated to use Kotlin and targets Android 12 (API 31) and newer. Ensure your project's Android configuration is compatible.
iOS
Minimum iOS Version: 14.0 The iOS module is written in Swift and integrated using Swift Package Manager.
- Dialing: Calling
Ussd.dial()
will open the native iOS dialer and pre-fill the USSD code. The user then needs to manually initiate the call. ThesubscriptionId
inDialOptions
is ignored. getSimInfo()
: This method will return available SIM information (carrier name, ISO code, etc.) butphoneNumber
will typically benull
. This information is for display or informational purposes; it cannot be used to programmatically select the SIM for dialing.- USSD Responses: As stated, iOS does not provide USSD responses to the application.
ussdEventEmitter
will not emit events for USSD session results. - Permissions: No special permissions are typically required for
tel:
URL dialing or basic SIM info on iOS.
Example Usage
import Ussd, { ussdEventEmitter, SimInfo, DialOptions } from 'react-native-ussd';
import { PermissionsAndroid, Platform } from 'react-native';
// --- Getting SIM Info ---
async function displaySimInfo() {
try {
// On Android, you might want to request READ_PHONE_STATE first
if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.READ_PHONE_STATE,
{
title: 'SIM Info Permission',
message: 'This app needs access to your phone state to read SIM information.',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
console.log('READ_PHONE_STATE permission denied');
return;
}
}
const sims: SimInfo[] = await Ussd.getSimInfo();
if (sims.length > 0) {
console.log('Available SIMs:', sims);
// Example: Store this info, allow user to pick a SIM for Android dialing
// For instance, to use the first SIM for dialing on Android:
// const selectedSubscriptionId = sims[0].subscriptionId;
// await dialWithSim("*123#", selectedSubscriptionId);
} else {
console.log('No SIMs found or accessible.');
}
} catch (error) {
console.error('Error getting SIM info:', error);
}
}
// --- Dialing with a specific SIM (Android primarily) ---
async function dialWithSim(ussd: string, selectedSubscriptionId?: number) {
// On Android, ensure CALL_PHONE permission
if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.CALL_PHONE,
{
title: 'Call Permission',
message: 'This app needs to make calls to run USSD codes.',
buttonPositive: 'OK',
buttonNegative: 'Cancel',
},
);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
console.log('CALL_PHONE permission denied');
return;
}
}
const options: DialOptions = {};
if (selectedSubscriptionId !== undefined && Platform.OS === 'android') {
// Only apply subscriptionId on Android
options.subscriptionId = selectedSubscriptionId;
}
try {
await Ussd.dial(ussd, options); // Pass options, even if empty
console.log(`USSD dial initiated for ${ussd} with options:`, options);
} catch (error) {
console.error('Error dialing USSD:', error);
}
}
// --- Listening to USSD events (Android specific for USSD results) ---
// It's good practice to add listeners early, e.g., in a top-level component's mount effect.
// Listener for successful USSD replies (Android only)
const ussdEventListener = ussdEventEmitter.addListener('ussdEvent', (event) => {
console.log('USSD Event (ussdEvent):', event.ussdReply);
// Example: Parse event.ussdReply
// let balance = event.ussdReply.split("is")[1].split(".Valid")[0];
// let date = event.ussdReply.split("until")[1].split(".")[0];
});
// Listener for USSD errors during the session (Android only)
const ussdErrorEventListener = ussdEventEmitter.addListener('ussdErrorEvent', (event) => {
console.error('USSD Error Event (ussdErrorEvent):', event.error);
if (event.failureCode !== undefined) {
console.error('Failure Code:', event.failureCode);
}
});
// --- Example Function Calls ---
// Make sure to call these functions where appropriate in your app logic.
// For example, in a button press handler:
// To display SIM info:
// displaySimInfo();
// To dial with default/system-chosen SIM:
// dialWithSim("*123#");
// To dial with a specific SIM on Android (e.g., first SIM found):
// async function dialWithFirstSim() {
// const sims = await Ussd.getSimInfo();
// if (sims.length > 0 && Platform.OS === 'android') {
// dialWithSim("*123#", sims[0].subscriptionId);
// } else if (Platform.OS === 'ios') {
// dialWithSim("*123#"); // On iOS, subscriptionId is ignored
// } else {
// console.log("No SIMs available or not on Android for specific SIM dialing.");
// }
// }
// dialWithFirstSim();
// --- Cleanup ---
// Remember to remove listeners when they are no longer needed, e.g., in componentWillUnmount or useEffect cleanup.
// Example (if listeners were set up in a class component):
// componentWillUnmount() {
// ussdEventListener.remove();
// ussdErrorEventListener.remove();
// }
// For functional components with useEffect:
// useEffect(() => {
// // ... listeners added here
// return () => {
// ussdEventListener.remove();
// ussdErrorEventListener.remove();
// };
// }, []);
The previous example can beadapted. The core functionalities Ussd.dial
, Ussd.getSimInfo
, and ussdEventEmitter
are the building blocks.
Running Tests
This project uses Jest as its testing framework. The tests cover the core functionalities of the library for both Android and iOS platforms by mocking native module interactions.
To run all tests, execute the following command from the project root:
npm test
To run tests for a specific file, you can use:
npm test <fileNameToTest>
For example, to run only the tests for the dial
functionality:
npm test dial.test.js
Ensure you have installed the project dependencies (npm install
) before running the tests.