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.2.7

Published

Omikit Plugin by ViHAT

Readme

OMICALL SDK for React Native

npm version npm downloads license

npm install omikit-plugin

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

✅ Expo is supported (v4.2.0+) via a config plugin — works with expo prebuild / dev-client / EAS Build (not Expo Go, since the SDK ships native code). Verified end-to-end (outbound + inbound calls) on real iOS and Android devices. See Expo Setup. React Native CLI is also fully supported (manual native setup below).


Table of Contents


Compatibility

| omikit-plugin | React Native | Architecture | Installation | |---------------|--------------|--------------|--------------| | 4.x (latest) | 0.74+ | Old + New (auto-detect) | npm install omikit-plugin | | 3.3.x (legacy) | 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 | omi-sdk | 2.8.17 | | iOS | OmiKit | 1.11.29 |

Platform Requirements

| | Android | iOS | |--|---------|-----| | Min SDK | API 24 (Android 7.0) | iOS 13.0 | | Target SDK | API 36 (Android 16) | — | | Compile SDK | API 36 | — |

Device Requirements

| Requirement | Platform | Notes | |-------------|----------|-------| | Physical device | iOS (required) | iOS Simulator is not supported — OmiKit binary is arm64 device-only | | Physical device | Android (recommended) | Emulator works for basic UI testing but VoIP/audio routing is unreliable | | Google Play Services | Android (required) | Required for FCM push notifications | | Microphone | Both (required) | Required for all calls | | Camera | Both (optional) | Only required for video calls | | Internet | Both (required) | SIP registration + RTP media streaming |

Package Size

| Component | Size | |-----------|------| | npm package (total) | ~353 KB | | Android native code | ~4.7 MB | | iOS native code | ~176 KB |

Note: These sizes are for the plugin only. The native SDKs (OmiKit/OMIKIT) are installed separately via CocoaPods/Maven and will add to the final app size.


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.


Expo Setup

For Expo projects using prebuild / dev-client / EAS Build. Requires a custom dev client (not Expo Go) because the SDK ships native code — this is normal for any native library. If you use React Native CLI (bare workflow), skip this section and follow Android Setup / iOS Setup instead.

1. Install

npx expo install omikit-plugin

2. Add the config plugin to app.json

The plugin automates all native setup (permissions, background modes, Push capability, incoming-call intent-filter, MainActivity attributes, maven repos, and OmiKit runtime init) — you do not edit AppDelegate, MainActivity, Info.plist, or AndroidManifest by hand.

{
  "expo": {
    "plugins": [
      [
        "omikit-plugin",
        {
          "environment": "production",
          "enableVideo": false,
          "callKitImage": "call_image",
          "maxCall": 1,
          "microphonePermission": "This app needs microphone access for voice calls.",
          "cameraPermission": "This app needs camera access for video calls."
        }
      ]
    ]
  }
}

3. Android — Maven credentials & Kotlin version

The Android SDK (io.omicrm.vihat:omi-sdk) is served from GitHub Packages (private), so Gradle needs credentials. The config plugin adds the repo with a credentials block that reads OMI_USER / OMI_TOKEN from the environment (or a gradle property) — set them before building:

export OMI_USER=omicall
export OMI_TOKEN=<omi_github_packages_token>

Contact the OMICall development team to get OMI_USER / OMI_TOKEN. On EAS Build, add them as secrets. You can also put them in android/gradle.properties (keep it gitignored — never commit the token).

Match your Kotlin version to your React Native version (RN 0.76 → 1.9.24) via expo-build-properties to avoid a Compose-compiler mismatch:

[
  "expo-build-properties",
  { "android": { "kotlinVersion": "1.9.24" } }
]

4. Prebuild & run

npx expo prebuild --clean
npx expo run:ios                 # iOS — use a physical device to test VoIP push
OMI_USER=omicall OMI_TOKEN=… npx expo run:android   # Android needs the token

On EAS Build, no extra steps besides the secrets above — the plugin runs during the prebuild phase.

Plugin options

| Option | Type | Default | Description | |--------|------|---------|-------------| | environment | 'sandbox' \| 'production' | 'production' | OmiKit environment | | enableVideo | boolean | false | Enables camera permission, NSCameraUsageDescription, and video-view interop. Leave false for audio-only apps | | userNameKey | string | 'full_name' | OmiKit userNameKey | | maxCall | number | 1 | Max concurrent calls | | callKitImage | string | 'call_image' | iOS CallKit image asset name | | typePushVoip | 'default' \| 'callkit' | 'default' | VoIP push type | | microphonePermission | string | (generic) | iOS NSMicrophoneUsageDescription message | | cameraPermission | string | (generic) | iOS NSCameraUsageDescription message (video only) | | apsEnvironment | 'development' \| 'production' | 'development' | iOS aps-environment entitlement | | onPremise | object | — | On-premise endpoint overrides (see below) |

On-premise (self-hosted) config

For customers routing traffic to their own infrastructure, pass an onPremise object. Every field is optional — omit a field to keep the SDK default (OMI cloud). The SDK applies these before its first request at launch.

[
  "omikit-plugin",
  {
    "environment": "production",
    "onPremise": {
      "mobileSdkHost": "omisdk.your-domain.com",
      "publicApiHost": "public.your-domain.com",
      "pushInfoHost": "push-info.your-domain.com",
      "logUploadHost": "log.your-domain.com",
      "sipProxy": "sip.your-domain.com",
      "stunServer": "stun.your-domain.com",
      "turnServer": "turn.your-domain.com",
      "turnUsername": "your-turn-user",
      "turnPassword": "your-turn-pass"
    }
  }
]

⚠️ Secrets: app.json is committed to git. Do not put a real turnPassword there for production — inject it via an env-driven prebuild config (e.g. app.config.js reading process.env) or EAS secrets.

Firebase / FCM

Android inbound calls require FCM (iOS uses PushKit, handled natively — no Firebase needed for iOS calls). Firebase is your dependency — omikit-plugin does not bundle it. Install it and let its own config plugin wire the native side:

  1. Install: npx expo install @react-native-firebase/app @react-native-firebase/messaging
  2. Supply your Google services files: google-services.json (Android) and GoogleService-Info.plist (iOS).
  3. Fetch the FCM token via messaging().getToken() and pass it to the SDK at login.

Declare everything in app.json (see the combined block below).

Use useFrameworks: "dynamic" — not "static". @react-native-firebase on iOS needs use_frameworks!. Choose dynamic: Firebase v21 supports it and OmiKit ships as a dynamic xcframework, so it builds out of the box. "static" triggers GoogleUtilities does not define modules and (on New Architecture) Redefinition of module 'ReactCommon' — do not use it.

You do not need to create any custom config plugin (no plugins/*.js file). Every plugin below comes from npm and is referenced by name.

Full Expo app.json (copy-paste)

{
  "expo": {
    "ios": {
      "bundleIdentifier": "your.bundle.id",
      "googleServicesFile": "./GoogleService-Info.plist"
    },
    "android": {
      "package": "your.package.name",
      "googleServicesFile": "./google-services.json"
    },
    "plugins": [
      ["expo-build-properties", { "ios": { "useFrameworks": "dynamic" } }],
      ["react-native-permissions", { "iosPermissions": ["Microphone"] }],
      "@react-native-firebase/app",
      "@react-native-firebase/messaging",
      ["omikit-plugin", { "environment": "production" }]
    ]
  }
}

Then npx expo prebuild --clean && npx expo run:ios. See expo-example/ for a complete, working setup (its google-services.json / GoogleService-Info.plist are gitignored — supply your own).

Microphone permission: react-native-permissions with iosPermissions: ["Microphone"] is required — it generates setup_permissions(['Microphone']) in the Podfile so the SDK can request the mic. Without it, iOS reports the mic as unavailable and calls fail.

Video calls on New Architecture

Video views (OmiLocalCameraView / OmiRemoteCameraView) are legacy ViewManagers and require bridge mode. If you enable enableVideo on New Architecture, do not enable React Native bridgeless mode, or video won't render.

How it works (no AppDelegate/MainActivity edits)

OmiKit's runtime init runs via native lifecycle hooks shipped in the SDK, so there is nothing to inject into your AppDelegate / MainActivity and nothing breaks across RN/Expo upgrades:

  • iOSOmikitExpoAppDelegateBridge (an Objective-C class in the pod) registers itself as an Expo AppDelegate subscriber at load time (+load) and runs the OmiKit init (CallKit provider, PushKit registry, setEnviroment, on-premise, notification-center delegate), reading its config from the Info.plist keys the config plugin writes. On a bare React Native app (no ExpoModulesCore) it detects Expo is absent and no-ops, so your existing AppDelegate integration is untouched.
  • Android — Expo autolinking discovers the plugin via expo-module.config.json and registers OmikitReactActivityLifecycleListener, which forwards onResume / onNewIntent to the SDK.

Verified end-to-end on real iOS and Android devices — expo prebuild + build + launch shows the SDK initialising automatically ([OMI NATIVE] +load … → didFinishLaunching — init OmiKit), and both outbound and inbound calls work.


Android Setup

Note: This section is for React Native CLI (bare) projects. Expo projects should use Expo Setup instead — the config plugin does all of this automatically.

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" />

Note: By default, the SDK declares WRITE_CALL_LOG permission to save calls to the device's call history. If your app does NOT want calls saved to the device call log, add the following to remove it:

<!-- Remove if you do NOT want calls saved to device call history -->
<uses-permission android:name="android.permission.WRITE_CALL_LOG"
    tools:node="remove" />

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

2. Incoming Call Activity (Required)

Your main Activity must handle incoming call intents from the SDK. Add the following intent-filter to your MainActivity in AndroidManifest.xml:

<activity
  android:name=".MainActivity"
  android:showWhenLocked="true"
  android:turnScreenOn="true"
  android:launchMode="singleTask"
  ...>

  <!-- Incoming call intent-filter (required for lock screen) -->
  <intent-filter>
    <action android:name="${applicationId}.ACTION_INCOMING_CALL" />
    <action android:name="android.intent.action.CALL" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:host="incoming_call" android:scheme="omisdk" />
  </intent-filter>
</activity>

Important: The ${applicationId}.ACTION_INCOMING_CALL action ensures incoming calls show correctly on lock screen for Android 9-14. Without this, the default dialer may intercept the intent instead of your app.

3. Firebase Cloud Messaging (FCM)

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

In android/app/build.gradle:

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

4. 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://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://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.

5. New Architecture (Optional)

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

newArchEnabled=true

6. Code Shrinking (R8 / ProGuard)

No configuration needed. Turning on minifyEnabled true in your release build works out of the box from v4.2.7 — the plugin ships its ProGuard rules through consumerProguardFiles, and the OMI Android SDK ships its own, so your app inherits both automatically:

// android/app/build.gradle — this is enough
buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
    }
}

Do not copy plugin rules into your own proguard-rules.pro; they are applied for you.

R8 removes and renames code it believes is unused. Several things here are reached only by name at runtime, so R8 cannot see the reference and would strip them — the app then fails at runtime with no build-time warning:

| Kept | Why | |------|-----| | com.omikitplugin.** | React Native instantiates native modules and view managers reflectively and looks them up by the string returned from getName() | | com.omikitplugin.expo.OmikitExpoPackage | Expo autolinking loads it by the class name written in expo-module.config.json | | vn.vihat.omicall.**, net.gotev.sipservice.**, org.pjsip.** | SIP stack and SDK entry points resolved by name (shipped by the OMI SDK) | | retrofit2.Call, retrofit2.Response, generic signatures | Retrofit reads the generic type argument at call time; stripping it throws ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType | | SDK data models | Gson maps JSON keys to field names; renaming a field silently turns parsed values into null |

If you are on a version older than 4.2.7 and cannot upgrade yet, add these to your app's proguard-rules.pro as a stopgap:

-keep class com.omikitplugin.** { *; }
-keep class vn.vihat.omicall.** { *; }
-keep class net.gotev.sipservice.** { *; }
-keep class org.pjsip.** { *; }
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-keepattributes Signature, *Annotation*, InnerClasses, EnclosingMethod

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

The AppDelegate template differs depending on your React Native version. Choose the one that matches your project:

RN 0.74 – 0.78 (RCTAppDelegate pattern)

#import <RCTAppDelegate.h>
#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>
#import <OmiKit/OmiKit.h>

@interface AppDelegate : RCTAppDelegate <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>

@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, strong) PushKitManager *pushkitManager;
@property (nonatomic, strong) CallKitProviderDelegate *provider;
@property (nonatomic, strong) PKPushRegistry *voipRegistry;

@end
#import "AppDelegate.h"
#import <Firebase.h>
#import <React/RCTBundleURLProvider.h>
#import <OmiKit/OmiKit.h>

#if __has_include("OmikitNotification.h")
#import "OmikitNotification.h"
#else
#import <omikit_plugin/OmikitNotification.h>
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  self.moduleName = @"YourAppName"; // Replace with your app name

  // ----- OmiKit Config ------
  [OmiClient setEnviroment:KEY_OMI_APP_ENVIROMENT_SANDBOX
                userNameKey:@"full_name"
                    maxCall:2
               callKitImage:@"call_image"
              typePushVoip:TYPE_PUSH_CALLKIT_DEFAULT];

  self.provider = [[CallKitProviderDelegate alloc]
      initWithCallManager:[OMISIPLib sharedInstance].callManager];
  self.voipRegistry = [[PKPushRegistry alloc]
      initWithQueue:dispatch_get_main_queue()];
  self.pushkitManager = [[PushKitManager alloc]
      initWithVoipRegistry:self.voipRegistry];

  if (@available(iOS 10.0, *)) {
    [UNUserNotificationCenter currentNotificationCenter].delegate =
        (id<UNUserNotificationCenterDelegate>)self;
  }

  if ([FIRApp defaultApp] == nil) {
    [FIRApp configure];
  }
  // ----- End OmiKit Config ------

  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

// Handle foreground notifications
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
  completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
}

// Handle missed call notification tap
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)())completionHandler
{
  NSDictionary *userInfo = response.notification.request.content.userInfo;
  if (userInfo && [userInfo valueForKey:@"omisdkCallerNumber"]) {
    [OmikitNotification didRecieve:userInfo];
  }
  completionHandler();
}

// Register push notification token
- (void)application:(UIApplication *)app
    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken
{
  const unsigned char *data = (const unsigned char *)[devToken bytes];
  NSMutableString *token = [NSMutableString string];
  for (NSUInteger i = 0; i < [devToken length]; i++) {
    [token appendFormat:@"%02.2hhX", data[i]];
  }
  [OmiClient setUserPushNotificationToken:[token copy]];
}

// Terminate all calls when app is killed
- (void)applicationWillTerminate:(UIApplication *)application {
  @try {
    [OmiClient OMICloseCall];
  } @catch (NSException *exception) {}
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
  return [self bundleURL];
}

- (NSURL *)bundleURL
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

@end

RN 0.79+ (RCTReactNativeFactory pattern)

RN 0.79+ uses RCTReactNativeFactory instead of RCTAppDelegate. Add OmiKit setup in your existing AppDelegate:

import UIKit
import React
import ReactAppDependencyProvider
import OmiKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
  var window: UIWindow?
  var provider: CallKitProviderDelegate?
  var pushkitManager: PushKitManager?
  var voipRegistry: PKPushRegistry?

  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // React Native setup
    let delegate = ReactNativeDelegate()
    let factory = RCTReactNativeFactory(delegate: delegate)
    delegate.dependencyProvider = RCTAppDependencyProvider()

    window = UIWindow(frame: UIScreen.main.bounds)
    factory.startReactNative(
      withModuleName: "YourAppName",
      in: window,
      launchOptions: launchOptions
    )

    // ----- OmiKit Config ------
    #ifdef DEBUG
    OmiClient.setEnviroment(KEY_OMI_APP_ENVIROMENT_SANDBOX,
                            userNameKey: "full_name",
                            maxCall: 1,
                            callKitImage: "call_image",
                            typePushVoip: TYPE_PUSH_CALLKIT_DEFAULT)
    #else
    OmiClient.setEnviroment(KEY_OMI_APP_ENVIROMENT_PRODUCTION,
                            userNameKey: "full_name",
                            maxCall: 1,
                            callKitImage: "call_image",
                            typePushVoip: TYPE_PUSH_CALLKIT_DEFAULT)
    #endif

    provider = CallKitProviderDelegate(callManager: OMISIPLib.sharedInstance().callManager)
    voipRegistry = PKPushRegistry(queue: .main)
    pushkitManager = PushKitManager(voipRegistry: voipRegistry!)

    UNUserNotificationCenter.current().delegate = self
    FirebaseApp.configure()
    // ----- End OmiKit Config ------

    return true
  }

  // Handle missed call notification tap
  func userNotificationCenter(_ center: UNUserNotificationCenter,
                              didReceive response: UNNotificationResponse,
                              withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo
    if userInfo["omisdkCallerNumber"] != nil {
      OmikitNotification.didRecieve(userInfo)
    }
    completionHandler()
  }

  // Register push notification token
  func application(_ application: UIApplication,
                   didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhX", $0) }.joined()
    OmiClient.setUserPushNotificationToken(token)
  }

  // Terminate all calls when app is killed
  func applicationWillTerminate(_ application: UIApplication) {
    try? OmiClient.omiCloseCall()
  }
}

class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
  override func sourceURL(for bridge: RCTBridge) -> URL? {
    return bundleURL()
  }

  override func bundleURL() -> URL? {
    #if DEBUG
    return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
    #else
    return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
    #endif
  }
}
#import "AppDelegate.h"
#import <Firebase.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTReactNativeFactory.h>
#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>
#import <OmiKit/OmiKit.h>

#if __has_include("OmikitNotification.h")
#import "OmikitNotification.h"
#else
#import <omikit_plugin/OmikitNotification.h>
#endif

@interface ReactNativeDelegate : RCTDefaultReactNativeFactoryDelegate
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  ReactNativeDelegate *delegate = [ReactNativeDelegate new];
  RCTReactNativeFactory *factory = [[RCTReactNativeFactory alloc] initWithDelegate:delegate];
  delegate.dependencyProvider = [RCTAppDependencyProvider new];

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  [factory startReactNativeWithModuleName:@"YourAppName" in:self.window launchOptions:launchOptions];

  // ----- OmiKit Config ------
  #ifdef DEBUG
        [OmiClient setEnviroment:KEY_OMI_APP_ENVIROMENT_SANDBOX userNameKey:@"full_name" maxCall:1 callKitImage:@"icYourApp" typePushVoip:@"background"];
  #else
        [OmiClient setEnviroment:KEY_OMI_APP_ENVIROMENT_PRODUCTION userNameKey:@"full_name" maxCall:1 callKitImage:@"icYourApp" typePushVoip:@"background"];
  #endif

  self.provider = [[CallKitProviderDelegate alloc]
      initWithCallManager:[OMISIPLib sharedInstance].callManager];
  self.voipRegistry = [[PKPushRegistry alloc]
      initWithQueue:dispatch_get_main_queue()];
  self.pushkitManager = [[PushKitManager alloc]
      initWithVoipRegistry:self.voipRegistry];

  if (@available(iOS 10.0, *)) {
    [UNUserNotificationCenter currentNotificationCenter].delegate =
        (id<UNUserNotificationCenterDelegate>)self;
  }

  if ([FIRApp defaultApp] == nil) {
    [FIRApp configure];
  }
  // ----- End OmiKit Config ------

  return YES;
}

// Handle missed call notification tap
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)())completionHandler
{
  NSDictionary *userInfo = response.notification.request.content.userInfo;
  if (userInfo && [userInfo valueForKey:@"omisdkCallerNumber"]) {
    [OmikitNotification didRecieve:userInfo];
  }
  completionHandler();
}

// Register push notification token
- (void)application:(UIApplication *)app
    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken
{
  const unsigned char *data = (const unsigned char *)[devToken bytes];
  NSMutableString *token = [NSMutableString string];
  for (NSUInteger i = 0; i < [devToken length]; i++) {
    [token appendFormat:@"%02.2hhX", data[i]];
  }
  [OmiClient setUserPushNotificationToken:[token copy]];
}

// Terminate all calls when app is killed
- (void)applicationWillTerminate:(UIApplication *)application {
  @try {
    [OmiClient OMICloseCall];
  } @catch (NSException *exception) {}
}

@end

@implementation ReactNativeDelegate

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
  return [self bundleURL];
}

- (NSURL *)bundleURL
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

@end

Note: Replace YourAppName with your app's module name. For production, change KEY_OMI_APP_ENVIROMENT_SANDBOX to KEY_OMI_APP_ENVIROMENT_PRODUCTION.

5. New Architecture (Optional)

In your Podfile:

ENV['RN_NEW_ARCH_ENABLED'] = '1'

For New Architecture with video call support, add Fabric interop registration in AppDelegate.mm inside didFinishLaunchingWithOptions, before return [super ...]:

// Required imports at the top of AppDelegate.mm
#import <React-RCTFabric/React/RCTComponentViewFactory.h>
#import <React-RCTFabric/React/RCTLegacyViewManagerInteropComponentView.h>

// Inside didFinishLaunchingWithOptions, before return:
[RCTLegacyViewManagerInteropComponentView supportLegacyViewManagerWithName:@"OmiLocalCameraView"];
[RCTLegacyViewManagerInteropComponentView supportLegacyViewManagerWithName:@"OmiRemoteCameraView"];

Important: Bridgeless mode is not yet supported for video call views. If you use New Architecture, keep bridge mode enabled (do not add bridgelessEnabled returning 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.5)   │                  │  (v1.11.4)  │
   │      │      │                  │      │       │
   │      ▼      │                  │      ▼       │
   │  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 | | logoutAndWait() | Promise<boolean> | (v4.1.7+) Logout that resolves only after the SDK finishes the backend devices/remove HTTP call and clears local state. Use before an immediate re-login to avoid race conditions |

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. (v4.1.7+ Android) Sourced from OmiClient.getAppId() to match getOmiDevices() payload | | getDeviceId() | Promise<string\|null> | Current device ID. (v4.1.7+ Android) Sourced from OmiClient.getDeviceId() to match getOmiDevices() payload | | getFcmToken() | Promise<string\|null> | FCM push token | | getSipInfo() | Promise<string\|null> | SIP info (user@realm) | | getVoipToken() | Promise<string\|null> | VoIP token (iOS only) |

On-Premise Endpoint Configuration (v4.1.8+) — Native Only

For enterprise customers self-hosting OMI infrastructure. Override SDK default endpoints / SIP proxy / STUN / TURN with the customer's own hosts. Each field is optional — omit any field to keep the SDK default. Persisted natively across app relaunches.

There is no JavaScript API for this feature. Config must be set in AppDelegate (iOS) / MainApplication (Android), BEFORE React Native bootstraps. Setting from JS would race the SDK's first HTTP / SIP call (FCM token, push registration, VoIP push handler) and those requests would hit the default cloud endpoints. The native API persists the config so subsequent app launches are race-free.

iOS — AppDelegate.m

Add the call at the top of didFinishLaunchingWithOptions:, BEFORE the existing RN bootstrap and BEFORE any other Omi call:

#import <OmiKit/OmiKit.h>

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  [OmiClient setOnPremiseInfoWithMobileSdkHost:@"omisdk.your-domain.com"
                                callEventHost:@"call-event.your-domain.com"
                                publicApiHost:@"public.your-domain.com"
                                 pushInfoHost:@"push-info.your-domain.com"
                                  app2AppHost:@"app-2-app.your-domain.com"
                                logUploadHost:@"log-upload.your-domain.com"
                                     sipProxy:@"sig.your-domain.com:5222"
                                   stunServer:@"stun.your-domain.com:3478"
                                   turnServer:@"turn.your-domain.com:2222"
                                 turnUsername:@"your-turn-user"
                                 turnPassword:@"your-turn-pass"];

  // ... existing RN bootstrap (RCTAppDelegate / RCTReactNativeFactory)
}

To revert: [OmiClient clearOnPremiseInfo];

Android — MainApplication.kt

Add the call at the top of onCreate(), BEFORE SoLoader.init and RN init:

import vn.vihat.omicall.omisdk.OmiClient

class MainApplication : Application(), ReactApplication {
  override fun onCreate() {
    super.onCreate()

    OmiClient.setOnPremiseInfo(
      this,
      mobileSdkHost = "omisdk.your-domain.com",
      callEventHost = "call-event.your-domain.com",
      publicApiHost = "public.your-domain.com",
      pushInfoHost  = "push-info.your-domain.com",
      app2AppHost   = "app-2-app.your-domain.com",
      logUploadHost = "log-upload.your-domain.com",
      sipProxy      = "sig.your-domain.com:5222",
      stunServer    = "stun.your-domain.com:3478",
      turnServer    = "turn.your-domain.com:2222",
      turnUsername  = "your-turn-user",
      turnPassword  = "your-turn-pass",
    )

    SoLoader.init(this, false)
    // ... existing RN bootstrap
  }
}

To revert: OmiClient.clearOnPremiseInfo(this).

Field Reference

HTTP host groups (SDK replaces scheme + host only; path + query preserved 100%):

| Field | Replaces | |-------|----------| | mobileSdkHost | omisdk-v1*.omicrm.com — devices, extensions, network info, ICE provider, rtp log | | callEventHost | call-event-v2*.omicrm.com — call-action APIs | | publicApiHost | public-v1*.omicrm.com — init call API | | pushInfoHost | push-info-v2*.omicrm.com — has-answered | | app2AppHost | app-2-app*.omicrm.com — agent/customer login | | logUploadHost | elastic-v2*.omicrm.com — log upload |

SIP / Media ("host:port" format):

| Field | SDK default | |-------|------------| | sipProxy | 171.244.138.14:5222 | | stunServer | stun.omicrm.com:3478 | | turnServer | turn.omicrm.com:2222 | | turnUsername | embedded credentials | | turnPassword | embedded credentials |

Behavior Notes

  • Config is persisted natively (iOS NSUserDefaults key omicall/onpremise_config_v1, Android SharedPreferences omicall_onpremise/config_v1) — no need to set on every app launch after the first.
  • Priority: HTTP → on-premise > SDK default. SIP / Media → on-premise > dynamic API provider > SDK default.
  • Android DNS: when on-premise is active, the SDK bypasses custom public DNS (8.8.8.8 / 1.1.1.1) and uses system DNS so internal hostnames resolve over the customer's private network / VPN. Applies to both OkHttp HTTP layer and PJSIP native.
  • Empty strings, null, and missing fields are treated identically as "keep SDK default" for that field.
  • Requires native SDK: iOS OmiKit ≥ 1.11.25, Android OMICore ≥ 2.7.4.
  • Backward compatible: clients that do not call setOnPremiseInfo see byte-for-byte identical behavior to earlier versions.

Backend Device Registration Check (v4.1.7+)

Read-only diagnostics for verifying the local device record still exists on the OMI backend. Useful after app reinstall, account migration, or backend cleanup. The SDK never auto-logouts — your app decides the recovery action.

| Function | Returns | Description | |----------|---------|-------------| | getOmiDevices() | Promise<OmiDeviceInfo[]> | Fetch all devices registered on the OMI backend for the active SIP user. Empty array when not logged in / network error / parse error — never rejects | | isCurrentDeviceRegistered() | Promise<boolean> | true if the local deviceId + appId is in the backend list. Returns false early when not logged in (no HTTP call) | | needsReLogin() | Promise<boolean> | true when SIP user is set locally but the backend has no matching device (stale session — user must logout + login again) | | findSipNumberByDeviceId(devices, deviceId) | string \| null | Pure JS helper. Returns the sipNumber for the given device ID in the array, or null if not found |

OmiDeviceInfo shape (camelCase, normalized at JS layer):

type OmiDeviceInfo = {
  deviceId: string;
  token: string;
  deviceType: 'ios' | 'android' | string;
  voipToken: string;
  appId: string;
  createdTime: string;
  projectId: string;
  sipNumber: string;
};

Recommended usage:

import {
  getOmiDevices,
  needsReLogin,
  findSipNumberByDeviceId,
  getDeviceId,
  logoutAndWait,
} from 'omikit-plugin';


// Or: verify the SIP account matches the one bound to this device
const devices = await getOmiDevices();
const myDeviceId = await getDeviceId();
const boundSip = findSipNumberByDeviceId(devices, myDeviceId ?? '');
if (boundSip !== expectedUsername) {
  await logoutAndWait();
  // Then re-login with correct credentials
}

Notes:

  • Each call performs a fresh HTTP request — no caching. Call once after login / on foreground, not in tight loops.
  • Requires native SDK: iOS OmiKit ≥ 1.11.19, Android OMICore ≥ 2.6.21.

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-f