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

omikit-plugin

v4.0.2

Published

Omikit Plugin by ViHAT

Readme

OMICALL SDK for React Native

The omikit-plugin enables VoIP/SIP calling via the OMICALL platform with support for both Old and New Architecture (TurboModules + Fabric).

Status: Active maintenance | Version: 4.0.1


Table of Contents


Compatibility

| omikit-plugin | React Native | Architecture | Installation | |---------------|--------------|--------------|--------------| | 4.0.x (latest) | 0.74+ | Old + New (auto-detect) | npm install omikit-plugin@latest | | 3.3.x | 0.60 – 0.73 | Old Architecture only | npm install [email protected] |

v4.0.x highlights:

  • TurboModules (JSI) — 4-10x faster native method calls via direct C++ bridge
  • 100% backward compatible — auto-detects architecture at runtime
  • Zero breaking changes from v3.x for RN 0.74+
  • Bridgeless mode support for full New Architecture (iOS & Android)

Native SDK Versions

| Platform | SDK | Version | |----------|-----|---------| | Android | OMIKIT | 2.6.4 | | iOS | OmiKit | 1.10.34 |


Installation

npm install omikit-plugin
# or
yarn add omikit-plugin

iOS

cd ios && pod install

Android

No extra steps — permissions are declared in the module's AndroidManifest.xml.


Android Setup

1. Permissions

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

<!-- Required for all calls -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />

<!-- Only required for video calls -->
<uses-permission android:name="android.permission.CAMERA" />

Note: If your app does NOT use video calls, add the following to your app's AndroidManifest.xml to remove the camera foreground service permission declared by the SDK:

<!-- Remove camera foreground service if NOT using video call -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"
    tools:node="remove" />

Make sure to add the tools namespace to your manifest tag: xmlns:tools="http://schemas.android.com/tools"

2. Firebase Cloud Messaging (FCM)

Add your google-services.json to android/app/.

In android/app/build.gradle:

apply plugin: 'com.google.gms.google-services'

3. Maven Repository

Option A — settings.gradle.kts (recommended for new projects)

// settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
        maven { url = uri("https://repo.omicall.com/maven") }
        maven {
            url = uri("https://maven.pkg.github.com/omicall/OMICall-SDK")
            credentials {
                username = providers.gradleProperty("OMI_USER").getOrElse("")
                password = providers.gradleProperty("OMI_TOKEN").getOrElse("")
            }
            authentication {
                create<BasicAuthentication>("basic")
            }
        }
    }
}

Option B — build.gradle (Groovy / legacy projects)

// android/build.gradle (project level)
allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
        maven { url 'https://repo.omicall.com/maven' }
        maven {
            url "https://maven.pkg.github.com/omicall/OMICall-SDK"
            credentials {
                username = project.findProperty("OMI_USER") ?: ""
                password = project.findProperty("OMI_TOKEN") ?: ""
            }
            authentication {
                basic(BasicAuthentication)
            }
        }
    }
}

Then add your credentials to ~/.gradle/gradle.properties (or project-level gradle.properties):

OMI_USER=omi_github_username
OMI_TOKEN=omi_github_access_token

Note: Contact the OMICall development team to get OMI_USER and OMI_TOKEN credentials.

4. New Architecture (Optional)

To enable New Architecture on Android, in android/gradle.properties:

newArchEnabled=true

iOS Setup

1. Info.plist

Add to your Info.plist:

<key>NSMicrophoneUsageDescription</key>
<string>Required for VoIP calls</string>
<key>NSCameraUsageDescription</key>
<string>Required for video calls</string>

2. Background Modes

In Xcode, enable the following Background Modes:

  • [x] Voice over IP
  • [x] Remote notifications
  • [x] Background fetch

3. Push Notifications

Enable Push Notifications capability in Xcode for VoIP push (PushKit).

4. AppDelegate Setup

In your AppDelegate.mm (or .m):

#import <OmiKit/OmiKit-umbrella.h>
#import <OmiKit/Constants.h>

5. New Architecture (Optional)

In your Podfile:

ENV['RN_NEW_ARCH_ENABLED'] = '1'

For full bridgeless mode, in AppDelegate.mm:

- (BOOL)bridgelessEnabled
{
  return YES;
}

Then run cd ios && pod install.


Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                     React Native App                        │
│                                                             │
│   import { startCall, omiEmitter } from 'omikit-plugin'     │
└──────────────────────────┬──────────────────────────────────┘
                           │
              ┌────────────▼────────────┐
              │   Architecture Bridge   │
              │                         │
              │  TurboModule? ──► JSI   │  (New Arch: direct C++ calls)
              │       │                 │
              │       └──► NativeModule │  (Old Arch: JSON bridge)
              └────────────┬────────────┘
                           │
          ┌────────────────┼────────────────┐
          │                                 │
   ┌──────▼──────┐                  ┌───────▼──────┐
   │   Android   │                  │     iOS      │
   │             │                  │              │
   │ OmikitPlugin│                  │ OmikitPlugin │
   │  Module.kt  │                  │   .swift     │
   │      │      │                  │      │       │
   │      ▼      │                  │      ▼       │
   │  OMIKIT SDK │                  │  OmiKit SDK  │
   │  (v2.6.4)   │                  │  (v1.10.34)  │
   │      │      │                  │      │       │
   │      ▼      │                  │      ▼       │
   │  SIP Stack  │                  │  SIP Stack   │
   │  (OMSIP)    │                  │  (OMSIP)     │
   └─────────────┘                  └──────────────┘

Quick Start

import {
  startServices,
  initCallWithUserPassword,
  startCall,
  joinCall,
  endCall,
  omiEmitter,
  OmiCallEvent,
  OmiCallState,
} from 'omikit-plugin';

// Step 1: Start SDK services
// ⚠️ Call ONCE on app launch (e.g., in App.tsx / index.js / useEffect in root component)
// Do NOT call this multiple times — it initializes native audio and event listeners.
await startServices();

// Step 2: Login with SIP credentials
const loginResult = await initCallWithUserPassword({
  userName: 'sip_user',
  password: 'sip_password',
  realm: 'your_realm',
  host: '',              // SIP proxy, defaults to vh.omicrm.com
  isVideo: false,
  fcmToken: 'your_fcm_token',
  projectId: 'your_project_id', // firebase project id
});

// Step 3: Listen to call events
const subscription = omiEmitter.addListener(
  OmiCallEvent.onCallStateChanged,
  (data) => {
    console.log('Call state:', data.status);

    switch (data.status) {
      case OmiCallState.incoming:
        // Show incoming call UI
        // data.callerNumber, data.isVideo
        break;
      case OmiCallState.confirmed:
        // Call connected — show active call UI
        break;
      case OmiCallState.disconnected:
        // Call ended
        // data.codeEndCall — SIP end code
        break;
    }
  }
);

// Step 4: Make outgoing call
const result = await startCall({
  phoneNumber: '0901234567',
  isVideo: false,
});

if (result.status === 8) {
  console.log('Call started, ID:', result._id);
}

// Step 5: Accept incoming call
await joinCall();

// Step 6: End call
await endCall();

// Cleanup on unmount
subscription.remove();

Authentication

Two authentication methods are available. Each supports two login modes depending on who is using the app:

| Mode | isSkipDevices | Use Case | Capabilities | |------|-----------------|----------|--------------| | Agent (default) | false | Employees / call center agents | Can make outbound calls to any telecom number | | Customer | true | End customers | Can only call the business hotline (no outbound to external numbers) |

Option 1: Username + Password (SIP Credentials)

await initCallWithUserPassword({
  userName: string,         // SIP username
  password: string,         // SIP password
  realm: string,            // SIP realm/domain
  host?: string,            // SIP proxy server (optional)
  isVideo: boolean,         // Enable video capability
  fcmToken: string,         // Firebase token for push notifications
  projectId: string,       // Firebase project ID 
  isSkipDevices?: boolean,  // true = Customer mode, false = Agent mode (default)
});

Agent Login (default)

For employees / call center agents who can make outbound calls to any phone number:

await initCallWithUserPassword({
  userName: '100',
  password: 'sip_password',
  realm: 'your_realm',
  host: '',
  isVideo: false,
  fcmToken: fcmToken,
  // isSkipDevices defaults to false — Agent mode
});

Customer Login

For end customers who can only call the business hotline — no outbound dialing to external telecom numbers, no assigned phone number:

await initCallWithUserPassword({
  userName: '200',
  password: 'sip_password',
  realm: 'your_realm',
  host: '',
  isVideo: false,
  fcmToken: fcmToken,
  isSkipDevices: true,  // Customer mode — skip device registration
});

Option 2: API Key

await initCallWithApiKey({
  fullName: string,    // Display name
  usrUuid: string,     // User UUID from OMICALL
  apiKey: string,      // API key from OMICALL dashboard
  isVideo: boolean,    // Enable video capability
  phone: string,       // Phone number
  fcmToken: string,    // Firebase token for push notifications
  projectId?: string,  // OMICALL project ID (optional)
});

Option 3: App-to-App API (v4.0+)

Starting from v4.0, customers using the App-to-App service must call the OMICALL API to provision SIP extensions before initializing the SDK. The API returns SIP credentials that you pass to initCallWithUserPassword() with isSkipDevices: true.

For full API documentation (endpoints, request/response formats), see the API Integration Guide.

Quick flow:

Your Backend                    OMICALL API                 Mobile App (SDK)
     │                              │                            │
     │  1. POST .../init            │                            │
     ├─────────────────────────────►│                            │
     │  {domain, extension,         │                            │
     │   password, proxy}           │                            │
     │◄─────────────────────────────┤                            │
     │                              │                            │
     │  2. Return credentials       │                            │
     ├──────────────────────────────────────────────────────────►│
     │                              │   3. startServices()       │
     │                              │   4. initCallWithUserPassword
     │                              │      (isSkipDevices: true) │
     │                              │                            │
// After getting credentials from your backend:
await startServices();

await initCallWithUserPassword({
  userName: credentials.extension,     // from API response
  password: credentials.password,      // from API response
  realm: credentials.domain,           // from API response
  host: credentials.outboundProxy,     // from API response
  isVideo: false,
  fcmToken: 'your-fcm-token',
  isSkipDevices: true,                 // Required for App-to-App
});

Important:

  • Call the OMICALL API from your backend server only — never expose the Bearer token in client-side code.
  • You must call the Logout API before switching users. Otherwise, both devices using the same SIP extension will receive incoming calls simultaneously.
  • Use getter functions (getProjectId(), getAppId(), getDeviceId(), getFcmToken(), getVoipToken()) to retrieve device params for the Add Device and Logout APIs.

Call Flows

Outgoing Call Flow

 ┌──────────┐          ┌──────────┐          ┌──────────┐
 │  JS App  │          │  Native  │          │ SIP/PBX  │
 └────┬─────┘          └────┬─────┘          └────┬─────┘
      │                     │                     │
      │  startCall()        │                     │
      ├────────────────────►│                     │
      │                     │  SIP INVITE         │
      │                     ├────────────────────►│
      │                     │                     │
      │  calling (1)        │  180 Ringing        │
      │◄────────────────────┤◄────────────────────┤
      │                     │                     │
      │  early (3)          │  183 Progress       │
      │◄────────────────────┤◄────────────────────┤
      │                     │                     │
      │  connecting (4)     │  200 OK             │
      │◄────────────────────┤◄────────────────────┤
      │                     │                     │
      │  confirmed (5)      │  ACK                │
      │◄────────────────────┤────────────────────►│
      │                     │                     │
      │     ══════ Active Call (RTP audio/video) ══════
      │                     │                     │
      │  endCall()          │                     │
      ├────────────────────►│  BYE                │
      │                     ├────────────────────►│
      │  disconnected (6)   │  200 OK             │
      │◄────────────────────┤◄────────────────────┤
      │                     │                     │

Incoming Call — App in Foreground

 ┌──────────┐          ┌──────────┐          ┌──────────┐
 │  JS App  │          │  Native  │          │ SIP/PBX  │
 └────┬─────┘          └────┬─────┘          └────┬─────┘
      │                     │                     │
      │                     │  SIP INVITE         │
      │                     │◄────────────────────┤
      │                     │  180 Ringing        │
      │                     ├────────────────────►│
      │                     │                     │
      │  incoming (2)       │                     │
      │◄────────────────────┤  (event emitted)    │
      │                     │                     │
      │  ┌──────────────┐   │                     │
      │  │ Show Call UI │   │                     │
      │  │[Accept][Deny]│   │                     │
      │  └──────────────┘   │                     │
      │                     │                     │
      │  joinCall()         │                     │
      ├────────────────────►│  200 OK             │
      │                     ├────────────────────►│
      │  confirmed (5)      │  ACK                │
      │◄────────────────────┤◄────────────────────┤
      │                     │                     │
      │     ══════ Active Call (RTP audio/video) ══════
      │                     │                     │

Incoming Call — App in Background / Killed

 ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
 │  JS App  │    │  Native  │    │Push Svc  │    │ SIP/PBX  │
 └────┬─────┘    └────┬─────┘    └────┬─────┘    └────┬─────┘
      │               │               │               │
      │               │               │  Push Notify  │
      │               │               │◄──────────────┤
      │               │               │               │

  ┌───────────────── iOS (VoIP Push) ───────────────────┐
  │   │               │               │               │ │
  │   │               │  PushKit VoIP │               │ │
  │   │               │◄──────────────┤               │ │
  │   │               │               │               │ │
  │   │               │  Show CallKit │               │ │
  │   │               │  ┌──────────┐ │               │ │
  │   │               │  │ System   │ │               │ │
  │   │               │  │ Call UI  │ │               │ │
  │   │               │  │[Slide ►] │ │               │ │
  │   │               │  └──────────┘ │               │ │
  │   │               │               │               │ │
  │   │  App launched │               │               │ │
  │   │◄──────────────┤               │               │ │
  │   │  incoming (2) │               │               │ │
  │   │◄──────────────┤               │               │ │
  └─────────────────────────────────────────────────────┘

  ┌───────────────── Android (FCM) ─────────────────────┐
  │   │               │               │               │ │
  │   │               │  FCM Message  │               │ │
  │   │               │◄──────────────┤               │ │
  │   │               │               │               │ │
  │   │               │  Start Foreground Service     │ │
  │   │               │  ┌──────────────────────┐     │ │
  │   │               │  │ Full-screen Notif    │     │ │
  │   │               │  │ [Accept]  [Decline]  │     │ │
  │   │               │  └──────────────────────┘     │ │
  │   │               │               │               │ │
  │   │  App launched │               │               │ │
  │   │◄──────────────┤               │               │ │
  │   │  incoming (2) │               │               │ │
  │   │◄──────────────┤               │               │ │
  └─────────────────────────────────────────────────────┘

      │               │               │               │
      │  joinCall()   │               │               │
      ├──────────────►│  200 OK       │               │
      │               ├──────────────────────────────►│
      │  confirmed (5)│               │               │
      │◄──────────────┤               │               │
      │               │               │               │

Missed Call Flow

 ┌──────────┐          ┌──────────┐          ┌──────────┐
 │  JS App  │          │  Native  │          │ SIP/PBX  │
 └────┬─────┘          └────┬─────┘          └────┬─────┘
      │                     │                     │
      │                     │  SIP INVITE         │
      │                     │◄────────────────────┤
      │  incoming (2)       │                     │
      │◄────────────────────┤                     │
      │                     │                     │
      │     (user ignores / timeout / caller hangs up)
      │                     │                     │
      │                     │  CANCEL             │
      │                     │◄────────────────────┤
      │  disconnected (6)   │  200 OK             │
      │◄────────────────────┤────────────────────►│
      │                     │                     │
      │                     │  Show Missed Call   │
      │                     │  Notification       │
      │                     │                     │
      │  (user taps notif)  │                     │
      │                     │                     │
      │  onClickMissedCall  │                     │
      │◄────────────────────┤                     │
      │                     │                     │

Call Transfer Flow

 ┌──────────┐          ┌──────────┐          ┌──────────┐
 │  JS App  │          │  Native  │          │ SIP/PBX  │
 └────┬─────┘          └────┬─────┘          └────┬─────┘
      │                     │                     │
      │     ══════ Active Call with Party A ══════
      │                     │                     │
      │  transferCall(B)    │                     │
      ├────────────────────►│  SIP REFER → B      │
      │                     ├────────────────────►│
      │                     │                     │
      │                     │  202 Accepted       │
      │                     │◄────────────────────┤
      │                     │                     │
      │  disconnected (6)   │  BYE (from A)       │
      │◄────────────────────┤◄────────────────────┤
      │                     │                     │
      │     ══════ Party A now talks to B ══════
      │                     │                     │

Reject / Drop Call Flow

 ┌──────────┐          ┌──────────┐          ┌──────────┐
 │  JS App  │          │  Native  │          │ SIP/PBX  │
 └────┬─────┘          └────┬─────┘          └────┬─────┘
      │                     │                     │
      │  incoming (2)       │  SIP INVITE         │
      │◄────────────────────┤◄────────────────────┤
      │                     │                     │
  ┌── rejectCall() ───┐                           │
  │ Decline this      │  486 Busy Here            │
  │ device only       ├─────────────────────────► │
  └───────────────────┘  (other devices ring)     │
      │                     │                     │
  ┌── dropCall() ─────┐                           │
  │ Decline + stop    │  603 Decline              │
  │ ALL devices       ├─────────────────────────► │
  └───────────────────┘  (PBX stops all ringing)  │
      │                     │                     │

API Reference

Service & Auth

| Function | Returns | Description | |----------|---------|-------------| | startServices() | Promise<boolean> | Initialize SDK. Call once on app launch (e.g., App.tsx or index.js). Do not call multiple times | | initCallWithUserPassword(data) | Promise<boolean> | Login with SIP username/password | | initCallWithApiKey(data) | Promise<boolean> | Login with API key | | logout() | Promise<boolean> | Logout and unregister SIP |

Call Control

| Function | Returns | Description | |----------|---------|-------------| | startCall({ phoneNumber, isVideo }) | Promise<{ status, message, _id }> | Initiate outgoing call | | startCallWithUuid({ usrUuid, isVideo }) | Promise<boolean> | Call by user UUID | | joinCall() | Promise<any> | Accept incoming call | | endCall() | Promise<any> | End active call (sends SIP BYE) | | rejectCall() | Promise<boolean> | Reject on this device only (486) | | dropCall() | Promise<boolean> | Reject + stop ringing on ALL devices (603) | | transferCall({ phoneNumber }) | Promise<boolean> | Blind transfer to another number | | getInitialCall() | Promise<any> | Get pending call data on cold start |

Media Control

| Function | Returns | Description | |----------|---------|-------------| | toggleMute() | Promise<boolean\|null> | Toggle microphone mute | | toggleSpeaker() | Promise<boolean> | Toggle speakerphone | | toggleHold() | Promise<void> | Toggle call hold | | onHold({ holdStatus }) | Promise<boolean> | Set hold state explicitly | | sendDTMF({ character }) | Promise<boolean> | Send DTMF tone (0-9, *, #) | | getAudio() | Promise<any> | List available audio devices | | setAudio({ portType }) | Promise<void> | Set audio output device | | getCurrentAudio() | Promise<any> | Get current audio device |

Video Control

| Function | Returns | Description | |----------|---------|-------------| | toggleOmiVideo() | Promise<boolean> | Toggle video stream on/off | | switchOmiCamera() | Promise<boolean> | Switch front/back camera | | registerVideoEvent() | Promise<boolean> | Start receiving remote video frames | | removeVideoEvent() | Promise<boolean> | Stop receiving remote video frames |

User & Info

| Function | Returns | Description | |----------|---------|-------------| | getCurrentUser() | Promise<any> | Get logged-in user details | | getGuestUser() | Promise<any> | Get guest/remote user details | | getUserInfo(phone) | Promise<any> | Look up user by phone number |

Getter Functions (v4.0.1+)

| Function | Returns | Description | |----------|---------|-------------| | getProjectId() | Promise<string\|null> | Current project ID | | getAppId() | Promise<string\|null> | Current app ID | | getDeviceId() | Promise<string\|null> | Current device ID | | getFcmToken() | Promise<string\|null> | FCM push token | | getSipInfo() | Promise<string\|null> | SIP info (user@realm) | | getVoipToken() | Promise<string\|null> | VoIP token (iOS only) |

Notification Control

| Function | Returns | Description | |----------|---------|-------------| | configPushNotification(data) | Promise<any> | Configure push notification settings | | hideSystemNotificationSafely() | Promise<boolean> | Hide notification without unregistering | | hideSystemNotificationOnly() | Promise<boolean> | Hide notification only | | hideSystemNotificationAndUnregister(reason) | Promise<boolean> | Hide + unregister with reason |


Events

Use omiEmitter to listen for events emitted by the native SDK.

import { omiEmitter, OmiCallEvent } from 'omikit-plugin';

Event Reference

| Event | Payload | Description | |-------|---------|-------------| | onCallStateChanged | { status, callerNumber, isVideo, incoming, codeEndCall } | Call lifecycle changes | | onMuted | boolean | Microphone mute toggled | | onSpeaker | boolean | Speaker toggled | | onHold | boolean | Hold state changed | | onRemoteVideoReady | — | Remote video stream is ready | | onClickMissedCall | { callerNumber } | User tapped missed call notification | | onSwitchboardAnswer | { data } | Switchboard answered | | onCallQuality | { quality, stat } | Call quality metrics (see Quality & Diagnostics) | | onAudioChange | { data } | Audio device changed | | onRequestPermissionAndroid | { permissions } | Permission request needed (Android only) |

Usage Example

import { omiEmitter, OmiCallEvent, OmiCallState } from 'omikit-plugin';

useEffect(() => {
  const subscriptions = [
    // Call state changes
    omiEmitter.addListener(OmiCallEvent.onCallStateChanged, (data) => {
      console.log('State:', data.status, 'Caller:', data.callerNumber);

      if (data.status === OmiCallState.incoming) {
        // Navigate to incoming call screen
      }
      if (data.status === OmiCallState.confirmed) {
        // Call connected
      }
      if (data.status === OmiCallState.disconnected) {
        // Call ended, check data.codeEndCall for reason
      }
    }),

    // Mute state
    omiEmitter.addListener(OmiCallEvent.onMuted, (isMuted) => {
      setMuted(isMuted);
    }),

    // Speaker state
    omiEmitter.addListener(OmiCallEvent.onSpeaker, (isOn) => {
      setSpeaker(isOn);
    }),

    // Missed call notification tapped
    omiEmitter.addListener(OmiCallEvent.onClickMissedCall, (data) => {
      // Navigate to call history or callback
    }),

    // Call quality & diagnostics
    omiEmitter.addListener(OmiCallEvent.onCallQuality, ({ quality, stat }) => {
      console.log('Quality level:', quality); // 0=Good, 1=Medium, 2=Bad
      if (stat) {
        console.log('MOS:', stat.mos, 'Jitter:', stat.jitter, 'Latency:', stat.latency);
      }
    }),
  ];

  return () => subscriptions.forEach(sub => sub.remove());
}, []);

Enums

OmiCallState

| Value | Name | Description | |-------|------|-------------| | 0 | unknown | Initial/unknown state | | 1 | calling | Outgoing call initiated, waiting for response | | 2 | incoming | Incoming call received | | 3 | early | Early media (183 Session Progress) | | 4 | connecting | 200 OK received, establishing media | | 5 | confirmed | Call active, RTP media flowing | | 6 | disconnected | Call ended | | 7 | hold | Call on hold |

OmiStartCallStatus

| Value | Name | Description | |-------|------|-------------| | 0 | invalidUuid | Invalid user UUID | | 1 | invalidPhoneNumber | Invalid phone number format | | 2 | samePhoneNumber | Calling your own number | | 3 | maxRetry | Max retry attempts exceeded | | 4 | permissionDenied | General permission denied | | 450 | permissionMicrophone | Microphone permission needed | | 451 | permissionCamera | Camera permission needed | | 452 | permissionOverlay | Overlay permission needed | | 5 | couldNotFindEndpoint | SIP endpoint not found | | 6 | accountRegisterFailed | SIP registration failed | | 7 | startCallFailed | Call initiation failed | | 8 | startCallSuccess | Call started successfully (Android) | | 407 | startCallSuccessIOS | Call started successfully (iOS) | | 9 | haveAnotherCall | Another call is in progress | | 10 | accountTurnOffNumberInternal | Internal number has been deactivated | | 11 | noNetwork | No network connection available |

OmiAudioType

| Value | Name | Description | |-------|------|-------------| | 0 | receiver | Phone earpiece | | 1 | speaker | Speakerphone | | 2 | bluetooth | Bluetooth device | | 3 | headphones | Wired headphones |

End Call Status Codes (codeEndCall)

When a call ends (state = disconnected), the codeEndCall field in the onCallStateChanged event payload contains the status code indicating why the call ended.

Standard SIP Codes

| Code | Description | |------|-------------| | 200 | Normal call ending | | 408 | Call timeout — no answer | | 480 | Temporarily unavailable | | 486 | Busy (or call rejected via rejectCall()) | | 487 | Call cancelled before being answered | | 500 | Server error | | 503 | Server unavailable |

OMICALL Extended Codes

| Code | Description | |------|-------------| | 600 | Call declined | | 601 | Call ended by customer | | 602 | Call answered / ended by another agent | | 603 | Call declined (via dropCall() — stops ringing on ALL devices) |

Business Rule Codes (PBX)

| Code | Description | |------|-------------| | 850 | Exceeded concurrent call limit | | 851 | Exceeded call limit | | 852 | No service plan assigned — contact provider | | 853 | Internal number has been deactivated | | 854 | Number is in DNC (Do Not Call) list | | 855 | Exceeded call limit for trial plan | | 856 | Exceeded minute limit for trial plan | | 857 | Number blocked in configuration | | 858 | Unknown or unconfigured number prefix | | 859 | No available number for Viettel direction — contact provider | | 860 | No available number for Vinaphone direction — contact provider | | 861 | No available number for Mobifone direction — contact provider | | 862 | Number prefix temporarily locked for Viettel | | 863 | Number prefix temporarily locked for Vinaphone | | 864 | Number prefix temporarily locked for Mobifone | | 865 | Advertising call outside allowed time window — try again later |

Common scenarios:

User hangs up normally           → 200
Caller cancels before answer     → 487
Callee rejects (this device)     → 486  (rejectCall)
Callee rejects (all devices)     → 603  (dropCall)
Callee busy on another call      → 486
No answer / timeout              → 408 or 480
Answered by another agent        → 602
Exceeded concurrent call limit   → 850
Number in DNC list               → 854

Usage:

omiEmitter.addListener(OmiCallEvent.onCallStateChanged, (data) => {
  if (data.status === OmiCallState.disconnected) {
    const code = data.codeEndCall;

    if (code === 200) {
      console.log('Call ended normally');
    } else if (code === 487) {
      console.log('Call was cancelled');
    } else if (code === 602) {
      console.log('Call was answered by another agent');
    } else if (code >= 850 && code <= 865) {
      console.log('Business rule error:', code);
      // Show user-friendly message based on code
    } else {
      console.log('Call ended with code:', code);
    }
  }
});

Video Calls

Setup

import { OmiLocalCamera, OmiRemoteCamera } from 'omikit-plugin';

Video Components

// Local camera preview (your camera)
<OmiLocalCamera style={{ width: 120, height: 160 }} />

// Remote camera view (other party's video)
<OmiRemoteCamera style={{ width: '100%', height: '100%' }} />

Video Call Flow

// 1. Register for video events BEFORE starting call
await registerVideoEvent();

// 2. Start video call
await startCall({ phoneNumber: '0901234567', isVideo: true });

// 3. Toggle video during call
await toggleOmiVideo();   // on/off video stream
await switchOmiCamera();  // front/back camera

// 4. Listen for remote video ready
omiEmitter.addListener(OmiCallEvent.onRemoteVideoReady, () => {
  // Remote video is now available - show OmiRemoteCamera
});

// 5. Cleanup when call ends
await removeVideoEvent();

Push Notifications

Setup Guide: To configure VoIP (iOS) and FCM (Android) for receiving incoming calls, follow the detailed guide at OMICall Mobile SDK Setup.

Configuration

// Call after startServices(), before or after login
await configPushNotification({
  // Your platform-specific push configuration
});

How Push Works

iOS — VoIP Push (PushKit)

PBX ──► APNS ──► PushKit ──► App wakes up
                              ├── Report to CallKit
                              ├── Show system call UI
                              └── Register SIP & connect
  • Uses PushKit VoIP push for reliable delivery even when app is killed
  • CallKit provides native system call UI (slide to answer)
  • No user-visible notification — CallKit handles the UI

Android — FCM

PBX ──► FCM ──► App receives data message
                 ├── Start foreground service
                 ├── Show full-screen notification
                 └── Register SIP & connect
  • Uses FCM data message (not notification message)
  • Foreground service keeps the app alive during the call
  • Full-screen intent for lock screen call UI

Notification Management

// Hide the system notification without affecting SIP registration
await hideSystemNotificationSafely();

// Hide notification only
await hideSystemNotificationOnly();

// Hide notification and unregister SIP (with reason for analytics)
await hideSystemNotificationAndUnregister('user_dismissed');

Permissions (Android)

Android 15+ requires explicit runtime permissions for VoIP functionality.

Quick Setup

import {
  checkAndRequestPermissions,
  checkPermissionStatus,
  requestPermissionsByCodes,
  requestSystemAlertWindowPermission,
  openSystemAlertSetting,
} from 'omikit-plugin';

// Check and request all permissions at once
const granted = await checkAndRequestPermissions(isVideo);

// Check current permission status
const status = await checkPermissionStatus();
// Returns: { microphone: boolean, camera: boolean, overlay: boolean, ... }

Handle Permission Errors from startCall

const result = await startCall({ phoneNumber, isVideo: false });

switch (result.status) {
  case 450: // OmiStartCallStatus.permissionMicrophone
    await requestPermissionsByCodes([450]);
    break;
  case 451: // OmiStartCallStatus.permissionCamera
    await requestPermissionsByCodes([451]);
    break;
  case 452: // OmiStartCallStatus.permissionOverlay
    await requestSystemAlertWindowPermission();
    // or open system settings directly:
    await openSystemAlertSetting();
    break;
}

Permission Codes

| Code | Permission | Required for | |------|-----------|--------------| | 450 | Microphone | All calls | | 451 | Camera | Video calls | | 452 | Overlay (SYSTEM_ALERT_WINDOW) | Incoming call popup on lock screen |

Note: On iOS, permissions are handled via Info.plist and system prompts. The above functions return true on iOS.


Quality & Diagnostics

The onCallQuality event provides real-time call quality metrics during an active call.

Event Payload

{
  quality: number;   // 0 = Good, 1 = Medium, 2 = Bad
  stat: {
    mos: number;         // Mean Opinion Score (1.0 – 5.0)
    jitter: number;      // Jitter in milliseconds
    latency: number;     // Round-trip latency in milliseconds
    packetLoss: number;  // Packet loss percentage (0 – 100)
    lcn?: number;        // Loss Connect Number (Android only)
  }
}

MOS Score Thresholds

| MOS Range | Quality | Description | |-----------|---------|-------------| | ≥ 4.0 | Excellent | Clear, no perceptible issues | | 3.5 – 4.0 | Good | Minor impairments, generally clear | | 3.0 – 3.5 | Fair | Noticeable degradation | | 2.0 – 3.0 | Poor | Significant degradation | | < 2.0 | Bad | Nearly unusable |

Network Freeze Detection

When lcn (Loss Connect Number) increases consecutively, it indicates potential network freeze — useful for showing a "weak network" warning in the UI.

Usage Example

import { omiEmitter, OmiCallEvent } from 'omikit-plugin';

omiEmitter.addListener(OmiCallEvent.onCallQuality, ({ quality, stat }) => {
  // quality: 0 = Good, 1 = Medium, 2 = Bad
  if (quality >= 2 && stat) {
    console.warn(
      `Poor call quality — MOS: ${stat.mos}, Jitter: ${stat.jitter}ms, ` +
      `Latency: ${stat.latency}ms, Loss: ${stat.packetLoss}%`
    );
    // Show weak network warning to user
  }
});

Advanced Features

Check Credentials Without Connecting

Validate credentials without establishing a SIP connection:

const result = await checkCredentials({
  userName: 'sip_user',
  password: 'sip_password',
  realm: 'your_realm',
});
// result: { success: boolean, statusCode: number, message: string }

Register With Options

Full control over registration behavior:

const result = await registerWithOptions({
  // Registration options
});
// result: { success: boolean, statusCode: number, message: string }

Keep-Alive

Monitor and maintain SIP connection:

// Check current keep-alive status
const status = await getKeepAliveStatus();

// Manually trigger a keep-alive ping
await triggerKeepAlivePing();

Cold Start Call Handling

When the app is launched from a push notification:

// Call this in your app's entry point
const initialCall = await getInitialCall();

if (initialCall) {
  // There's a pending call — navigate to call screen
  console.log('Pending call from:', initialCall.callerNumber);
}

Troubleshooting

| Problem | Cause | Solution | |---------|-------|----------| | LINKING_ERROR on launch | Native module not linked | Run pod install (iOS) or rebuild the app | | Login returns false | Wrong SIP credentials or network | Verify userName, password, realm, host | | accountRegisterFailed (status 6) | SIP registration failed | Check realm and host params; verify network connectivity | | No incoming calls | Push not configured | Ensure FCM (Android) / PushKit VoIP (iOS) is set up | | No incoming calls on iOS (killed) | Missing Background Mode | Enable "Voice over IP" in Background Modes | | Incoming call on Android — no UI | Missing overlay permission | Call requestSystemAlertWindowPermission() | | startCall returns 450/451/452 | Missing runtime permissions | Call requestPermissionsByCodes([code]) | | No audio during call | Audio route issue | Check getAudio() and setAudio() | | Video not showing | Video event not registered | Call registerVideoEvent() before the call | | NativeEventEmitter warning (iOS) | Old RN bridge issue | Upgrade to v4.0.x with New Architecture | | Invalid local URI in logs | Empty proxy/host in login | Pass host parameter in initCallWithUserPassword | | Build error with New Arch | Codegen not configured | Ensure codegenConfig exists in package.json | | iOS Simulator build fails (arm64) | OmiKit binary does not include simulator slice | iOS Simulator is not supported. OmiKit SDK is device-only (arm64 real device). Always build and test on a physical iOS device |


Documentation

Full documentation in ./docs/:

License

MIT — ViHAT Group