omikit-plugin 3.3.29 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +994 -1217
  2. package/android/build.gradle +22 -72
  3. package/android/gradle.properties +4 -4
  4. package/android/src/main/java/com/omikitplugin/FLLocalCameraModule.kt +1 -1
  5. package/android/src/main/java/com/omikitplugin/FLRemoteCameraModule.kt +1 -1
  6. package/android/src/main/java/com/omikitplugin/OmikitPluginModule.kt +326 -356
  7. package/android/src/main/java/com/omikitplugin/constants/constant.kt +2 -1
  8. package/ios/CallProcess/CallManager.swift +45 -35
  9. package/ios/Constant/Constant.swift +1 -0
  10. package/ios/Library/OmikitPlugin.m +75 -1
  11. package/ios/Library/OmikitPlugin.swift +199 -16
  12. package/ios/OmikitPlugin-Protocol.h +161 -0
  13. package/lib/commonjs/NativeOmikitPlugin.js +9 -0
  14. package/lib/commonjs/NativeOmikitPlugin.js.map +1 -0
  15. package/lib/commonjs/index.js +11 -0
  16. package/lib/commonjs/index.js.map +1 -1
  17. package/lib/commonjs/omi_audio_type.js +20 -0
  18. package/lib/commonjs/omi_audio_type.js.map +1 -0
  19. package/lib/commonjs/omi_local_camera.js +18 -2
  20. package/lib/commonjs/omi_local_camera.js.map +1 -1
  21. package/lib/commonjs/omi_remote_camera.js +18 -2
  22. package/lib/commonjs/omi_remote_camera.js.map +1 -1
  23. package/lib/commonjs/omi_start_call_status.js +30 -0
  24. package/lib/commonjs/omi_start_call_status.js.map +1 -1
  25. package/lib/commonjs/omikit.js +125 -14
  26. package/lib/commonjs/omikit.js.map +1 -1
  27. package/lib/module/NativeOmikitPlugin.js +3 -0
  28. package/lib/module/NativeOmikitPlugin.js.map +1 -0
  29. package/lib/module/index.js +1 -0
  30. package/lib/module/index.js.map +1 -1
  31. package/lib/module/omi_audio_type.js +14 -0
  32. package/lib/module/omi_audio_type.js.map +1 -0
  33. package/lib/module/omi_local_camera.js +19 -2
  34. package/lib/module/omi_local_camera.js.map +1 -1
  35. package/lib/module/omi_remote_camera.js +19 -2
  36. package/lib/module/omi_remote_camera.js.map +1 -1
  37. package/lib/module/omi_start_call_status.js +30 -0
  38. package/lib/module/omi_start_call_status.js.map +1 -1
  39. package/lib/module/omikit.js +119 -15
  40. package/lib/module/omikit.js.map +1 -1
  41. package/omikit-plugin.podspec +26 -24
  42. package/package.json +11 -2
  43. package/src/NativeOmikitPlugin.ts +160 -0
  44. package/src/index.tsx +2 -1
  45. package/src/omi_audio_type.tsx +9 -0
  46. package/src/omi_local_camera.tsx +17 -3
  47. package/src/omi_remote_camera.tsx +17 -3
  48. package/src/omi_start_call_status.tsx +29 -10
  49. package/src/omikit.tsx +118 -28
  50. package/src/types/index.d.ts +111 -11
package/README.md CHANGED
@@ -1,1461 +1,1238 @@
1
- # 📦 OMICALL SDK FOR React-Native
1
+ # OMICALL SDK for React Native
2
2
 
3
- The OmiKit exposes the 📦 <a href="https://www.npmjs.com/package/omikit-plugin">omikit-plugin</a>.
3
+ The [omikit-plugin](https://www.npmjs.com/package/omikit-plugin) enables VoIP/SIP calling via the OMICALL platform with support for both Old and **New Architecture** (TurboModules + Fabric).
4
4
 
5
- The most important part of the framework is :
5
+ **Status:** Active maintenance | **Version:** 4.0.1
6
6
 
7
- - ✅ Help to easy integrate with Omicall.
8
- - ✅ Easy custom Call UI/UX.
9
- - ✅ Optimize codec voip for you.
10
- - ✅ Full inteface to interactive with core function like sound/ringtone/codec.
7
+ ---
11
8
 
12
- ### 📝 Status
9
+ ## Table of Contents
13
10
 
14
- Currently active maintenance and improve performance
15
- <br>
11
+ - [Compatibility](#compatibility)
12
+ - [Installation](#installation)
13
+ - [Android Setup](#android-setup)
14
+ - [iOS Setup](#ios-setup)
15
+ - [Architecture Overview](#architecture-overview)
16
+ - [Quick Start](#quick-start)
17
+ - [Authentication](#authentication)
18
+ - [Call Flows (ASCII Diagrams)](#call-flows)
19
+ - [API Reference](#api-reference)
20
+ - [Events](#events)
21
+ - [Enums](#enums)
22
+ - [Video Calls](#video-calls)
23
+ - [Push Notifications](#push-notifications)
24
+ - [Permissions (Android)](#permissions-android)
25
+ - [Quality & Diagnostics](#quality--diagnostics)
26
+ - [Advanced Features](#advanced-features)
27
+ - [Troubleshooting](#troubleshooting)
28
+ - [License](#license)
16
29
 
17
- ## 🛠️ Configuration
30
+ ---
18
31
 
19
- ### 🛠️ Install
20
- <br>
32
+ ## Compatibility
21
33
 
22
- Install via npm:
34
+ | omikit-plugin | React Native | Architecture | Installation |
35
+ |---------------|--------------|--------------|--------------|
36
+ | **4.0.x** (latest) | 0.74+ | Old + New (auto-detect) | `npm install omikit-plugin@latest` |
37
+ | 3.3.x | 0.60 – 0.73 | Old Architecture only | `npm install omikit-plugin@3.3.29` |
23
38
 
24
- ```ruby
25
- npm install omikit-plugin@latest
26
- ```
39
+ **v4.0.x highlights:**
40
+ - **TurboModules (JSI)** — 4-10x faster native method calls via direct C++ bridge
41
+ - **100% backward compatible** — auto-detects architecture at runtime
42
+ - **Zero breaking changes** from v3.x for RN 0.74+
43
+ - **Bridgeless mode** support for full New Architecture (iOS & Android)
27
44
 
28
- Install via yarn:
29
- ```ruby
30
- yarn add omikit-plugin --latest
31
- ```
45
+ ### Native SDK Versions
32
46
 
33
- #### 🛠️ Step 1: Config native file
47
+ | Platform | SDK | Version |
48
+ |----------|-----|---------|
49
+ | Android | OMIKIT | 2.6.4 |
50
+ | iOS | OmiKit | 1.10.34 |
34
51
 
35
- ##### 🚀 Android:
36
- 📌 **Config gradle file**
37
- - Add these settings in `build.gradle`:
52
+ ---
38
53
 
39
- ```gradle
40
- jcenter() // This func will replace soon
41
- maven {
42
- url "https://maven.pkg.github.com/omicall/OMICall-SDK"
43
- credentials {
44
- username = project.findProperty("OMI_USER") ?: "" // Please connect with developer OMI for get information
45
- password = project.findProperty("OMI_TOKEN") ?: ""
46
- }
47
- authentication {
48
- basic(BasicAuthentication)
49
- }
50
- }
54
+ ## Installation
51
55
 
56
+ ```bash
57
+ npm install omikit-plugin
58
+ # or
59
+ yarn add omikit-plugin
52
60
  ```
53
61
 
62
+ ### iOS
54
63
 
55
- ```kotlin
56
- // gradle.properties
57
- OMI_USER=omicall
58
- OMI_TOKEN=${OMI_TOKEN} // connect with dev off OMI for get token
64
+ ```bash
65
+ cd ios && pod install
59
66
  ```
60
67
 
61
- ```kotlin
62
- // in dependencies
63
- classpath 'com.google.gms:google-services:4.3.13'
64
- // You can choose the version of google-services to suit your project
65
- ```
68
+ ### Android
66
69
 
67
- ```kotlin
68
- // under buildscript
69
- allprojects {
70
- repositories {
71
- maven {
72
- // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
73
- url("$rootDir/../node_modules/react-native/android")
74
- }
75
- maven {
76
- // Android JSC is installed from npm
77
- url("$rootDir/../node_modules/jsc-android/dist")
78
- }
79
- mavenCentral {
80
- // We don't want to fetch react-native from Maven Central as there are
81
- // older versions over there.
82
- content {
83
- excludeGroup "com.facebook.react"
84
- }
85
- }
86
- google()
87
- maven { url 'https://www.jitpack.io' }
88
- maven {
89
- url "https://maven.pkg.github.com/omicall/OMICall-SDK"
90
- credentials {
91
- username = project.findProperty("OMI_USER") ?: ""
92
- password = project.findProperty("OMI_TOKEN") ?: ""
93
- }
94
- authentication {
95
- basic(BasicAuthentication)
96
- }
97
- }
98
- }
99
- }
100
- ```
70
+ No extra steps — permissions are declared in the module's `AndroidManifest.xml`.
101
71
 
102
- You can refer <a href="https://github.com/VIHATTeam/OMICALL-React-Native-SDK/blob/main/example/android/build.gradle">android/build.gradle</a> to know more informations.
72
+ ---
103
73
 
104
- - Add these settings in `app/build.gradle`:
105
-
106
- ```kotlin
107
- apply plugin: 'com.android.application'
108
- apply plugin: 'com.google.gms.google-services'
109
- ```
110
-
111
- You can refer <a href="https://github.com/VIHATTeam/OMICALL-React-Native-SDK/blob/main/example/android/app/build.gradle">android/app/build.gradle</a> to know more informations.
112
-
113
- <br>
114
-
115
- 📌 **Config AndroidManifest.xml file**
74
+ ## Android Setup
116
75
 
76
+ ### 1. Permissions
117
77
 
78
+ Add to `android/app/src/main/AndroidManifest.xml`:
118
79
 
119
80
  ```xml
120
- <manifest
121
- xmlns:tools="http://schemas.android.com/tools">
122
- // ... your config
123
- <uses-feature android:name="android.hardware.telephony" android:required="false" />
124
- <uses-permission android:name="android.permission.INTERNET" />
125
- <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
126
- <uses-permission android:name="android.permission.WAKE_LOCK" />
127
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
128
-
129
- <!-- 🔥 Android 15+ (SDK 35+) Required Permissions -->
130
- <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
131
- <uses-permission android:name="android.permission.RECORD_AUDIO"/>
132
- <uses-permission android:name="android.permission.CALL_PHONE"/>
133
- <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
134
- <uses-permission android:name="android.permission.USE_SIP"/>
135
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
136
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/>
137
- <uses-permission android:name="android.permission.CAMERA"/> <!-- For video calls -->
138
-
139
- // ... your config
140
-
141
- <application
142
- android:name=".MainApplication"
143
- android:alwaysRetainTaskState="true"
144
- android:largeHeap="true"
145
- android:exported="true"
146
- android:supportsRtl="true"
147
- android:allowBackup="false"
148
- android:enableOnBackInvokedCallback="true"
149
- // ... your config
150
- >
151
- <activity
152
- android:name=".MainActivity"
153
- android:windowSoftInputMode="adjustResize"
154
- android:showOnLockScreen="true"
155
- android:launchMode="singleTask"
156
- android:largeHeap="true"
157
- android:alwaysRetainTaskState="true"
158
- android:supportsPictureInPicture="false"
159
- android:showWhenLocked="true"
160
- android:turnScreenOn="true"
161
- android:exported="true"
162
- // ... your config
163
- >
164
- // ... your config
165
- <intent-filter>
166
- <action android:name="android.intent.action.MAIN" />
167
- <category android:name="android.intent.category.LAUNCHER" />
168
- </intent-filter>
169
- <intent-filter>
170
- <action android:name="android.intent.action.CALL" />
171
- <category android:name="android.intent.category.DEFAULT" />
172
- <data
173
- android:host="incoming_call"
174
- android:scheme="omisdk" />
175
- </intent-filter>
176
- // ... your config
177
- </activity>
178
- // ... your config
179
- <receiver
180
- android:name="vn.vihat.omicall.omisdk.receiver.FirebaseMessageReceiver"
181
- android:exported="true"
182
- android:enabled="true"
183
- tools:replace="android:exported"
184
- android:permission="com.google.android.c2dm.permission.SEND">
185
- <intent-filter>
186
- <action android:name="com.google.android.c2dm.intent.RECEIVE" />
187
- </intent-filter>
188
- </receiver>
189
- <service
190
- android:name="vn.vihat.omicall.omisdk.service.NotificationService"
191
- android:enabled="true"
192
- android:exported="false">
193
- </service>
194
- // ... your config
195
- </application>
196
- </manifest>
81
+ <!-- Required for all calls -->
82
+ <uses-permission android:name="android.permission.INTERNET" />
83
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
84
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
85
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
86
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
87
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
88
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
89
+ <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
90
+
91
+ <!-- Only required for video calls -->
92
+ <uses-permission android:name="android.permission.CAMERA" />
197
93
  ```
198
94
 
199
- <br>
200
-
201
- 📌 **Config MainActivity file**
202
- ### For React Native < 0.74
95
+ > **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:
96
+ >
97
+ > ```xml
98
+ > <!-- Remove camera foreground service if NOT using video call -->
99
+ > <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"
100
+ > tools:node="remove" />
101
+ > ```
102
+ >
103
+ > Make sure to add the `tools` namespace to your manifest tag: `xmlns:tools="http://schemas.android.com/tools"`
203
104
 
204
- ```java
205
- public class MainActivity extends ReactActivity {
206
- // your config ...
105
+ ### 2. Firebase Cloud Messaging (FCM)
207
106
 
107
+ Add your `google-services.json` to `android/app/`.
208
108
 
209
- @Override
210
- protected void onCreate(Bundle savedInstanceState) {
211
- super.onCreate(savedInstanceState);
212
- reactApplicationContext = new ReactApplicationContext(this);
213
- }
214
-
215
- @Override
216
- public void onNewIntent(Intent intent) {
217
- super.onNewIntent(intent);
218
- if (intent != null) {
219
- OmikitPluginModule.Companion.onGetIntentFromNotification(reactApplicationContext, intent, this);
220
- }
221
- }
109
+ In `android/app/build.gradle`:
222
110
 
223
- @Override
224
- protected void onResume() {
225
- super.onResume();
226
- OmikitPluginModule.Companion.onResume(this);
227
- Intent intent = getIntent();
228
- if (intent != null) {
229
- OmikitPluginModule.Companion.onGetIntentFromNotification(reactApplicationContext, intent, this);
230
- }
231
- // your config ...
232
- }
233
- }
111
+ ```groovy
112
+ apply plugin: 'com.google.gms.google-services'
234
113
  ```
235
114
 
236
- ### For React Native > 0.74
237
-
238
- ```kotlin
239
- class MainActivity : ReactActivity() {
240
- // your config ....
241
- private var reactApplicationContext: ReactApplicationContext? = null
242
- override fun onCreate(savedInstanceState: Bundle?) {
243
- super.onCreate(savedInstanceState)
244
-
245
- intent?.let { intentData ->
246
- try {
247
- OmikitPluginModule.Companion.handlePickupIntentEarly(this, intentData)
248
- } catch (e: Exception) {
249
- Log.e("MainActivity", "⚠️ PICKUP-FIX: Error handling early intent: ${e.message}")
250
- }
251
- }
252
-
253
- val reactInstanceManager: ReactInstanceManager = reactNativeHost.reactInstanceManager
254
- val currentContext = reactInstanceManager.currentReactContext
255
- if (currentContext != null && currentContext is ReactApplicationContext) {
256
- reactApplicationContext = currentContext
257
- Log.d("MainActivity", "ReactApplicationContext is available.")
258
- } else {
259
- Log.d("MainActivity", "ReactApplicationContext Not ready yet, will listen to the event.")
260
- }
115
+ ### 3. Maven Repository
261
116
 
262
- reactInstanceManager.addReactInstanceEventListener(object : ReactInstanceManager.ReactInstanceEventListener {
263
- override fun onReactContextInitialized(reactContext: com.facebook.react.bridge.ReactContext) {
264
- if (reactContext is ReactApplicationContext) {
265
- reactApplicationContext = reactContext
266
- Log.d("MainActivity", "ReactApplicationContext đã được khởi tạo.")
267
- }
268
- }
269
- })
270
- }
117
+ **Option A — `settings.gradle.kts` (recommended for new projects)**
271
118
 
272
- override fun onNewIntent(intent: Intent?) {
273
- super.onNewIntent(intent)
274
- intent?.let { newIntent ->
275
- Log.d("MainActivity", "🚀 PICKUP-FIX: New intent received (warm start)")
276
- // IMPORTANT: Update the activity's intent to the new one
277
- setIntent(newIntent)
278
- try {
279
- // Try to handle immediately if React context is ready
280
- reactApplicationContext?.let {
281
- OmikitPluginModule.Companion.onGetIntentFromNotification(it, newIntent, this)
282
- } ?: run {
283
- OmikitPluginModule.Companion.handlePickupIntentEarly(this, newIntent)
284
- }
285
- } catch (e: Exception) {
286
- Log.e("MainActivity", "❌ PICKUP-FIX: Error in onNewIntent: ${e.message}")
119
+ ```kotlin
120
+ // settings.gradle.kts
121
+ dependencyResolutionManagement {
122
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
123
+ repositories {
124
+ google()
125
+ mavenCentral()
126
+ maven { url = uri("https://jitpack.io") }
127
+ maven { url = uri("https://repo.omicall.com/maven") }
128
+ maven {
129
+ url = uri("https://maven.pkg.github.com/omicall/OMICall-SDK")
130
+ credentials {
131
+ username = providers.gradleProperty("OMI_USER").getOrElse("")
132
+ password = providers.gradleProperty("OMI_TOKEN").getOrElse("")
287
133
  }
288
- } ?: Log.e("MainActivity", "Intent in onNewIntent is null.")
289
- }
290
-
291
- override fun onResume() {
292
- super.onResume()
293
- reactApplicationContext?.let { context ->
294
- OmikitPluginModule.Companion.onResume(this)
295
- // Handle intent if exists (already updated by onNewIntent or from onCreate)
296
- intent?.let { intentData ->
297
- OmikitPluginModule.Companion.onGetIntentFromNotification(context, intentData, this)
134
+ authentication {
135
+ create<BasicAuthentication>("basic")
298
136
  }
299
- } ?: Log.e("MainActivity", "ReactApplicationContext has not been initialized in onResume.")
137
+ }
300
138
  }
301
-
302
- // your config ....
303
139
  }
304
140
  ```
305
141
 
306
- ```kotlin
307
- import com.google.firebase.FirebaseApp;
308
-
309
- // This is important because we push incoming calls via Firebase.
310
- class MainApplication : Application() {
311
- override fun onCreate() {
312
- super.onCreate()
313
- if (FirebaseApp.getApps(this).isEmpty()) {
314
- FirebaseApp.initializeApp(this)
142
+ **Option B — `build.gradle` (Groovy / legacy projects)**
143
+
144
+ ```groovy
145
+ // android/build.gradle (project level)
146
+ allprojects {
147
+ repositories {
148
+ google()
149
+ mavenCentral()
150
+ maven { url 'https://jitpack.io' }
151
+ maven { url 'https://repo.omicall.com/maven' }
152
+ maven {
153
+ url "https://maven.pkg.github.com/omicall/OMICall-SDK"
154
+ credentials {
155
+ username = project.findProperty("OMI_USER") ?: ""
156
+ password = project.findProperty("OMI_TOKEN") ?: ""
157
+ }
158
+ authentication {
159
+ basic(BasicAuthentication)
160
+ }
315
161
  }
316
162
  }
317
163
  }
318
-
319
164
  ```
320
165
 
321
- - Setup remote push notification: Only support Firebase for remote push notification.
166
+ Then add your credentials to `~/.gradle/gradle.properties` (or project-level `gradle.properties`):
322
167
 
323
- - ✅ Add `google-service.json` in `android/app` (For more information, you can refer <a href="https://rnfirebase.io/app/usage">Core/App</a>)
324
- - ✅ Add Firebase Messaging to receive `fcm_token` (You can refer <a href="https://rnfirebase.io/messaging/usage">Cloud Messaging</a> to setup notification for React native)
325
-
326
- - ✅ For more setting information, please refer <a href="https://api.omicall.com/web-sdk/mobile-sdk/android-sdk/cau-hinh-push-notification">Config Push for Android</a>
327
-
328
- <br>
329
-
330
- *Now let's continue configuring iOS, let's go 🚀*
331
-
332
- ##### 🚀 Config for IOS
333
-
334
- ##### 📌 iOS(Object-C):
335
-
336
- - ✅ Assets: Add `call_image` into assets folder to update callkit image. We only support png style. *(This will help show your application icon on iOS CallKit when a call comes in)*
337
-
338
- - ✅ Add variables in **Appdelegate.h** for **Old Architecture**:
168
+ ```properties
169
+ OMI_USER=omi_github_username
170
+ OMI_TOKEN=omi_github_access_token
171
+ ```
339
172
 
340
- ```objc
341
- #import <UIKit/UIKit.h>
342
- #import <UserNotifications/UserNotifications.h>
343
- // #import <OmiKit/OmiKit-umbrella.h>
344
- #import <OmiKit/OmiKit.h>
345
- #import <OmiKit/Constants.h>
173
+ > **Note:** Contact the OMICall development team to get `OMI_USER` and `OMI_TOKEN` credentials.
346
174
 
347
- @interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>
175
+ ### 4. New Architecture (Optional)
348
176
 
349
- @property (nonatomic, strong) UIWindow *window;
350
- @property (nonatomic, strong) PushKitManager *pushkitManager;
351
- @property (nonatomic, strong) CallKitProviderDelegate * provider;
352
- @property (nonatomic, strong) PKPushRegistry * voipRegistry;
177
+ To enable New Architecture on Android, in `android/gradle.properties`:
353
178
 
354
- @end
179
+ ```properties
180
+ newArchEnabled=true
355
181
  ```
356
182
 
357
- - ✅ Add variables in **Appdelegate.h** for **New Architecture**:
358
-
359
- ```objc
360
- #import <UIKit/UIKit.h>
361
- #import <UserNotifications/UserNotifications.h>
362
- // #import <OmiKit/OmiKit-umbrella.h>
363
- #import <OmiKit/OmiKit.h>
364
- #import <OmiKit/Constants.h>
183
+ ---
365
184
 
366
- @interface AppDelegate : NSObject <UIApplicationDelegate, UNUserNotificationCenterDelegate, RCTBridgeDelegate>
185
+ ## iOS Setup
367
186
 
368
- @property (nonatomic, strong) UIWindow *window;
369
- @property (nonatomic, strong) PushKitManager *pushkitManager;
370
- @property (nonatomic, strong) CallKitProviderDelegate * provider;
371
- @property (nonatomic, strong) PKPushRegistry * voipRegistry;
187
+ ### 1. Info.plist
372
188
 
373
- @end
189
+ Add to your `Info.plist`:
374
190
 
191
+ ```xml
192
+ <key>NSMicrophoneUsageDescription</key>
193
+ <string>Required for VoIP calls</string>
194
+ <key>NSCameraUsageDescription</key>
195
+ <string>Required for video calls</string>
375
196
  ```
376
197
 
377
- - Update AppDelegate.m:
378
-
379
- ```objc
380
- #import <OmiKit/OmiKit.h>
198
+ ### 2. Background Modes
381
199
 
200
+ In Xcode, enable the following Background Modes:
382
201
 
383
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
384
- {
202
+ - [x] Voice over IP
203
+ - [x] Remote notifications
204
+ - [x] Background fetch
385
205
 
386
- // ----- Start OmiKit Config ------
387
- [OmiClient setEnviroment:KEY_OMI_APP_ENVIROMENT_SANDBOX userNameKey:@"full_name" maxCall:2 callKitImage:@"call_image" typePushVoip:TYPE_PUSH_CALLKIT_DEFAULT];
388
- _provider = [[CallKitProviderDelegate alloc] initWithCallManager: [OMISIPLib sharedInstance].callManager];
389
- _voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
390
- _pushkitManager = [[PushKitManager alloc] initWithVoipRegistry:_voipRegistry];
391
- if (@available(iOS 10.0, *)) {
392
- [UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
393
- }
394
- // ----- End OmiKit Config ------
206
+ ### 3. Push Notifications
395
207
 
396
- return YES;
208
+ Enable **Push Notifications** capability in Xcode for VoIP push (PushKit).
397
209
 
398
- }
210
+ ### 4. AppDelegate Setup
399
211
 
212
+ In your `AppDelegate.mm` (or `.m`):
400
213
 
401
- //Called when a notification is delivered to a foreground app.
402
- -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
403
- {
404
- NSLog(@"User Info : %@",notification.request.content.userInfo);
405
- completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
406
- }
407
-
408
- // This function is used to send an event back into the app when the user presses on a missed call notification
409
- - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
410
- NSDictionary *userInfo = response.notification.request.content.userInfo;
411
- if (userInfo && [userInfo valueForKey:@"omisdkCallerNumber"]) {
412
- NSLog(@"User Info : %@",userInfo);
413
- [OmikitNotification didRecieve:userInfo];
414
- }
415
- completionHandler();
416
- }
417
-
418
- // This function will terminate all ongoing calls when the user kills the app
419
- - (void)applicationWillTerminate:(UIApplication *)application {
420
- @try {
421
- [OmiClient OMICloseCall];
422
- }
423
- @catch (NSException *exception) {
424
-
425
- }
426
- }
214
+ ```objc
215
+ #import <OmiKit/OmiKit-umbrella.h>
216
+ #import <OmiKit/Constants.h>
427
217
  ```
428
218
 
429
- - 📝 Tips: Error Use of undeclared identifier 'OmikitNotification' at file `AppDelegate.m`, please import this line below
219
+ ### 5. New Architecture (Optional)
430
220
 
431
- ```objc
432
- #if __has_include("OmikitNotification.h")
433
- #import "OmikitNotification.h"
434
- #elif __has_include(<OmikitPlugin/OmikitPlugin-Swift.h>)
435
- #import <OmikitPlugin/OmikitPlugin-Swift.h>
436
- #else
437
- #import <omikit_plugin/OmikitNotification.h>
438
- #endif
221
+ In your `Podfile`:
439
222
 
440
- ```
441
- - Add these lines into `Info.plist`:
442
-
443
- ```xml
444
- <key>NSMicrophoneUsageDescription</key>
445
- <string>Need microphone access for make Call</string>
446
- //If you implement video call
447
- <key>NSCameraUsageDescription</key>
448
- <string>Need camera access for video call functions</string>
223
+ ```ruby
224
+ ENV['RN_NEW_ARCH_ENABLED'] = '1'
449
225
  ```
450
226
 
451
- - 💡 Save token for `OmiClient`: if You added `Cloud Messaging` in your project so you don't need add these lines.
227
+ For **full bridgeless mode**, in `AppDelegate.mm`:
452
228
 
453
- ```swift
454
- - (void)application:(UIApplication*)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)devToken
229
+ ```objc
230
+ - (BOOL)bridgelessEnabled
455
231
  {
456
- // parse token bytes to string
457
- // const char *data = [devToken bytes];
458
- const unsigned char *data = (const unsigned char *)[devToken bytes];
459
- NSMutableString *token = [NSMutableString string];
460
- for (NSUInteger i = 0; i < [devToken length]; i++)
461
- {
462
- [token appendFormat:@"%02.2hhX", data[i]];
463
- }
464
-
465
- // print the token in the console.
466
- NSLog(@"Push Notification Token: %@", [token copy]);
467
- [OmiClient setUserPushNotificationToken:[token copy]];
232
+ return YES;
468
233
  }
469
-
470
234
  ```
471
235
 
472
- **✨ Only use under lines when added `Cloud Messaging` plugin in your project**
236
+ Then run `cd ios && pod install`.
473
237
 
474
- - ✅ Setup push notification: We only support Firebase for push notification.
475
- - ✅ Add `google-service.json` in `android/app` (For more information, you can refer <a href="https://rnfirebase.io/app/usage">Core/App</a>)
476
- - ✅ Add Firebase Messaging to receive `fcm_token` (You can refer <a href="https://pub.dev/packages/firebase_messaging">Cloud Messaging</a> to setup notification for React Native)
477
- - ✅ For more setting information, please refer <a href="https://api.omicall.com/web-sdk/mobile-sdk/ios-sdk/cau-hinh-push-notification">Config Push for iOS</a>
238
+ ---
478
239
 
479
- **✨Important release note**
240
+ ## Architecture Overview
480
241
 
481
242
  ```
482
- We support 2 environments. So you need set correct key in Appdelegate.
483
- - KEY_OMI_APP_ENVIROMENT_SANDBOX support on debug mode
484
- - KEY_OMI_APP_ENVIROMENT_PRODUCTION support on release mode
485
- - Visit on web admin to select correct enviroment.
243
+ ┌─────────────────────────────────────────────────────────────┐
244
+ │ React Native App │
245
+ │ │
246
+ │ import { startCall, omiEmitter } from 'omikit-plugin' │
247
+ └──────────────────────────┬──────────────────────────────────┘
248
+
249
+ ┌────────────▼────────────┐
250
+ │ Architecture Bridge │
251
+ │ │
252
+ │ TurboModule? ──► JSI │ (New Arch: direct C++ calls)
253
+ │ │ │
254
+ │ └──► NativeModule │ (Old Arch: JSON bridge)
255
+ └────────────┬────────────┘
256
+
257
+ ┌────────────────┼────────────────┐
258
+ │ │
259
+ ┌──────▼──────┐ ┌───────▼──────┐
260
+ │ Android │ │ iOS │
261
+ │ │ │ │
262
+ │ OmikitPlugin│ │ OmikitPlugin │
263
+ │ Module.kt │ │ .swift │
264
+ │ │ │ │ │ │
265
+ │ ▼ │ │ ▼ │
266
+ │ OMIKIT SDK │ │ OmiKit SDK │
267
+ │ (v2.6.4) │ │ (v1.10.34) │
268
+ │ │ │ │ │ │
269
+ │ ▼ │ │ ▼ │
270
+ │ SIP Stack │ │ SIP Stack │
271
+ │ (OMSIP) │ │ (OMSIP) │
272
+ └─────────────┘ └──────────────┘
486
273
  ```
487
274
 
488
- *📝Note: At Tab Build Setting off Target Project, you need set: **_Enable Modules (C and Objective C)_** : YES*
275
+ ---
276
+
277
+ ## Quick Start
278
+
279
+ ```typescript
280
+ import {
281
+ startServices,
282
+ initCallWithUserPassword,
283
+ startCall,
284
+ joinCall,
285
+ endCall,
286
+ omiEmitter,
287
+ OmiCallEvent,
288
+ OmiCallState,
289
+ } from 'omikit-plugin';
290
+
291
+ // Step 1: Start SDK services
292
+ // ⚠️ Call ONCE on app launch (e.g., in App.tsx / index.js / useEffect in root component)
293
+ // Do NOT call this multiple times — it initializes native audio and event listeners.
294
+ await startServices();
295
+
296
+ // Step 2: Login with SIP credentials
297
+ const loginResult = await initCallWithUserPassword({
298
+ userName: 'sip_user',
299
+ password: 'sip_password',
300
+ realm: 'your_realm',
301
+ host: '', // SIP proxy, defaults to vh.omicrm.com
302
+ isVideo: false,
303
+ fcmToken: 'your_fcm_token',
304
+ projectId: 'your_project_id', // firebase project id
305
+ });
489
306
 
490
- #### Currently, OMICALL does not support React Native new architect.
307
+ // Step 3: Listen to call events
308
+ const subscription = omiEmitter.addListener(
309
+ OmiCallEvent.onCallStateChanged,
310
+ (data) => {
311
+ console.log('Call state:', data.status);
312
+
313
+ switch (data.status) {
314
+ case OmiCallState.incoming:
315
+ // Show incoming call UI
316
+ // data.callerNumber, data.isVideo
317
+ break;
318
+ case OmiCallState.confirmed:
319
+ // Call connected — show active call UI
320
+ break;
321
+ case OmiCallState.disconnected:
322
+ // Call ended
323
+ // data.codeEndCall — SIP end code
324
+ break;
325
+ }
326
+ }
327
+ );
491
328
 
492
- 📌 Config turn Off for new architect
329
+ // Step 4: Make outgoing call
330
+ const result = await startCall({
331
+ phoneNumber: '0901234567',
332
+ isVideo: false,
333
+ });
493
334
 
494
- <br>
335
+ if (result.status === 8) {
336
+ console.log('Call started, ID:', result._id);
337
+ }
495
338
 
496
- For iOS
497
- ```Ruby
498
- use_react_native!(
499
- :path => config[:reactNativePath],
500
- :new_arch_enabled => false, // <=== add this line
501
- ... your config
502
- )
503
- ```
339
+ // Step 5: Accept incoming call
340
+ await joinCall();
504
341
 
505
- For Android
342
+ // Step 6: End call
343
+ await endCall();
506
344
 
507
- - Open file **_android/gradle.properties_** and add line below:
508
- ```kotlin
509
- # Turn off New Architecture
510
- newArchEnabled=false
345
+ // Cleanup on unmount
346
+ subscription.remove();
511
347
  ```
512
- #### 📌 iOS(Swift):
513
348
 
514
- 📝 Notes: The configurations are similar to those for object C above, with only a slight difference in the syntax of the functions
349
+ ---
515
350
 
516
- - ✅ Add variables in Appdelegate.swift:
351
+ ## Authentication
517
352
 
518
- ```swift
519
- import OmiKit
520
- import PushKit
521
- import NotificationCenter
353
+ Two authentication methods are available. Each supports two login modes depending on who is using the app:
522
354
 
523
- var pushkitManager: PushKitManager?
524
- var provider: CallKitProviderDelegate?
525
- var voipRegistry: PKPushRegistry?
526
- ```
355
+ | Mode | `isSkipDevices` | Use Case | Capabilities |
356
+ |------|-----------------|----------|--------------|
357
+ | **Agent** (default) | `false` | Employees / call center agents | Can make outbound calls to any telecom number |
358
+ | **Customer** | `true` | End customers | Can only call the business hotline (no outbound to external numbers) |
527
359
 
528
- - Add these lines into `didFinishLaunchingWithOptions`:
529
-
530
- ```swift
531
- OmiClient.setEnviroment(KEY_OMI_APP_ENVIROMENT_SANDBOX, userNameKey: "extension", maxCall: 1, callKitImage: "call_image")
532
- provider = CallKitProviderDelegate.init(callManager: OMISIPLib.sharedInstance().callManager)
533
- voipRegistry = PKPushRegistry.init(queue: .main)
534
- pushkitManager = PushKitManager.init(voipRegistry: voipRegistry)
535
- ```
360
+ ### Option 1: Username + Password (SIP Credentials)
536
361
 
537
- - ✅ Add these lines into `Info.plist`:
538
-
539
- ```xml
540
- <key>NSCameraUsageDescription</key>
541
- <string>Need camera access for video call functions</string>
542
- <key>NSMicrophoneUsageDescription</key>
543
- <string>Need microphone access for make Call</string>
362
+ ```typescript
363
+ await initCallWithUserPassword({
364
+ userName: string, // SIP username
365
+ password: string, // SIP password
366
+ realm: string, // SIP realm/domain
367
+ host?: string, // SIP proxy server (optional)
368
+ isVideo: boolean, // Enable video capability
369
+ fcmToken: string, // Firebase token for push notifications
370
+ projectId: string, // Firebase project ID
371
+ isSkipDevices?: boolean, // true = Customer mode, false = Agent mode (default)
372
+ });
544
373
  ```
545
374
 
546
- - Save token for `OmiClient`: if you added `firebase_messaging` in your project so you don't need add these lines.
375
+ #### Agent Login (default)
547
376
 
548
- ```swift
549
- func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
550
- let deviceTokenString = deviceToken.hexString
551
- OmiClient.setUserPushNotificationToken(deviceTokenString)
552
- }
377
+ For employees / call center agents who can make outbound calls to any phone number:
553
378
 
554
- extension Data {
555
- var hexString: String {
556
- let hexString = map { String(format: "%02.2hhx", $0) }.joined()
557
- return hexString
558
- }
559
- }
379
+ ```typescript
380
+ await initCallWithUserPassword({
381
+ userName: '100',
382
+ password: 'sip_password',
383
+ realm: 'your_realm',
384
+ host: '',
385
+ isVideo: false,
386
+ fcmToken: fcmToken,
387
+ // isSkipDevices defaults to false — Agent mode
388
+ });
560
389
  ```
561
390
 
562
- **✨ Only use under lines when added `Cloud Messaging` plugin in your project**
391
+ #### Customer Login
563
392
 
564
- - Setup push notification: We only support Firebase for push notification.
565
- - ✅ Add `google-service.json` in `android/app` (For more information, you can refer <a href="https://rnfirebase.io/app/usage">Core/App</a>)
566
- - ✅ Add Firebase Messaging to receive `fcm_token` (You can refer <a href="https://pub.dev/packages/firebase_messaging">Cloud Messaging</a> to setup notification for React Native)
567
- - ✅ For more setting information, please refer <a href="https://api.omicall.com/web-sdk/mobile-sdk/ios-sdk/cau-hinh-push-notification">Config Push for iOS</a>
568
- -
569
-
570
- **❌ Important release note**
393
+ For end customers who can only call the business hotline — no outbound dialing to external telecom numbers, no assigned phone number:
571
394
 
395
+ ```typescript
396
+ await initCallWithUserPassword({
397
+ userName: '200',
398
+ password: 'sip_password',
399
+ realm: 'your_realm',
400
+ host: '',
401
+ isVideo: false,
402
+ fcmToken: fcmToken,
403
+ isSkipDevices: true, // Customer mode — skip device registration
404
+ });
572
405
  ```
573
- We support 2 environments. So you need set correct key in Appdelegate.
574
- - KEY_OMI_APP_ENVIROMENT_SANDBOX support on debug mode
575
- - KEY_OMI_APP_ENVIROMENT_PRODUCTION support on release mode
576
- - Visit on web admin to select correct enviroment.
406
+
407
+ ### Option 2: API Key
408
+
409
+ ```typescript
410
+ await initCallWithApiKey({
411
+ fullName: string, // Display name
412
+ usrUuid: string, // User UUID from OMICALL
413
+ apiKey: string, // API key from OMICALL dashboard
414
+ isVideo: boolean, // Enable video capability
415
+ phone: string, // Phone number
416
+ fcmToken: string, // Firebase token for push notifications
417
+ projectId?: string, // OMICALL project ID (optional)
418
+ });
577
419
  ```
578
420
 
579
- ## 🛠️ Step 2: Integrate into React Native code
421
+ ### Option 3: App-to-App API (v4.0+)
580
422
 
581
- ### 🚀 Request permission
423
+ 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`.
582
424
 
583
- **📌 We need you request permission about call before make call:**
425
+ > For full API documentation (endpoints, request/response formats), see the [API Integration Guide](./docs/api-integration-guide.md).
584
426
 
585
- - ✅ You can use <a href="https://github.com/zoontek/react-native-permissions">react-native-permissions</a> to do this
427
+ **Quick flow:**
586
428
 
587
429
  ```
588
- -Android:
589
- + PERMISSIONS.ANDROID.RECORD_AUDIO
590
- + PERMISSIONS.ANDROID.POST_NOTIFICATIONS
591
- + PERMISSIONS.ANDROID.CALL_PHONE
592
- + PERMISSIONS.ANDROID.CAMERA; (if you want to make Video calls)
593
-
594
- -IOS:
595
- + PERMISSIONS.IOS.MICROPHONE;
596
- + PERMISSIONS.IOS.CAMERA; (if you want to make Video calls)
430
+ Your Backend OMICALL API Mobile App (SDK)
431
+ │ │ │
432
+ │ 1. POST .../init │ │
433
+ ├─────────────────────────────►│ │
434
+ │ {domain, extension, │ │
435
+ │ password, proxy} │ │
436
+ │◄─────────────────────────────┤ │
437
+ │ │ │
438
+ │ 2. Return credentials │ │
439
+ ├──────────────────────────────────────────────────────────►│
440
+ │ │ 3. startServices() │
441
+ │ │ 4. initCallWithUserPassword
442
+ │ │ (isSkipDevices: true) │
443
+ │ │ │
444
+ ```
597
445
 
446
+ ```typescript
447
+ // After getting credentials from your backend:
448
+ await startServices();
449
+
450
+ await initCallWithUserPassword({
451
+ userName: credentials.extension, // from API response
452
+ password: credentials.password, // from API response
453
+ realm: credentials.domain, // from API response
454
+ host: credentials.outboundProxy, // from API response
455
+ isVideo: false,
456
+ fcmToken: 'your-fcm-token',
457
+ isSkipDevices: true, // Required for App-to-App
458
+ });
598
459
  ```
599
460
 
600
- ### 🔥 **Android Permission Management**
461
+ > **Important:**
462
+ > - Call the OMICALL API from your **backend server** only — never expose the Bearer token in client-side code.
463
+ > - You **must** call the [Logout API](./docs/api-integration-guide.md#5-logout) before switching users. Otherwise, both devices using the same SIP extension will receive incoming calls simultaneously.
464
+ > - Use getter functions (`getProjectId()`, `getAppId()`, `getDeviceId()`, `getFcmToken()`, `getVoipToken()`) to retrieve device params for the [Add Device](./docs/api-integration-guide.md#4-add-device) and Logout APIs.
601
465
 
602
- **📌 For Android (SDK), additional permissions are required:**
466
+ ---
603
467
 
604
- 🔥 Notes:
605
-
606
- • POST_NOTIFICATIONS and RECORD_AUDIO must be requested at runtime in your code.
607
-
608
- • FOREGROUND_SERVICE* permissions only need to be declared in the manifest; Android will enforce them automatically when you call startForegroundService().
468
+ ## Call Flows
609
469
 
610
- ```xml
611
- <!-- Runtime permissions -->
612
- <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
613
- <uses-permission android:name="android.permission.RECORD_AUDIO"/>
614
-
615
- <!-- Foreground service permissions (manifest only, no runtime request needed) -->
616
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
617
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
618
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/>
619
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" tools:node="remove" />
620
-
621
- <service
622
- android:name="net.gotev.sipservice.SipService"
623
- android:foregroundServiceType="phoneCall|microphone"
624
- tools:replace="android:foregroundServiceType"
625
- android:exported="false"
626
- />
470
+ ### Outgoing Call Flow
627
471
 
472
+ ```
473
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
474
+ │ JS App │ │ Native │ │ SIP/PBX │
475
+ └────┬─────┘ └────┬─────┘ └────┬─────┘
476
+ │ │ │
477
+ │ startCall() │ │
478
+ ├────────────────────►│ │
479
+ │ │ SIP INVITE │
480
+ │ ├────────────────────►│
481
+ │ │ │
482
+ │ calling (1) │ 180 Ringing │
483
+ │◄────────────────────┤◄────────────────────┤
484
+ │ │ │
485
+ │ early (3) │ 183 Progress │
486
+ │◄────────────────────┤◄────────────────────┤
487
+ │ │ │
488
+ │ connecting (4) │ 200 OK │
489
+ │◄────────────────────┤◄────────────────────┤
490
+ │ │ │
491
+ │ confirmed (5) │ ACK │
492
+ │◄────────────────────┤────────────────────►│
493
+ │ │ │
494
+ │ ══════ Active Call (RTP audio/video) ══════
495
+ │ │ │
496
+ │ endCall() │ │
497
+ ├────────────────────►│ BYE │
498
+ │ ├────────────────────►│
499
+ │ disconnected (6) │ 200 OK │
500
+ │◄────────────────────┤◄────────────────────┤
501
+ │ │ │
628
502
  ```
629
503
 
630
- - Set up <a href="https://rnfirebase.io/messaging/usage">Cloud Messaging</a> plugin:
504
+ ### Incoming Call App in Foreground
631
505
 
632
506
  ```
633
- //if you use only on Android. you only implement for Android.
634
- //because we use APNS to push notification on iOS so you don't need add Firebase for iOS.
635
- //But you can use firebase-messaging to get APNS token for iOS.
507
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
508
+ │ JS App │ │ Native │ │ SIP/PBX │
509
+ └────┬─────┘ └────┬─────┘ └────┬─────┘
510
+ │ │ │
511
+ │ │ SIP INVITE │
512
+ │ │◄────────────────────┤
513
+ │ │ 180 Ringing │
514
+ │ ├────────────────────►│
515
+ │ │ │
516
+ │ incoming (2) │ │
517
+ │◄────────────────────┤ (event emitted) │
518
+ │ │ │
519
+ │ ┌──────────────┐ │ │
520
+ │ │ Show Call UI │ │ │
521
+ │ │[Accept][Deny]│ │ │
522
+ │ └──────────────┘ │ │
523
+ │ │ │
524
+ │ joinCall() │ │
525
+ ├────────────────────►│ 200 OK │
526
+ │ ├────────────────────►│
527
+ │ confirmed (5) │ ACK │
528
+ │◄────────────────────┤◄────────────────────┤
529
+ │ │ │
530
+ │ ══════ Active Call (RTP audio/video) ══════
531
+ │ │ │
636
532
  ```
637
- #### 🚀 OMIKIT-Plugin functions 🚀
638
- <br>
639
-
640
- 📌 **startServices()**
641
533
 
642
- Description:
534
+ ### Incoming Call — App in Background / Killed
643
535
 
644
- The `startServices()` function is used to initialize necessary services in `omikit-plugin`.
645
- It should only be called once in the root file of your application.
646
-
647
- - Usage:
648
- ```javascript
649
- // Import startServices from omikit-plugin
650
- import { startServices } from 'omikit-plugin';
536
+ ```
537
+ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
538
+ │ JS App │ │ Native │ │Push Svc │ │ SIP/PBX │
539
+ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
540
+ │ │ │ │
541
+ │ │ │ Push Notify │
542
+ │ │ │◄──────────────┤
543
+ │ │ │ │
544
+
545
+ ┌───────────────── iOS (VoIP Push) ───────────────────┐
546
+ │ │ │ │ │ │
547
+ │ │ │ PushKit VoIP │ │ │
548
+ │ │ │◄──────────────┤ │ │
549
+ │ │ │ │ │ │
550
+ │ │ │ Show CallKit │ │ │
551
+ │ │ │ ┌──────────┐ │ │ │
552
+ │ │ │ │ System │ │ │ │
553
+ │ │ │ │ Call UI │ │ │ │
554
+ │ │ │ │[Slide ►] │ │ │ │
555
+ │ │ │ └──────────┘ │ │ │
556
+ │ │ │ │ │ │
557
+ │ │ App launched │ │ │ │
558
+ │ │◄──────────────┤ │ │ │
559
+ │ │ incoming (2) │ │ │ │
560
+ │ │◄──────────────┤ │ │ │
561
+ └─────────────────────────────────────────────────────┘
562
+
563
+ ┌───────────────── Android (FCM) ─────────────────────┐
564
+ │ │ │ │ │ │
565
+ │ │ │ FCM Message │ │ │
566
+ │ │ │◄──────────────┤ │ │
567
+ │ │ │ │ │ │
568
+ │ │ │ Start Foreground Service │ │
569
+ │ │ │ ┌──────────────────────┐ │ │
570
+ │ │ │ │ Full-screen Notif │ │ │
571
+ │ │ │ │ [Accept] [Decline] │ │ │
572
+ │ │ │ └──────────────────────┘ │ │
573
+ │ │ │ │ │ │
574
+ │ │ App launched │ │ │ │
575
+ │ │◄──────────────┤ │ │ │
576
+ │ │ incoming (2) │ │ │ │
577
+ │ │◄──────────────┤ │ │ │
578
+ └─────────────────────────────────────────────────────┘
579
+
580
+ │ │ │ │
581
+ │ joinCall() │ │ │
582
+ ├──────────────►│ 200 OK │ │
583
+ │ ├──────────────────────────────►│
584
+ │ confirmed (5)│ │ │
585
+ │◄──────────────┤ │ │
586
+ │ │ │ │
587
+ ```
651
588
 
652
- // Call startServices() to initialize the required services
653
- startServices();
654
- ```
655
- - 📝 Notes:<br>
656
- • Do not call this function multiple times; it should be called only once when the application starts. <br>
657
- • Ensure that `omikit-plugin` is installed before using this function.
589
+ ### Missed Call Flow
658
590
 
659
- *Add the following code to the root file of your application, such as `App.js` or `index.js`*
591
+ ```
592
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
593
+ │ JS App │ │ Native │ │ SIP/PBX │
594
+ └────┬─────┘ └────┬─────┘ └────┬─────┘
595
+ │ │ │
596
+ │ │ SIP INVITE │
597
+ │ │◄────────────────────┤
598
+ │ incoming (2) │ │
599
+ │◄────────────────────┤ │
600
+ │ │ │
601
+ │ (user ignores / timeout / caller hangs up)
602
+ │ │ │
603
+ │ │ CANCEL │
604
+ │ │◄────────────────────┤
605
+ │ disconnected (6) │ 200 OK │
606
+ │◄────────────────────┤────────────────────►│
607
+ │ │ │
608
+ │ │ Show Missed Call │
609
+ │ │ Notification │
610
+ │ │ │
611
+ │ (user taps notif) │ │
612
+ │ │ │
613
+ │ onClickMissedCall │ │
614
+ │◄────────────────────┤ │
615
+ │ │ │
616
+ ```
660
617
 
618
+ ### Call Transfer Flow
661
619
 
662
- 📌 **requestLoginPermissions()**
620
+ ```
621
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
622
+ │ JS App │ │ Native │ │ SIP/PBX │
623
+ └────┬─────┘ └────┬─────┘ └────┬─────┘
624
+ │ │ │
625
+ │ ══════ Active Call with Party A ══════
626
+ │ │ │
627
+ │ transferCall(B) │ │
628
+ ├────────────────────►│ SIP REFER → B │
629
+ │ ├────────────────────►│
630
+ │ │ │
631
+ │ │ 202 Accepted │
632
+ │ │◄────────────────────┤
633
+ │ │ │
634
+ │ disconnected (6) │ BYE (from A) │
635
+ │◄────────────────────┤◄────────────────────┤
636
+ │ │ │
637
+ │ ══════ Party A now talks to B ══════
638
+ │ │ │
639
+ ```
663
640
 
664
- `Note`: Starting from Android 13+, certain foreground services (such as microphone or phone call) require explicit user permission before they can be started.
665
- This means the user must grant these permissions before initiating a call or any service that relies on them.
641
+ ### Reject / Drop Call Flow
666
642
 
667
- ```TypeScript
668
- import {
669
- PERMISSIONS,
670
- request,
671
- check,
672
- RESULTS,
673
- requestMultiple
674
- } from 'react-native-permissions';
675
- import { Platform } from 'react-native';
643
+ ```
644
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
645
+ JS App │ │ Native │ │ SIP/PBX │
646
+ └────┬─────┘ └────┬─────┘ └────┬─────┘
647
+ │ │ │
648
+ incoming (2) │ SIP INVITE │
649
+ │◄────────────────────┤◄────────────────────┤
650
+ │ │ │
651
+ ┌── rejectCall() ───┐ │
652
+ │ Decline this │ 486 Busy Here │
653
+ │ device only ├─────────────────────────► │
654
+ └───────────────────┘ (other devices ring) │
655
+ │ │ │
656
+ ┌── dropCall() ─────┐ │
657
+ │ Decline + stop │ 603 Decline │
658
+ │ ALL devices ├─────────────────────────► │
659
+ └───────────────────┘ (PBX stops all ringing) │
660
+ │ │ │
661
+ ```
676
662
 
677
- export async function requestLoginPermissions(): Promise<boolean> {
678
- if (Platform.OS !== 'android') return true;
663
+ ---
664
+
665
+ ## API Reference
666
+
667
+ ### Service & Auth
668
+
669
+ | Function | Returns | Description |
670
+ |----------|---------|-------------|
671
+ | `startServices()` | `Promise<boolean>` | Initialize SDK. **Call once** on app launch (e.g., `App.tsx` or `index.js`). Do not call multiple times |
672
+ | `initCallWithUserPassword(data)` | `Promise<boolean>` | Login with SIP username/password |
673
+ | `initCallWithApiKey(data)` | `Promise<boolean>` | Login with API key |
674
+ | `logout()` | `Promise<boolean>` | Logout and unregister SIP |
675
+
676
+ ### Call Control
677
+
678
+ | Function | Returns | Description |
679
+ |----------|---------|-------------|
680
+ | `startCall({ phoneNumber, isVideo })` | `Promise<{ status, message, _id }>` | Initiate outgoing call |
681
+ | `startCallWithUuid({ usrUuid, isVideo })` | `Promise<boolean>` | Call by user UUID |
682
+ | `joinCall()` | `Promise<any>` | Accept incoming call |
683
+ | `endCall()` | `Promise<any>` | End active call (sends SIP BYE) |
684
+ | `rejectCall()` | `Promise<boolean>` | Reject on this device only (486) |
685
+ | `dropCall()` | `Promise<boolean>` | Reject + stop ringing on ALL devices (603) |
686
+ | `transferCall({ phoneNumber })` | `Promise<boolean>` | Blind transfer to another number |
687
+ | `getInitialCall()` | `Promise<any>` | Get pending call data on cold start |
688
+
689
+ ### Media Control
690
+
691
+ | Function | Returns | Description |
692
+ |----------|---------|-------------|
693
+ | `toggleMute()` | `Promise<boolean\|null>` | Toggle microphone mute |
694
+ | `toggleSpeaker()` | `Promise<boolean>` | Toggle speakerphone |
695
+ | `toggleHold()` | `Promise<void>` | Toggle call hold |
696
+ | `onHold({ holdStatus })` | `Promise<boolean>` | Set hold state explicitly |
697
+ | `sendDTMF({ character })` | `Promise<boolean>` | Send DTMF tone (0-9, *, #) |
698
+ | `getAudio()` | `Promise<any>` | List available audio devices |
699
+ | `setAudio({ portType })` | `Promise<void>` | Set audio output device |
700
+ | `getCurrentAudio()` | `Promise<any>` | Get current audio device |
701
+
702
+ ### Video Control
703
+
704
+ | Function | Returns | Description |
705
+ |----------|---------|-------------|
706
+ | `toggleOmiVideo()` | `Promise<boolean>` | Toggle video stream on/off |
707
+ | `switchOmiCamera()` | `Promise<boolean>` | Switch front/back camera |
708
+ | `registerVideoEvent()` | `Promise<boolean>` | Start receiving remote video frames |
709
+ | `removeVideoEvent()` | `Promise<boolean>` | Stop receiving remote video frames |
710
+
711
+ ### User & Info
712
+
713
+ | Function | Returns | Description |
714
+ |----------|---------|-------------|
715
+ | `getCurrentUser()` | `Promise<any>` | Get logged-in user details |
716
+ | `getGuestUser()` | `Promise<any>` | Get guest/remote user details |
717
+ | `getUserInfo(phone)` | `Promise<any>` | Look up user by phone number |
718
+
719
+ ### Getter Functions (v4.0.1+)
720
+
721
+ | Function | Returns | Description |
722
+ |----------|---------|-------------|
723
+ | `getProjectId()` | `Promise<string\|null>` | Current project ID |
724
+ | `getAppId()` | `Promise<string\|null>` | Current app ID |
725
+ | `getDeviceId()` | `Promise<string\|null>` | Current device ID |
726
+ | `getFcmToken()` | `Promise<string\|null>` | FCM push token |
727
+ | `getSipInfo()` | `Promise<string\|null>` | SIP info (`user@realm`) |
728
+ | `getVoipToken()` | `Promise<string\|null>` | VoIP token (iOS only) |
729
+
730
+ ### Notification Control
731
+
732
+ | Function | Returns | Description |
733
+ |----------|---------|-------------|
734
+ | `configPushNotification(data)` | `Promise<any>` | Configure push notification settings |
735
+ | `hideSystemNotificationSafely()` | `Promise<boolean>` | Hide notification without unregistering |
736
+ | `hideSystemNotificationOnly()` | `Promise<boolean>` | Hide notification only |
737
+ | `hideSystemNotificationAndUnregister(reason)` | `Promise<boolean>` | Hide + unregister with reason |
738
+
739
+ ---
740
+
741
+ ## Events
742
+
743
+ Use `omiEmitter` to listen for events emitted by the native SDK.
744
+
745
+ ```typescript
746
+ import { omiEmitter, OmiCallEvent } from 'omikit-plugin';
747
+ ```
679
748
 
680
- const permissions: string[] = [];
749
+ ### Event Reference
681
750
 
682
- // Android 13+ cần POST_NOTIFICATIONS
683
- if (Platform.Version >= 33) {
684
- permissions.push(PERMISSIONS.ANDROID.POST_NOTIFICATIONS);
685
- }
751
+ | Event | Payload | Description |
752
+ |-------|---------|-------------|
753
+ | `onCallStateChanged` | `{ status, callerNumber, isVideo, incoming, codeEndCall }` | Call lifecycle changes |
754
+ | `onMuted` | `boolean` | Microphone mute toggled |
755
+ | `onSpeaker` | `boolean` | Speaker toggled |
756
+ | `onHold` | `boolean` | Hold state changed |
757
+ | `onRemoteVideoReady` | — | Remote video stream is ready |
758
+ | `onClickMissedCall` | `{ callerNumber }` | User tapped missed call notification |
759
+ | `onSwitchboardAnswer` | `{ data }` | Switchboard answered |
760
+ | `onCallQuality` | `{ quality, stat }` | Call quality metrics (see [Quality & Diagnostics](#quality--diagnostics)) |
761
+ | `onAudioChange` | `{ data }` | Audio device changed |
762
+ | `onRequestPermissionAndroid` | `{ permissions }` | Permission request needed (Android only) |
686
763
 
687
- // Android 14+ và 15+ cần RECORD_AUDIO trước khi start foreground service
688
- permissions.push(PERMISSIONS.ANDROID.RECORD_AUDIO);
764
+ ### Usage Example
689
765
 
690
- const statuses = await requestMultiple(permissions);
766
+ ```typescript
767
+ import { omiEmitter, OmiCallEvent, OmiCallState } from 'omikit-plugin';
691
768
 
692
- // Check kết quả
693
- const allGranted = Object.values(statuses).every(
694
- status => status === RESULTS.GRANTED
695
- );
769
+ useEffect(() => {
770
+ const subscriptions = [
771
+ // Call state changes
772
+ omiEmitter.addListener(OmiCallEvent.onCallStateChanged, (data) => {
773
+ console.log('State:', data.status, 'Caller:', data.callerNumber);
696
774
 
697
- if (!allGranted) {
698
- console.warn('❌ Some required permissions were not granted');
699
- return false;
700
- }
775
+ if (data.status === OmiCallState.incoming) {
776
+ // Navigate to incoming call screen
777
+ }
778
+ if (data.status === OmiCallState.confirmed) {
779
+ // Call connected
780
+ }
781
+ if (data.status === OmiCallState.disconnected) {
782
+ // Call ended, check data.codeEndCall for reason
783
+ }
784
+ }),
785
+
786
+ // Mute state
787
+ omiEmitter.addListener(OmiCallEvent.onMuted, (isMuted) => {
788
+ setMuted(isMuted);
789
+ }),
790
+
791
+ // Speaker state
792
+ omiEmitter.addListener(OmiCallEvent.onSpeaker, (isOn) => {
793
+ setSpeaker(isOn);
794
+ }),
795
+
796
+ // Missed call notification tapped
797
+ omiEmitter.addListener(OmiCallEvent.onClickMissedCall, (data) => {
798
+ // Navigate to call history or callback
799
+ }),
800
+
801
+ // Call quality & diagnostics
802
+ omiEmitter.addListener(OmiCallEvent.onCallQuality, ({ quality, stat }) => {
803
+ console.log('Quality level:', quality); // 0=Good, 1=Medium, 2=Bad
804
+ if (stat) {
805
+ console.log('MOS:', stat.mos, 'Jitter:', stat.jitter, 'Latency:', stat.latency);
806
+ }
807
+ }),
808
+ ];
701
809
 
702
- console.log('✅ All required permissions granted');
703
- return true;
704
- }
810
+ return () => subscriptions.forEach(sub => sub.remove());
811
+ }, []);
705
812
  ```
706
813
 
707
- Example: use func `requestLoginPermissions()`
814
+ ---
815
+
816
+ ## Enums
817
+
818
+ ### OmiCallState
819
+
820
+ | Value | Name | Description |
821
+ |-------|------|-------------|
822
+ | 0 | `unknown` | Initial/unknown state |
823
+ | 1 | `calling` | Outgoing call initiated, waiting for response |
824
+ | 2 | `incoming` | Incoming call received |
825
+ | 3 | `early` | Early media (183 Session Progress) |
826
+ | 4 | `connecting` | 200 OK received, establishing media |
827
+ | 5 | `confirmed` | Call active, RTP media flowing |
828
+ | 6 | `disconnected` | Call ended |
829
+ | 7 | `hold` | Call on hold |
830
+
831
+ ### OmiStartCallStatus
832
+
833
+ | Value | Name | Description |
834
+ |-------|------|-------------|
835
+ | 0 | `invalidUuid` | Invalid user UUID |
836
+ | 1 | `invalidPhoneNumber` | Invalid phone number format |
837
+ | 2 | `samePhoneNumber` | Calling your own number |
838
+ | 3 | `maxRetry` | Max retry attempts exceeded |
839
+ | 4 | `permissionDenied` | General permission denied |
840
+ | 450 | `permissionMicrophone` | Microphone permission needed |
841
+ | 451 | `permissionCamera` | Camera permission needed |
842
+ | 452 | `permissionOverlay` | Overlay permission needed |
843
+ | 5 | `couldNotFindEndpoint` | SIP endpoint not found |
844
+ | 6 | `accountRegisterFailed` | SIP registration failed |
845
+ | 7 | `startCallFailed` | Call initiation failed |
846
+ | 8 | `startCallSuccess` | Call started successfully (Android) |
847
+ | 407 | `startCallSuccessIOS` | Call started successfully (iOS) |
848
+ | 9 | `haveAnotherCall` | Another call is in progress |
849
+ | 10 | `accountTurnOffNumberInternal` | Internal number has been deactivated |
850
+ | 11 | `noNetwork` | No network connection available |
851
+
852
+ ### OmiAudioType
853
+
854
+ | Value | Name | Description |
855
+ |-------|------|-------------|
856
+ | 0 | `receiver` | Phone earpiece |
857
+ | 1 | `speaker` | Speakerphone |
858
+ | 2 | `bluetooth` | Bluetooth device |
859
+ | 3 | `headphones` | Wired headphones |
860
+
861
+ ### End Call Status Codes (`codeEndCall`)
862
+
863
+ When a call ends (state = `disconnected`), the `codeEndCall` field in the `onCallStateChanged` event payload contains the status code indicating why the call ended.
864
+
865
+ #### Standard SIP Codes
866
+
867
+ | Code | Description |
868
+ |------|-------------|
869
+ | 200 | Normal call ending |
870
+ | 408 | Call timeout — no answer |
871
+ | 480 | Temporarily unavailable |
872
+ | 486 | Busy (or call rejected via `rejectCall()`) |
873
+ | 487 | Call cancelled before being answered |
874
+ | 500 | Server error |
875
+ | 503 | Server unavailable |
876
+
877
+ #### OMICALL Extended Codes
878
+
879
+ | Code | Description |
880
+ |------|-------------|
881
+ | 600 | Call declined |
882
+ | 601 | Call ended by customer |
883
+ | 602 | Call answered / ended by another agent |
884
+ | 603 | Call declined (via `dropCall()` — stops ringing on ALL devices) |
885
+
886
+ #### Business Rule Codes (PBX)
887
+
888
+ | Code | Description |
889
+ |------|-------------|
890
+ | 850 | Exceeded concurrent call limit |
891
+ | 851 | Exceeded call limit |
892
+ | 852 | No service plan assigned — contact provider |
893
+ | 853 | Internal number has been deactivated |
894
+ | 854 | Number is in DNC (Do Not Call) list |
895
+ | 855 | Exceeded call limit for trial plan |
896
+ | 856 | Exceeded minute limit for trial plan |
897
+ | 857 | Number blocked in configuration |
898
+ | 858 | Unknown or unconfigured number prefix |
899
+ | 859 | No available number for Viettel direction — contact provider |
900
+ | 860 | No available number for Vinaphone direction — contact provider |
901
+ | 861 | No available number for Mobifone direction — contact provider |
902
+ | 862 | Number prefix temporarily locked for Viettel |
903
+ | 863 | Number prefix temporarily locked for Vinaphone |
904
+ | 864 | Number prefix temporarily locked for Mobifone |
905
+ | 865 | Advertising call outside allowed time window — try again later |
906
+
907
+ **Common scenarios:**
708
908
 
709
- ```TypeScript
710
- async function handleLogin() {
711
- const ok = await requestLoginPermissions();
712
- if (!ok) {
713
- // Block login, show alert
714
- return;
715
- }
716
-
717
- // Safe để start service login
718
- // initCallWithApiKey();
719
- initCallWithUserPassword()
720
- }
909
+ ```
910
+ User hangs up normally → 200
911
+ Caller cancels before answer → 487
912
+ Callee rejects (this device) 486 (rejectCall)
913
+ Callee rejects (all devices) → 603 (dropCall)
914
+ Callee busy on another call → 486
915
+ No answer / timeout → 408 or 480
916
+ Answered by another agent → 602
917
+ Exceeded concurrent call limit → 850
918
+ Number in DNC list → 854
721
919
  ```
722
920
 
723
-
724
- 📌 **initCallWithApiKey()**
725
-
726
- 📝 Notes: The information below is taken from the API, you should connect with our Technical team for support
727
-
728
- Description: <br>
729
- - The `initCallWithApiKey()` function is usually used for your client, who only has a certain function, calling a fixed number. For example, you can only call your hotline number
730
-
731
- ```javascript
732
- import { initCallWithApiKey, getCurrentUser } from 'omikit-plugin';
733
- import messaging from '@react-native-firebase/messaging';
734
-
735
- let token: String;
736
-
737
- // Retrieve the appropriate push notification token based on the platform
738
- if (Platform.OS === "ios") {
739
- token = await messaging.getAPNSToken(); // Get APNS token for iOS
740
- } else {
741
- token = await messaging.getToken(); // Get FCM token for Android
742
- }
743
-
744
- // Define the login information required for call initialization
745
- const loginInfo = {
746
- usrUuid: usrUuid, // Unique user identifier
747
- fullName: fullName, // User's full name
748
- apiKey: apiKey, // API key for authentication
749
- phone: phone, // User's phone number
750
- fcmToken: token, // FCM token for Android, APNS token for iOS
751
- isVideo: isVideo, // Determines if video calls are enabled
752
- projectId: projectId // Firebase project ID
753
- };
754
-
755
- // Initialize call functionality using the provided API key
756
- const result = await initCallWithApiKey(loginInfo);
757
-
758
- /* ❌ ❌ NOTE: Please check the user information again, if the object is not empty then you have successfully logged in.
759
- Otherwise, if you have not successfully logged in, you should not navigate to the call screen. When startCall with empty information, it may crash your application or not be clear when receiving the startCall error ❌❌*/
760
-
761
- // Example:
762
-
763
- if (result){
764
- const infoUser = await getCurrentUser()
765
- if (infoUser != null && Object.keys(infoUser).length > 0) {
766
- // ✅ Login OMI Success
767
- // Can navigate to call screen or start call 🚀 🚀
921
+ **Usage:**
922
+
923
+ ```typescript
924
+ omiEmitter.addListener(OmiCallEvent.onCallStateChanged, (data) => {
925
+ if (data.status === OmiCallState.disconnected) {
926
+ const code = data.codeEndCall;
927
+
928
+ if (code === 200) {
929
+ console.log('Call ended normally');
930
+ } else if (code === 487) {
931
+ console.log('Call was cancelled');
932
+ } else if (code === 602) {
933
+ console.log('Call was answered by another agent');
934
+ } else if (code >= 850 && code <= 865) {
935
+ console.log('Business rule error:', code);
936
+ // Show user-friendly message based on code
937
+ } else {
938
+ console.log('Call ended with code:', code);
939
+ }
768
940
  }
769
- }
770
- ```
771
-
772
- 📌 **initCallWithUserPassword()**
773
-
774
- 📝 Notes: The information below is taken from the API, you should connect with our Technical team for support
775
-
776
- ✅ Description: <br>
777
- - The `initCallWithUserPassword()` function is for employees. They can call any telecommunications number allowed in your business on the OMI system.
778
-
779
- ```javascript
780
- import { initCallWithUserPassword, getCurrentUser } from 'omikit-plugin';
781
- import messaging from '@react-native-firebase/messaging';
782
-
783
- let token: String;
784
-
785
- // Retrieve the appropriate push notification token based on the platform
786
- if (Platform.OS === "ios") {
787
- token = await messaging.getAPNSToken(); // Get APNS token for iOS
788
- } else {
789
- token = await messaging.getToken(); // Get FCM token for Android
790
- }
791
-
792
- // Define the login information required for call initialization
793
- const loginInfo = {
794
- userName: userName, // User's SIP username (string)
795
- password: password, // User's SIP password (string)
796
- realm: realm, // SIP server domain (string)
797
- isVideo: isVideo, // Enables or disables video calls (boolean: true/false)
798
- fcmToken: token, // FCM token for Android, APNS token for iOS
799
- projectId: projectId // Firebase project ID
800
- };
801
-
802
- // Initialize call functionality using username and password authentication
803
- initCallWithUserPassword(loginInfo)
804
- .then(result => {
805
- console.log('initCallWithUserPassword success:', result);
806
-
807
- if (result) {
808
- // ✅ Login OMI Success
809
- /* ❌ ❌ NOTE: Please check the user information again, if the object is not empty then you have successfully logged in.
810
- Otherwise, if you have not successfully logged in, you should not navigate to the call screen. When startCall with empty information, it may crash your application or not be clear when receiving the startCall error ❌❌*/
811
- const infoUser = await getCurrentUser()
812
- if (infoUser != null && Object.keys(infoUser).length > 0) {
813
- // ✅ Login OMI Success
814
- // Can navigate to call screen or start call 🚀 🚀
815
- }
816
- }
817
- })
818
- .catch(error => {
819
- // You can log error and check cause error
820
- console.error('initCallWithUserPassword error:', error?.code, error?.message);
821
- if (error?.code === 'ERROR_MISSING_RECORD_AUDIO') { // Please request permission audio
822
- requestPermission();
823
- }
824
- })
825
- .finally(() => {
826
- // Doing something
827
- // setLoading(false);
828
- });
829
- ```
830
- 📝 **Detailed Description of Possible Errors(error?.code)**
831
-
832
- | **Message** | **Description** | **Next Action** |
833
- |------------------------------------|---------------------------------------------------------------------------------|----------------------------
834
- | `ERROR_MISSING_PARAMETERS` | Missing required parameters. Please check your configuration. | Verify all required fields are provided |
835
- | `ERROR_INVALID_CREDENTIALS` | Invalid credentials. Please check username/password. | Double-check login info |
836
- | `ERROR_FORBIDDEN` | Access denied. Check realm/domain permissions. | Confirm account permissions with provider |
837
- | `ERROR_REALM_NOT_FOUND` | Realm not found. Check configuration. | Ensure realm/domain is correct |
838
- | `ERROR_TIMEOUT` | Connection timeout | Retry with stable network |
839
- | `ERROR_MISSING_RECORD_AUDIO` | RECORD_AUDIO permission required for Android 14+ | Ask user to grant microphone permission |
840
- | `ERROR_MISSING_FOREGROUND_SERVICE` | FOREGROUND_SERVICE permission required | Request foreground service permission before starting service |
841
- | `ERROR_MISSING_POST_NOTIFICATIONS` | POST_NOTIFICATIONS permission required for Android 13+ | Request notification permission before registering |
842
- | `ERROR_SERVICE_START_FAILED` | Failed to start SIP service | Check logs and required permissions |
843
- | `ERROR_SERVICE_NOT_AVAILABLE` | SIP service not available | Ensure service is running |
844
- | `ERROR_SERVICE_DEGRADED` | Service degraded - may miss calls when app killed | Keep app in foreground or request proper permissions |
845
- | `ERROR_SERVICE_UNAVAILABLE` | Service temporarily unavailable | Try again later |
846
- | `ERROR_NETWORK_UNAVAILABLE` | Network unavailable | Check network connection |
847
- | `ERROR_CONNECTION_TIMEOUT` | Connection timeout | Verify network and server availability |
848
- | `ERROR_UNKNOWN` | Unknown error occurred | Check logs and report issue |
849
-
850
- 📌 **configPushNotification()**
851
-
852
- ✅ Description: Config push notification: func is used to configure the incoming call popup UI on Android and the representative name for iOS
853
-
854
- ```javascript
855
- import { configPushNotification } from 'omikit-plugin';
856
-
857
- // Configure push notifications for incoming calls
858
- configPushNotification({
859
- notificationIcon: "calling_face", // Notification icon for Android (located in drawable folder)
860
- prefix: "Cuộc gọi tới từ: ", // Prefix for incoming call notifications
861
- incomingBackgroundColor: "#FFFFFFFF", // Background color for incoming call screen
862
- incomingAcceptButtonImage: "join_call", // Image for the accept call button
863
- incomingDeclineButtonImage: "hangup", // Image for the decline call button
864
- backImage: "ic_back", // Image for the back button
865
- userImage: "calling_face", // Default user image for incoming calls
866
- prefixMissedCallMessage: "Cuộc gọi nhỡ từ", // Prefix message for missed call notifications
867
- missedCallTitle: "Cuộc gọi nhỡ", // Title for missed call notifications
868
- userNameKey: "uuid", // User identification key: options are "uuid", "full_name", or "extension"
869
- channelId: "com.channel.sample", // Custom notification channel ID for Android
870
- audioNotificationDescription: "Cuộc gọi audio", // Description for audio call notifications
871
- videoNotificationDescription: "Cuộc gọi video", // Description for video call notifications
872
- representName: "", // Representative name to display for all incoming calls (e.g., business name)
873
- isUserBusy: true // By default, it is set to true. The Omicall system will continue ringing the next user if isUserBusy is true. If it is false, the call will be immediately terminated, assuming the call scenario is based on a criteria-based routing.
874
941
  });
942
+ ```
875
943
 
876
- // Note: Ensure that the following images are added to `android/app/src/main/res/drawable`:
877
- // - incomingAcceptButtonImage (join_call)
878
- // - incomingDeclineButtonImage (hangup)
879
- // - backImage (ic_back)
880
- // - userImage (calling_face)
881
- ```
882
-
883
- 📌 **getInitialCall()**
884
-
885
- ✅ Description: Get call when user open application at first time
886
-
887
- ```javascript
888
- import { getInitialCall } from 'omikit-plugin';
889
-
890
- // Check if there is an ongoing call when the app initializes
891
- const callingInfo = await getInitialCall();
892
-
893
- if (callingInfo !== false) {
894
- // If there is an active call, navigate to the call screen
895
- navigation.navigate('DialCall' as never, callingInfo as never);
896
- }
897
-
898
- // If callingInfo is not false, it means the user has an ongoing call.
899
- ```
900
-
901
- 📌 **startCall()**
902
-
903
- ✅ Description: Used to initiate a call to a random number, telecommunication number, hotline or internal number
944
+ ---
904
945
 
905
- ```javascript
906
- import { startCall } from 'omikit-plugin';
946
+ ## Video Calls
907
947
 
908
- // Start a call with the given phone number
909
- const result = await startCall({
910
- phoneNumber: phone, // The phone number to call
911
- isVideo: false // Set to true for a video call, false for an audio call
912
- });
913
- ```
948
+ ### Setup
914
949
 
915
- ✨ The result returned by `startCall()` is an object with the following structure:
916
-
917
- ```javascript
918
- result = {
919
- "_id": String // This is call_id. it just have id for iOS,
920
- "status": Number // This is result code when make,
921
- "message": String // This is a string key, describing the status of the call
922
- }
923
- ```
950
+ ```typescript
951
+ import { OmiLocalCamera, OmiRemoteCamera } from 'omikit-plugin';
952
+ ```
924
953
 
925
- 📝 Detailed Description of Possible Results
954
+ ### Video Components
926
955
 
927
- | **Message** | **Status** | **Description** |
928
- |-------------------------------------------|------------|-------------------------------------------------------------------------|
929
- | `"INVALID_UUID"` | 0 | uid is invalid (we can not find on my page) |
930
- | `"INVALID_PHONE_NUMBER"` | 1 | sip user is invalid |
931
- | `"SAME_PHONE_NUMBER_WITH_PHONE_REGISTER"` | 2 | Cannot call same phone number |
932
- | `"MAX_RETRY"` | 3 | Call timeout exceeded, please try again later |
933
- | `"PERMISSION_DENIED"` | 4 | The user has not granted MIC or audio permissions |
934
- | `"COULD_NOT_FIND_END_POINT"` | 5 | Please login before making your call |
935
- | `"REGISTER_ACCOUNT_FAIL"` | 6 | Can't log in to OMI (maybe wrong login information) |
936
- | `"START_CALL_FAIL"` | 7 | Call failed, please try again |
937
- | `"HAVE_ANOTHER_CALL"` | 9 | There is another call in progress; please wait for that call to end |
938
- | `"EXTENSION_NUMBER_IS_OFF"` | 10 | Extension number off User is turn Off |
939
- | `"START_CALL_SUCCESS"` | 8 | START CALL SUCCESSFULLY |
956
+ ```tsx
957
+ // Local camera preview (your camera)
958
+ <OmiLocalCamera style={{ width: 120, height: 160 }} />
940
959
 
941
- <br>
960
+ // Remote camera view (other party's video)
961
+ <OmiRemoteCamera style={{ width: '100%', height: '100%' }} />
962
+ ```
942
963
 
964
+ ### Video Call Flow
943
965
 
944
- 📌 **startCallWithUuid()**
966
+ ```typescript
967
+ // 1. Register for video events BEFORE starting call
968
+ await registerVideoEvent();
945
969
 
946
- Description: Call with UUID (only support with Api key):
970
+ // 2. Start video call
971
+ await startCall({ phoneNumber: '0901234567', isVideo: true });
947
972
 
948
- ```javascript
949
- import { startCallWithUuid } from 'omikit-plugin';
973
+ // 3. Toggle video during call
974
+ await toggleOmiVideo(); // on/off video stream
975
+ await switchOmiCamera(); // front/back camera
950
976
 
951
- // Initiate a call using the user's UUID. This function works similarly to `startCall()`.
952
- const result = await startCallWithUuid({
953
- usrUuid: uuid, // The user's UUID (unique identifier)
954
- isVideo: false // Set to true for a video call, false for an audio call
977
+ // 4. Listen for remote video ready
978
+ omiEmitter.addListener(OmiCallEvent.onRemoteVideoReady, () => {
979
+ // Remote video is now available - show OmiRemoteCamera
955
980
  });
956
981
 
957
- // The result returned has the same structure as that from `startCall()`.
982
+ // 5. Cleanup when call ends
983
+ await removeVideoEvent();
958
984
  ```
959
985
 
960
- <br>
986
+ ---
961
987
 
962
- 📌 **joinCall()**
988
+ ## Push Notifications
963
989
 
964
- Description: Used to join (pick up) any incoming call
990
+ > **Setup Guide:** To configure VoIP (iOS) and FCM (Android) for receiving incoming calls, follow the detailed guide at [OMICall Mobile SDK Setup](https://omicrm.io/post/detail/mobile-sdk-post89?lng=vi&p=BrwVVWCLGM).
965
991
 
966
- ```javascript
967
- import {joinCall} from 'omikit-plugin';
992
+ ### Configuration
968
993
 
969
- await joinCall();
994
+ ```typescript
995
+ // Call after startServices(), before or after login
996
+ await configPushNotification({
997
+ // Your platform-specific push configuration
998
+ });
970
999
  ```
971
1000
 
972
- 📝 Note: When calling `joinCall`, sdk will check permission of microphone and camera. If have any permission denied, sdk will send a event `onRequestPermissionAndroid` with list permission you need to request. You need to request permission before calling `joinCall` again.
1001
+ ### How Push Works
973
1002
 
1003
+ #### iOS — VoIP Push (PushKit)
974
1004
 
975
- 📌 **transferCall()**
976
-
977
- ✅ Description: Used to forward the current ongoing call to any employee in your business
978
-
979
- ```javascript
980
- import {transferCall} from 'omikit-plugin';
981
-
982
- transferCall({
983
- phoneNumber: 102 // employee's internal number
984
- })
985
1005
  ```
986
-
987
- 📌 **endCall()**
988
-
989
- Description: We will push a event `endCall` for you.
990
-
991
- ```javascript
992
- import {endCall} from 'omikit-plugin';
993
-
994
- const value = await endCall();
995
- //value is call information
996
- Sample output:
997
- {
998
- "transaction_id":ea7dff38-cb1e-483d-8576...........,
999
- "direction":"inbound",
1000
- "source_number":111,
1001
- "destination_number":110,
1002
- "time_start_to_answer":1682858097393,
1003
- "time_end":1682858152181,
1004
- "sip_user":111,
1005
- "disposition":"answered"
1006
- }
1006
+ PBX ──► APNS ──► PushKit ──► App wakes up
1007
+ ├── Report to CallKit
1008
+ ├── Show system call UI
1009
+ └── Register SIP & connect
1007
1010
  ```
1008
1011
 
1009
- 📌 **dropCall()**
1010
-
1011
- Description: When an incoming call has not yet been answered, and the call scenario is based on criteria, invoking dropCall will cause the OMI system to cancel the ringing on other devices simultaneously.
1012
+ - Uses PushKit VoIP push for reliable delivery even when app is killed
1013
+ - CallKit provides native system call UI (slide to answer)
1014
+ - No user-visible notification CallKit handles the UI
1012
1015
 
1013
- ```javascript
1014
- import {dropCall} from 'omikit-plugin';
1016
+ #### Android — FCM
1015
1017
 
1016
- const value = await dropCall(); // return true/false
1017
-
1018
1018
  ```
1019
-
1020
- 📌 **toggleMute()**
1021
-
1022
- Description: Toggle the audio, On/off audio a call
1023
-
1024
- ```javascript
1025
- import {toggleMute} from 'omikit-plugin';
1026
-
1027
- toggleMute();
1019
+ PBX ──► FCM ──► App receives data message
1020
+ ├── Start foreground service
1021
+ ├── Show full-screen notification
1022
+ └── Register SIP & connect
1028
1023
  ```
1029
1024
 
1030
- 📌 **toggleSpeaker()**
1025
+ - Uses FCM data message (not notification message)
1026
+ - Foreground service keeps the app alive during the call
1027
+ - Full-screen intent for lock screen call UI
1031
1028
 
1032
- Description: Toggle the speaker, On/off the phone speaker
1029
+ ### Notification Management
1033
1030
 
1034
- ```javascript
1035
- import {toggleSpeaker} from 'omikit-plugin';
1036
-
1037
- toggleSpeaker();
1038
- ```
1031
+ ```typescript
1032
+ // Hide the system notification without affecting SIP registration
1033
+ await hideSystemNotificationSafely();
1039
1034
 
1040
- 📌 **toggleHold()**
1035
+ // Hide notification only
1036
+ await hideSystemNotificationOnly();
1041
1037
 
1042
- Description: hold current call
1043
-
1044
- ```javascript
1045
- import {toggleHold} from 'omikit-plugin';
1046
-
1047
- toggleHold();
1038
+ // Hide notification and unregister SIP (with reason for analytics)
1039
+ await hideSystemNotificationAndUnregister('user_dismissed');
1048
1040
  ```
1049
1041
 
1050
- 📌 **sendDTMF()**
1042
+ ---
1051
1043
 
1052
- Description: Send character: We only support `1 to 9` and `* #`.
1044
+ ## Permissions (Android)
1053
1045
 
1054
- ```javascript
1055
- // FUNC IS USED when the user wants key interaction during a call. For example, press key 1, 2, 3.. to move to group
1056
- import {sendDTMF} from 'omikit-plugin';
1046
+ Android 15+ requires explicit runtime permissions for VoIP functionality.
1057
1047
 
1058
- sendDTMF({
1059
- character: text,
1060
- });
1061
- ```
1048
+ ### Quick Setup
1062
1049
 
1063
- 📌 **getCurrentUser()**
1050
+ ```typescript
1051
+ import {
1052
+ checkAndRequestPermissions,
1053
+ checkPermissionStatus,
1054
+ requestPermissionsByCodes,
1055
+ requestSystemAlertWindowPermission,
1056
+ openSystemAlertSetting,
1057
+ } from 'omikit-plugin';
1064
1058
 
1065
- Description: Retrieves the current user's information.
1059
+ // Check and request all permissions at once
1060
+ const granted = await checkAndRequestPermissions(isVideo);
1066
1061
 
1067
- ```javascript
1068
- import {getCurrentUser} from 'omikit-plugin';
1069
- final user = await getCurrentUser();
1062
+ // Check current permission status
1063
+ const status = await checkPermissionStatus();
1064
+ // Returns: { microphone: boolean, camera: boolean, overlay: boolean, ... }
1070
1065
  ```
1071
1066
 
1072
- Output Sample:
1073
-
1074
- ```javascript
1075
- {
1076
- "extension": "111",
1077
- "full_name": "chau1",
1078
- "avatar_url": "",
1079
- "uuid": "122aaa"
1067
+ ### Handle Permission Errors from startCall
1068
+
1069
+ ```typescript
1070
+ const result = await startCall({ phoneNumber, isVideo: false });
1071
+
1072
+ switch (result.status) {
1073
+ case 450: // OmiStartCallStatus.permissionMicrophone
1074
+ await requestPermissionsByCodes([450]);
1075
+ break;
1076
+ case 451: // OmiStartCallStatus.permissionCamera
1077
+ await requestPermissionsByCodes([451]);
1078
+ break;
1079
+ case 452: // OmiStartCallStatus.permissionOverlay
1080
+ await requestSystemAlertWindowPermission();
1081
+ // or open system settings directly:
1082
+ await openSystemAlertSetting();
1083
+ break;
1080
1084
  }
1081
1085
  ```
1082
1086
 
1083
- 📌 **getGuestUser()**
1087
+ ### Permission Codes
1084
1088
 
1085
- Description: Get guest user information:
1089
+ | Code | Permission | Required for |
1090
+ |------|-----------|--------------|
1091
+ | 450 | Microphone | All calls |
1092
+ | 451 | Camera | Video calls |
1093
+ | 452 | Overlay (SYSTEM_ALERT_WINDOW) | Incoming call popup on lock screen |
1086
1094
 
1087
- ```javascript
1088
- import {getGuestUser} from 'omikit-plugin';
1089
- final user = await getGuestUser();
1090
- ```
1091
-
1092
- ✨ Output Sample:
1093
-
1094
- ```javascript
1095
- {
1096
- "extension": "111",
1097
- "full_name": "chau1",
1098
- "avatar_url": "",
1099
- "uuid": "122aaa"
1100
- }
1101
- ```
1095
+ > **Note:** On iOS, permissions are handled via `Info.plist` and system prompts. The above functions return `true` on iOS.
1102
1096
 
1103
- 📌 **ggetUserInfo()**
1097
+ ---
1104
1098
 
1105
- Description: Get user information from internal number
1099
+ ## Quality & Diagnostics
1106
1100
 
1107
- ```javascript
1108
- import {getUserInfo} from 'omikit-plugin';
1109
- final user = await ggetUserInfo("111");
1110
- ```
1101
+ The `onCallQuality` event provides real-time call quality metrics during an active call.
1111
1102
 
1112
- Output Sample:
1103
+ ### Event Payload
1113
1104
 
1114
- ```javascript
1105
+ ```typescript
1115
1106
  {
1116
- "extension": "111",
1117
- "full_name": "chau1",
1118
- "fullName": "chau1",
1119
- "avatar_url": "",
1120
- "avatarUrl": "",
1121
- "uuid": "122aaa"
1107
+ quality: number; // 0 = Good, 1 = Medium, 2 = Bad
1108
+ stat: {
1109
+ mos: number; // Mean Opinion Score (1.0 – 5.0)
1110
+ jitter: number; // Jitter in milliseconds
1111
+ latency: number; // Round-trip latency in milliseconds
1112
+ packetLoss: number; // Packet loss percentage (0 – 100)
1113
+ lcn?: number; // Loss Connect Number (Android only)
1114
+ }
1122
1115
  }
1123
1116
  ```
1124
-
1125
- 📌 **endCall()**
1126
1117
 
1127
- Description: End a completed call (including rejecting a call).
1118
+ ### MOS Score Thresholds
1128
1119
 
1129
- ```javascript
1130
- import {endCall} from 'omikit-plugin';
1131
- endCall();
1132
- ```
1120
+ | MOS Range | Quality | Description |
1121
+ |-----------|---------|-------------|
1122
+ | ≥ 4.0 | Excellent | Clear, no perceptible issues |
1123
+ | 3.5 – 4.0 | Good | Minor impairments, generally clear |
1124
+ | 3.0 – 3.5 | Fair | Noticeable degradation |
1125
+ | 2.0 – 3.0 | Poor | Significant degradation |
1126
+ | < 2.0 | Bad | Nearly unusable |
1133
1127
 
1128
+ ### Network Freeze Detection
1134
1129
 
1135
- 📌 **rejectCall()**
1130
+ When `lcn` (Loss Connect Number) increases consecutively, it indicates potential network freeze — useful for showing a "weak network" warning in the UI.
1136
1131
 
1137
- Description: Used to reject an incoming call when the user has not accepted it yet.
1132
+ ### Usage Example
1138
1133
 
1139
- 📝 Note: Do not use this function to end an ongoing call.
1134
+ ```typescript
1135
+ import { omiEmitter, OmiCallEvent } from 'omikit-plugin';
1140
1136
 
1141
- ```javascript
1142
- import {rejectCall} from 'omikit-plugin';
1143
- rejectCall();
1137
+ omiEmitter.addListener(OmiCallEvent.onCallQuality, ({ quality, stat }) => {
1138
+ // quality: 0 = Good, 1 = Medium, 2 = Bad
1139
+ if (quality >= 2 && stat) {
1140
+ console.warn(
1141
+ `Poor call quality — MOS: ${stat.mos}, Jitter: ${stat.jitter}ms, ` +
1142
+ `Latency: ${stat.latency}ms, Loss: ${stat.packetLoss}%`
1143
+ );
1144
+ // Show weak network warning to user
1145
+ }
1146
+ });
1144
1147
  ```
1145
1148
 
1149
+ ---
1146
1150
 
1147
- 📌 **logout()**
1151
+ ## Advanced Features
1148
1152
 
1149
- Description: logout and remove all information.
1150
-
1151
- ```javascript
1152
- import {logout} from 'omikit-plugin';
1153
- logout();
1154
- ```
1153
+ ### Check Credentials Without Connecting
1155
1154
 
1156
- 📌 **systemAlertWindow()**
1155
+ Validate credentials without establishing a SIP connection:
1157
1156
 
1158
- ✅ Description: Check system alert window permission (only Android).
1159
-
1160
- ```javascript
1161
- import {systemAlertWindow} from 'omikit-plugin';
1162
- const isAllow = await systemAlertWindow();
1163
- //true => allow
1164
- //false => denied
1157
+ ```typescript
1158
+ const result = await checkCredentials({
1159
+ userName: 'sip_user',
1160
+ password: 'sip_password',
1161
+ realm: 'your_realm',
1162
+ });
1163
+ // result: { success: boolean, statusCode: number, message: string }
1165
1164
  ```
1166
1165
 
1166
+ ### Register With Options
1167
1167
 
1168
- 📌 **openSystemAlertSetting()**
1169
-
1170
- ✅ Description: Open to enable system alert window (only Android).
1168
+ Full control over registration behavior:
1171
1169
 
1172
- ```javascript
1173
- import {openSystemAlertSetting} from 'omikit-plugin';
1174
-
1175
- if (Platform.OS === 'android') {
1176
- openSystemAlertSetting();
1177
- }
1170
+ ```typescript
1171
+ const result = await registerWithOptions({
1172
+ // Registration options
1173
+ });
1174
+ // result: { success: boolean, statusCode: number, message: string }
1178
1175
  ```
1179
1176
 
1180
- 📌 **getCurrentAudio()**
1177
+ ### Keep-Alive
1181
1178
 
1182
- Description: Get current information of audio devices
1179
+ Monitor and maintain SIP connection:
1183
1180
 
1184
- ```javascript
1185
- import {getCurrentAudio} from 'omikit-plugin';
1181
+ ```typescript
1182
+ // Check current keep-alive status
1183
+ const status = await getKeepAliveStatus();
1186
1184
 
1187
- getCurrentAudio().then((data: any) => {
1188
- console.log(data); // [{"name": "Speaker", "type": "Speaker"}]
1189
- // Note: Data is an array containing information about audio devices, with parameters:
1190
- // - name: Name of the audio device
1191
- // - type: Audio device type (e.g. "Speaker", "Receiver", etc.)
1192
- });
1185
+ // Manually trigger a keep-alive ping
1186
+ await triggerKeepAlivePing();
1193
1187
  ```
1194
1188
 
1195
- 📌 **setAudio()**
1189
+ ### Cold Start Call Handling
1196
1190
 
1197
- Description: set Audio calls the current device
1191
+ When the app is launched from a push notification:
1198
1192
 
1199
- ```javascript
1200
- import { getAudio, setAudio} from 'omikit-plugin';
1193
+ ```typescript
1194
+ // Call this in your app's entry point
1195
+ const initialCall = await getInitialCall();
1201
1196
 
1202
- const audioList = await getAudio(); // Get a list of supported audio device types
1203
- console.log("audioList --> ", audioList) // audioList --> [{"name": "Receiver", "type": "Receiver"}, {"name": "Speaker", "type": "Speaker"}]
1204
-
1205
- const receiver = audioList.find((element: any) => {
1206
- return element.type === 'Receiver'; // type: "Speaker" is the external speaker, Receiver is the internal speaker
1207
- });
1208
-
1209
- setAudio({
1210
- portType: receiver.type,
1211
- });
1197
+ if (initialCall) {
1198
+ // There's a pending call navigate to call screen
1199
+ console.log('Pending call from:', initialCall.callerNumber);
1200
+ }
1212
1201
  ```
1213
1202
 
1214
- ##### 📝 Video Call functions: Support only video call, You need enable video in `init functions` and `start call` to implements under functions.
1215
-
1216
- ✅ Description: Video Call functions: Support only video call, You need enable video in `init functions` and `start call` to implements under functions.
1217
-
1218
-
1219
- 📌 Switch front/back camera: We use the front camera for first time.
1220
-
1221
- ```javascript
1222
- import {switchOmiCamera} from 'omikit-plugin';
1223
- switchOmiCamera();
1224
- ```
1225
-
1226
- 📌 Toggle a video in video call: On/off video in video call
1227
-
1228
- ```javascript
1229
- import {toggleOmiVideo} from 'omikit-plugin';
1230
- toggleOmiVideo();
1231
- ```
1232
-
1233
- 📌 Local Camera Widget: Your camera view in a call
1234
-
1235
- ```javascript
1236
- import { OmiLocalCameraView } from 'omikit-plugin';
1237
- <OmiLocalCameraView style={styles.localCamera} />
1238
- ```
1239
-
1240
- 📌 Remote Camera Widget: Remote camera view in a call
1241
-
1242
- ```javascript
1243
- import { OmiRemoteCameraView } from 'omikit-plugin';
1244
- <OmiRemoteCameraView style={styles.remoteCamera} />
1245
- ```
1203
+ ---
1246
1204
 
1247
- 📌 More function: Refresh local camera
1205
+ ## Troubleshooting
1248
1206
 
1249
- ```javascript
1250
- import {refreshLocalCamera} from 'omikit-plugin';
1251
- refreshLocalCamera();
1252
- ```
1207
+ | Problem | Cause | Solution |
1208
+ |---------|-------|----------|
1209
+ | `LINKING_ERROR` on launch | Native module not linked | Run `pod install` (iOS) or rebuild the app |
1210
+ | Login returns `false` | Wrong SIP credentials or network | Verify `userName`, `password`, `realm`, `host` |
1211
+ | `accountRegisterFailed` (status 6) | SIP registration failed | Check `realm` and `host` params; verify network connectivity |
1212
+ | No incoming calls | Push not configured | Ensure FCM (Android) / PushKit VoIP (iOS) is set up |
1213
+ | No incoming calls on iOS (killed) | Missing Background Mode | Enable "Voice over IP" in Background Modes |
1214
+ | Incoming call on Android — no UI | Missing overlay permission | Call `requestSystemAlertWindowPermission()` |
1215
+ | `startCall` returns 450/451/452 | Missing runtime permissions | Call `requestPermissionsByCodes([code])` |
1216
+ | No audio during call | Audio route issue | Check `getAudio()` and `setAudio()` |
1217
+ | Video not showing | Video event not registered | Call `registerVideoEvent()` before the call |
1218
+ | NativeEventEmitter warning (iOS) | Old RN bridge issue | Upgrade to v4.0.x with New Architecture |
1219
+ | `Invalid local URI` in logs | Empty proxy/host in login | Pass `host` parameter in `initCallWithUserPassword` |
1220
+ | Build error with New Arch | Codegen not configured | Ensure `codegenConfig` exists in `package.json` |
1221
+ | 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 |
1253
1222
 
1254
- 📌 More function: Refresh remote camera
1223
+ ---
1255
1224
 
1256
- ```javascript
1257
- import {refreshRemoteCamera} from 'omikit-plugin';
1258
- refreshRemoteCamera();
1259
- ```
1225
+ ## Documentation
1260
1226
 
1261
- 📌 Register event: Register remote video ready: only visible on iOS
1227
+ Full documentation in [`./docs/`](./docs/):
1262
1228
 
1263
- ```javascript
1264
- import {registerVideoEvent} from 'omikit-plugin';
1265
- registerVideoEvent();
1266
- ```
1229
+ - [API Integration Guide (App-to-App)](./docs/api-integration-guide.md)
1230
+ - [Project Overview & PDR](./docs/project-overview-pdr.md)
1231
+ - [Codebase Summary](./docs/codebase-summary.md)
1232
+ - [System Architecture](./docs/system-architecture.md)
1233
+ - [Code Standards](./docs/code-standards.md)
1234
+ - [Roadmap](./docs/project-roadmap.md)
1267
1235
 
1268
- ### 🚀🚀 Events listener 🚀🚀
1269
-
1270
- ```javascript
1271
- import { omiEmitter } from 'omikit-plugin';
1272
-
1273
- /*
1274
- ❌ ❌ With TypeScript, in Android, it seems our omiEmitter is not working properly. Please use the following manual declaration, to ensure performance
1275
- */
1276
-
1277
- // 📌 For TypeScript, Android
1278
- import { NativeEventEmitter, NativeModules } from "react-native";
1279
- const { OmikitPlugin } = NativeModules;
1280
- const omiEmitter = new NativeEventEmitter(OmikitPlugin);
1281
-
1282
-
1283
-
1284
-
1285
- useEffect(() => {
1286
- omiEmitter.addListener(OmiCallEvent.onCallStateChanged, onCallStateChanged);
1287
- omiEmitter.addListener(OmiCallEvent.onMuted, onMuted);
1288
- omiEmitter.addListener(OmiCallEvent.onSpeaker, onSpeaker);
1289
- omiEmitter.addListener(OmiCallEvent.onHold, onHold);
1290
- omiEmitter.addListener(OmiCallEvent.onClickMissedCall, clickMissedCall);
1291
- omiEmitter.addListener(OmiCallEvent.onSwitchboardAnswer, onSwitchboardAnswer);
1292
- omiEmitter.addListener(OmiCallEvent.onCallQuality, onCallQuality);
1293
-
1294
- omiEmitter.addListener(OmiCallEvent.onAudioChange, onAudioChange);
1295
-
1296
-
1297
- if(Platform.OS == "android") {
1298
- omiEmitter.addListener(OmiCallEvent.onRequestPermissionAndroid, onRequestPermission);
1299
- }
1300
-
1301
- if (Platform.OS === 'ios') {
1302
- registerVideoEvent();
1303
- omiEmitter.addListener(
1304
- OmiCallEvent.onRemoteVideoReady,
1305
- refreshRemoteCameraEvent
1306
- );
1307
- }
1308
-
1309
- return () => {
1310
- omiEmitter.removeAllListeners(OmiCallEvent.onCallStateChanged);
1311
- omiEmitter.removeAllListeners(OmiCallEvent.onMuted);
1312
- omiEmitter.removeAllListeners(OmiCallEvent.onHold);
1313
- omiEmitter.removeAllListeners(OmiCallEvent.onSpeaker);
1314
- omiEmitter.removeAllListeners(OmiCallEvent.onSwitchboardAnswer);
1315
- omiEmitter.removeAllListeners(OmiCallEvent.onAudioChange);
1316
-
1317
- if(Platform.OS == "android") {
1318
- omiEmitter.removeAllListeners(OmiCallEvent.onRequestPermissionAndroid);
1319
- }
1320
-
1321
- if (Platform.OS === 'ios') {
1322
- removeVideoEvent();
1323
- omiEmitter.removeAllListeners(OmiCallEvent.onRemoteVideoReady);
1324
- }
1325
- };
1326
- }, []);
1327
- ```
1328
-
1329
- - ✅ **Important Event: `onCallStateChanged`**
1330
- This event is used to listen for call state changes. The emitted event is an `OmiAction` object containing two properties: `actionName` and `data`.
1331
-
1332
- - 📝 **Action Name Values:**
1333
- - **`onCallStateChanged`**: Indicates that the call state has changed.
1334
- - **`onSwitchboardAnswer`**: Indicates that the switchboard SIP is listening.
1335
- - **Call Status Values:**
1336
- - `unknown` (0)
1337
- - `calling` (1)
1338
- - `incoming` (2)
1339
- - `early` (3)
1340
- - `connecting` (4)
1341
- - `confirmed` (5)
1342
- - `disconnected` (6)
1343
- - `hold` (7)
1344
-
1345
- > **Note:** The `onCallStateChanged` event tracks the current state of the call. Please refer to `OmiCallState` for detailed status descriptions.
1346
-
1347
- ### 📞 **Call State Lifecycle**
1348
- - ✅ **Incoming Call Lifecycle:**
1349
- `incoming` → `connecting` → `confirmed` → `disconnected`
1350
-
1351
- - ✅ **Outgoing Call Lifecycle:**
1352
- `calling` → `early` → `connecting` → `confirmed` → `disconnected`
1353
-
1354
-
1355
- ```javascript
1356
- // The event is updated every time the call status changes
1357
- const onCallStateChanged = (data: any) => {
1358
- // ⚠️ ⚠️ Currently, we support two data formats: camelCase and snake_case. Snake_case is used for data v1, while camelCase is for v2. We encourage customers to use camelCase instead of snake_case, as we plan to completely remove the snake_case format in the future ❌ ❌
1359
-
1360
- /*
1361
- Call state change event data (Object) includes:
1362
-
1363
- - _id: string (UUID of the call)
1364
- - callInfo: object (Detailed call information)
1365
- - callerNumber: string (Phone number of the caller)
1366
- - code_end_call, codeEndCall: number (Status code when the call ends)
1367
- - destination_number, destinationNumber?: string (Destination phone number, optional)
1368
- - direction: string ("inbound" or "outbound", call direction)
1369
- - disposition: string (Call answer status)
1370
- - incoming: boolean (true if it is an incoming call)
1371
- - isVideo: boolean (true if it is a video call)
1372
- - sip_user, sipUser: string (Current SIP user)
1373
- - source_number, sourceNumber: string (SIP number of the user)
1374
- - status: string (value matching with List status call)
1375
- - time_end, timeEnd: number (Timestamp when the call ended)
1376
- - time_start_to_answer, timeStartToAnswer: number (Time taken to answer the call)
1377
- - transaction_id, transactionId: string (OMI Call unique ID)
1378
- - typeNumber: string ("", "internal", "phone", "zalo")
1379
- */
1380
- };
1381
-
1382
- // Event returned when the user mutes the call
1383
- const onMuted = (isMuted: boolean) => {
1384
- // isMuted: true when muted call
1385
- }
1386
-
1387
- // Event returns value when user holds call
1388
- const onHold = (isHold: boolean) => {
1389
- // isHold: true when hold call
1390
- }
1391
-
1392
- // The event updates the quality of an ongoing call
1393
- const onCallQuality = (data: any) => {
1394
- const { quality } = data;
1395
- // quality: int is mean quality off calling
1396
- // 1 is good, 2 is medium, 3 is low
1397
- }
1398
-
1399
- // Even when user turn on speakerphone
1400
- const onSpeaker = (isSpeaker: boolean) => {
1401
- // isSpeaker: true, false
1402
- // True mean speaker devices is open
1403
- }
1404
-
1405
- // * onSwitchboardAnswer have callback when employee answered script call.
1406
- const onSwitchboardAnswer = (data: any) => {
1407
- const { sip } = data
1408
- // sip: String
1409
- }
1410
-
1411
- // * onAudioChange have callback when the user switches the audio output device (headphones)
1412
- const onAudioChange = (audioData: any) => {
1413
- const { data } = audioData;
1414
-
1415
- }
1416
- ```
1236
+ ## License
1417
1237
 
1418
- Table describing `code_end_call, codeEndCall` status
1419
-
1420
- | Code | Description |
1421
- | --------------- | --------------------------------------------------------------------------------------------------------------------- |
1422
- | `600, 503` | These are the codes of the network operator or the user who did not answer the call |
1423
- | `408` | Call request timeout (Each call usually has a waiting time of 30 seconds. If the 30 seconds expire, it will time out) |
1424
- | `403` | Your service plan only allows calls to dialed numbers. Please upgrade your service pack|
1425
- | `404` | The current number is not allowed to make calls to the carrier|
1426
- | `480` | The number has an error, please contact support to check the details |
1427
- | `603` | The call was rejected. Please check your account limit or call barring configuration! |
1428
- | `850` | Simultaneous call limit exceeded, please try again later |
1429
- | `486` | The listener refuses the call and does not answer |
1430
- | `601` | Call ended by the customer |
1431
- | `602` | Call ended by the other employee |
1432
- | `603` | The call was rejected. Please check your account limit or call barring configuration |
1433
- | `850` | Simultaneous call limit exceeded, please try again later |
1434
- | `851` | Call duration limit exceeded, please try again later |
1435
- | `852` | Service package not assigned, please contact the provider |
1436
- | `853` | Internal number has been disabled |
1437
- | `854` | Subscriber is in the DNC list |
1438
- | `855` | Exceeded the allowed number of calls for the trial package |
1439
- | `856` | Exceeded the allowed minutes for the trial package |
1440
- | `857` | Subscriber has been blocked in the configuration |
1441
- | `858` | Unidentified or unconfigured number |
1442
- | `859` | No available numbers for Viettel direction, please contact the provider |
1443
- | `860` | No available numbers for VinaPhone direction, please contact the provider |
1444
- | `861` | No available numbers for Mobifone direction, please contact the provider |
1445
- | `862` | Temporary block on Viettel direction, please try again |
1446
- | `863` | Temporary block on VinaPhone direction, please try again |
1447
- | `864` | Temporary block on Mobifone direction, please try again |
1448
- | `865` | he advertising number is currently outside the permitted calling hours, please try again later |
1449
-
1450
-
1451
- ### **Breaking Changes**
1452
- - **Android 15+ Support**: Requires additional permissions in AndroidManifest.xml
1453
- - **New Architecture**: Still requires `newArchEnabled=false`
1454
- - **Minimum SDK**: Android SDK 21+ recommended for full feature support
1455
-
1456
- # ⚠️ Issues
1457
-
1458
-
1459
- ## ✨ iOS
1460
-
1461
- - Must use "Rosetta Destination" to run debug example app on macOS Apple chip
1238
+ MIT [ViHAT Group](https://github.com/VIHATTeam)