omikit-plugin 3.3.29 → 4.0.1
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.
- package/README.md +949 -1223
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/omikitplugin/FLLocalCameraModule.kt +1 -1
- package/android/src/main/java/com/omikitplugin/FLRemoteCameraModule.kt +1 -1
- package/android/src/main/java/com/omikitplugin/OmikitPluginModule.kt +277 -325
- package/android/src/main/java/com/omikitplugin/constants/constant.kt +2 -1
- package/ios/CallProcess/CallManager.swift +45 -35
- package/ios/Constant/Constant.swift +1 -0
- package/ios/Library/OmikitPlugin.m +75 -1
- package/ios/Library/OmikitPlugin.swift +199 -16
- package/ios/OmikitPlugin-Protocol.h +161 -0
- package/lib/commonjs/NativeOmikitPlugin.js +9 -0
- package/lib/commonjs/NativeOmikitPlugin.js.map +1 -0
- package/lib/commonjs/index.js +11 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/omi_audio_type.js +20 -0
- package/lib/commonjs/omi_audio_type.js.map +1 -0
- package/lib/commonjs/omi_local_camera.js +12 -2
- package/lib/commonjs/omi_local_camera.js.map +1 -1
- package/lib/commonjs/omi_remote_camera.js +12 -2
- package/lib/commonjs/omi_remote_camera.js.map +1 -1
- package/lib/commonjs/omi_start_call_status.js +30 -0
- package/lib/commonjs/omi_start_call_status.js.map +1 -1
- package/lib/commonjs/omikit.js +110 -16
- package/lib/commonjs/omikit.js.map +1 -1
- package/lib/module/NativeOmikitPlugin.js +3 -0
- package/lib/module/NativeOmikitPlugin.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/omi_audio_type.js +14 -0
- package/lib/module/omi_audio_type.js.map +1 -0
- package/lib/module/omi_local_camera.js +13 -2
- package/lib/module/omi_local_camera.js.map +1 -1
- package/lib/module/omi_remote_camera.js +13 -2
- package/lib/module/omi_remote_camera.js.map +1 -1
- package/lib/module/omi_start_call_status.js +30 -0
- package/lib/module/omi_start_call_status.js.map +1 -1
- package/lib/module/omikit.js +104 -17
- package/lib/module/omikit.js.map +1 -1
- package/omikit-plugin.podspec +26 -24
- package/package.json +11 -2
- package/src/NativeOmikitPlugin.ts +160 -0
- package/src/index.tsx +2 -1
- package/src/omi_audio_type.tsx +9 -0
- package/src/omi_local_camera.tsx +12 -3
- package/src/omi_remote_camera.tsx +12 -3
- package/src/omi_start_call_status.tsx +29 -10
- package/src/omikit.tsx +96 -19
- package/src/types/index.d.ts +111 -11
package/README.md
CHANGED
|
@@ -1,1461 +1,1187 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OMICALL SDK for React Native
|
|
2
2
|
|
|
3
|
-
The
|
|
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
|
-
|
|
5
|
+
**Status:** Active maintenance | **Version:** 4.0.1
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
9
|
+
## Table of Contents
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
30
|
+
---
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
<br>
|
|
32
|
+
## Compatibility
|
|
21
33
|
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
```ruby
|
|
30
|
-
yarn add omikit-plugin --latest
|
|
31
|
-
```
|
|
45
|
+
### Native SDK Versions
|
|
32
46
|
|
|
33
|
-
|
|
47
|
+
| Platform | SDK | Version |
|
|
48
|
+
|----------|-----|---------|
|
|
49
|
+
| Android | OMIKIT | 2.6.4 |
|
|
50
|
+
| iOS | OmiKit | 1.10.34 |
|
|
34
51
|
|
|
35
|
-
|
|
36
|
-
📌 **Config gradle file**
|
|
37
|
-
- Add these settings in `build.gradle`:
|
|
52
|
+
---
|
|
38
53
|
|
|
39
|
-
|
|
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
|
-
```
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
+
---
|
|
103
73
|
|
|
104
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
###
|
|
115
|
+
### 3. Maven Repository
|
|
116
|
+
|
|
117
|
+
**Option A — `settings.gradle.kts` (recommended for new projects)**
|
|
237
118
|
|
|
238
119
|
```kotlin
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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("")
|
|
250
133
|
}
|
|
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
|
-
}
|
|
261
|
-
|
|
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
|
-
}
|
|
134
|
+
authentication {
|
|
135
|
+
create<BasicAuthentication>("basic")
|
|
268
136
|
}
|
|
269
|
-
}
|
|
137
|
+
}
|
|
270
138
|
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
271
141
|
|
|
272
|
-
|
|
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}")
|
|
287
|
-
}
|
|
288
|
-
} ?: Log.e("MainActivity", "Intent in onNewIntent is null.")
|
|
289
|
-
}
|
|
142
|
+
**Option B — `build.gradle` (Groovy / legacy projects)**
|
|
290
143
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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)
|
|
298
160
|
}
|
|
299
|
-
}
|
|
161
|
+
}
|
|
300
162
|
}
|
|
301
|
-
|
|
302
|
-
// your config ....
|
|
303
163
|
}
|
|
304
164
|
```
|
|
305
165
|
|
|
306
|
-
|
|
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)
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
166
|
+
Then add your credentials to `~/.gradle/gradle.properties` (or project-level `gradle.properties`):
|
|
318
167
|
|
|
168
|
+
```properties
|
|
169
|
+
OMI_USER=your_github_username
|
|
170
|
+
OMI_TOKEN=your_github_personal_access_token
|
|
319
171
|
```
|
|
320
172
|
|
|
321
|
-
|
|
173
|
+
> **Note:** The GitHub token needs `read:packages` scope. Generate one at [GitHub Settings > Tokens](https://github.com/settings/tokens).
|
|
322
174
|
|
|
323
|
-
|
|
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)
|
|
175
|
+
### 4. New Architecture (Optional)
|
|
325
176
|
|
|
326
|
-
|
|
177
|
+
To enable New Architecture on Android, in `android/gradle.properties`:
|
|
327
178
|
|
|
328
|
-
|
|
179
|
+
```properties
|
|
180
|
+
newArchEnabled=true
|
|
181
|
+
```
|
|
329
182
|
|
|
330
|
-
|
|
183
|
+
---
|
|
331
184
|
|
|
332
|
-
|
|
185
|
+
## iOS Setup
|
|
333
186
|
|
|
334
|
-
|
|
187
|
+
### 1. Info.plist
|
|
335
188
|
|
|
336
|
-
|
|
189
|
+
Add to your `Info.plist`:
|
|
337
190
|
|
|
338
|
-
|
|
191
|
+
```xml
|
|
192
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
193
|
+
<string>Required for VoIP calls</string>
|
|
194
|
+
<key>NSCameraUsageDescription</key>
|
|
195
|
+
<string>Required for video calls</string>
|
|
196
|
+
```
|
|
339
197
|
|
|
340
|
-
|
|
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>
|
|
198
|
+
### 2. Background Modes
|
|
346
199
|
|
|
347
|
-
|
|
200
|
+
In Xcode, enable the following Background Modes:
|
|
348
201
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
@property (nonatomic, strong) PKPushRegistry * voipRegistry;
|
|
202
|
+
- [x] Voice over IP
|
|
203
|
+
- [x] Remote notifications
|
|
204
|
+
- [x] Background fetch
|
|
353
205
|
|
|
354
|
-
|
|
355
|
-
|
|
206
|
+
### 3. Push Notifications
|
|
207
|
+
|
|
208
|
+
Enable **Push Notifications** capability in Xcode for VoIP push (PushKit).
|
|
356
209
|
|
|
357
|
-
|
|
210
|
+
### 4. AppDelegate Setup
|
|
211
|
+
|
|
212
|
+
In your `AppDelegate.mm` (or `.m`):
|
|
358
213
|
|
|
359
214
|
```objc
|
|
360
|
-
#import <
|
|
361
|
-
#import <UserNotifications/UserNotifications.h>
|
|
362
|
-
// #import <OmiKit/OmiKit-umbrella.h>
|
|
363
|
-
#import <OmiKit/OmiKit.h>
|
|
215
|
+
#import <OmiKit/OmiKit-umbrella.h>
|
|
364
216
|
#import <OmiKit/Constants.h>
|
|
217
|
+
```
|
|
365
218
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
@property (nonatomic, strong) UIWindow *window;
|
|
369
|
-
@property (nonatomic, strong) PushKitManager *pushkitManager;
|
|
370
|
-
@property (nonatomic, strong) CallKitProviderDelegate * provider;
|
|
371
|
-
@property (nonatomic, strong) PKPushRegistry * voipRegistry;
|
|
219
|
+
### 5. New Architecture (Optional)
|
|
372
220
|
|
|
373
|
-
|
|
221
|
+
In your `Podfile`:
|
|
374
222
|
|
|
223
|
+
```ruby
|
|
224
|
+
ENV['RN_NEW_ARCH_ENABLED'] = '1'
|
|
375
225
|
```
|
|
376
226
|
|
|
377
|
-
|
|
227
|
+
For **full bridgeless mode**, in `AppDelegate.mm`:
|
|
378
228
|
|
|
379
229
|
```objc
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
|
230
|
+
- (BOOL)bridgelessEnabled
|
|
384
231
|
{
|
|
385
|
-
|
|
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 ------
|
|
395
|
-
|
|
396
232
|
return YES;
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
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
233
|
}
|
|
427
234
|
```
|
|
428
235
|
|
|
429
|
-
|
|
236
|
+
Then run `cd ios && pod install`.
|
|
430
237
|
|
|
431
|
-
|
|
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
|
|
238
|
+
---
|
|
439
239
|
|
|
440
|
-
|
|
441
|
-
- Add these lines into `Info.plist`:
|
|
240
|
+
## Architecture Overview
|
|
442
241
|
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
242
|
+
```
|
|
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
|
+
└─────────────┘ └──────────────┘
|
|
449
273
|
```
|
|
450
274
|
|
|
451
|
-
|
|
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 (call once on app launch)
|
|
292
|
+
await startServices();
|
|
293
|
+
|
|
294
|
+
// Step 2: Login with SIP credentials
|
|
295
|
+
const loginResult = await initCallWithUserPassword({
|
|
296
|
+
userName: 'sip_user',
|
|
297
|
+
password: 'sip_password',
|
|
298
|
+
realm: 'your_realm',
|
|
299
|
+
host: '', // SIP proxy, defaults to vh.omicrm.com
|
|
300
|
+
isVideo: false,
|
|
301
|
+
fcmToken: 'your_fcm_token',
|
|
302
|
+
projectId: 'your_project_id', // optional
|
|
303
|
+
});
|
|
452
304
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
305
|
+
// Step 3: Listen to call events
|
|
306
|
+
const subscription = omiEmitter.addListener(
|
|
307
|
+
OmiCallEvent.onCallStateChanged,
|
|
308
|
+
(data) => {
|
|
309
|
+
console.log('Call state:', data.status);
|
|
310
|
+
|
|
311
|
+
switch (data.status) {
|
|
312
|
+
case OmiCallState.incoming:
|
|
313
|
+
// Show incoming call UI
|
|
314
|
+
// data.callerNumber, data.isVideo
|
|
315
|
+
break;
|
|
316
|
+
case OmiCallState.confirmed:
|
|
317
|
+
// Call connected — show active call UI
|
|
318
|
+
break;
|
|
319
|
+
case OmiCallState.disconnected:
|
|
320
|
+
// Call ended
|
|
321
|
+
// data.codeEndCall — SIP end code
|
|
322
|
+
break;
|
|
463
323
|
}
|
|
324
|
+
}
|
|
325
|
+
);
|
|
464
326
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
```
|
|
327
|
+
// Step 4: Make outgoing call
|
|
328
|
+
const result = await startCall({
|
|
329
|
+
phoneNumber: '0901234567',
|
|
330
|
+
isVideo: false,
|
|
331
|
+
});
|
|
471
332
|
|
|
472
|
-
|
|
333
|
+
if (result.status === 8) {
|
|
334
|
+
console.log('Call started, ID:', result._id);
|
|
335
|
+
}
|
|
473
336
|
|
|
474
|
-
|
|
475
|
-
|
|
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>
|
|
337
|
+
// Step 5: Accept incoming call
|
|
338
|
+
await joinCall();
|
|
478
339
|
|
|
479
|
-
|
|
340
|
+
// Step 6: End call
|
|
341
|
+
await endCall();
|
|
480
342
|
|
|
481
|
-
|
|
482
|
-
|
|
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.
|
|
343
|
+
// Cleanup on unmount
|
|
344
|
+
subscription.remove();
|
|
486
345
|
```
|
|
487
346
|
|
|
488
|
-
|
|
347
|
+
---
|
|
489
348
|
|
|
490
|
-
|
|
349
|
+
## Authentication
|
|
491
350
|
|
|
492
|
-
|
|
351
|
+
Two authentication methods are available. Each supports two login modes depending on who is using the app:
|
|
493
352
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
use_react_native!(
|
|
499
|
-
:path => config[:reactNativePath],
|
|
500
|
-
:new_arch_enabled => false, // <=== add this line
|
|
501
|
-
... your config
|
|
502
|
-
)
|
|
503
|
-
```
|
|
353
|
+
| Mode | `isSkipDevices` | Use Case | Capabilities |
|
|
354
|
+
|------|-----------------|----------|--------------|
|
|
355
|
+
| **Agent** (default) | `false` | Employees / call center agents | Can make outbound calls to any telecom number |
|
|
356
|
+
| **Customer** | `true` | End customers | Can only call the business hotline (no outbound to external numbers) |
|
|
504
357
|
|
|
505
|
-
|
|
358
|
+
### Option 1: Username + Password (SIP Credentials)
|
|
506
359
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
360
|
+
```typescript
|
|
361
|
+
await initCallWithUserPassword({
|
|
362
|
+
userName: string, // SIP username
|
|
363
|
+
password: string, // SIP password
|
|
364
|
+
realm: string, // SIP realm/domain
|
|
365
|
+
host?: string, // SIP proxy server (optional)
|
|
366
|
+
isVideo: boolean, // Enable video capability
|
|
367
|
+
fcmToken: string, // Firebase token for push notifications
|
|
368
|
+
projectId?: string, // OMICALL project ID (optional)
|
|
369
|
+
isSkipDevices?: boolean, // true = Customer mode, false = Agent mode (default)
|
|
370
|
+
});
|
|
511
371
|
```
|
|
512
|
-
#### 📌 iOS(Swift):
|
|
513
372
|
|
|
514
|
-
|
|
373
|
+
#### Agent Login (default)
|
|
515
374
|
|
|
516
|
-
|
|
375
|
+
For employees / call center agents who can make outbound calls to any phone number:
|
|
517
376
|
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
377
|
+
```typescript
|
|
378
|
+
await initCallWithUserPassword({
|
|
379
|
+
userName: '100',
|
|
380
|
+
password: 'sip_password',
|
|
381
|
+
realm: 'your_realm',
|
|
382
|
+
host: '',
|
|
383
|
+
isVideo: false,
|
|
384
|
+
fcmToken: fcmToken,
|
|
385
|
+
// isSkipDevices defaults to false — Agent mode
|
|
386
|
+
});
|
|
526
387
|
```
|
|
527
388
|
|
|
528
|
-
|
|
389
|
+
#### Customer Login
|
|
529
390
|
|
|
530
|
-
|
|
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
|
-
```
|
|
536
|
-
|
|
537
|
-
- ✅ Add these lines into `Info.plist`:
|
|
391
|
+
For end customers who can only call the business hotline — no outbound dialing to external telecom numbers, no assigned phone number:
|
|
538
392
|
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
393
|
+
```typescript
|
|
394
|
+
await initCallWithUserPassword({
|
|
395
|
+
userName: '200',
|
|
396
|
+
password: 'sip_password',
|
|
397
|
+
realm: 'your_realm',
|
|
398
|
+
host: '',
|
|
399
|
+
isVideo: false,
|
|
400
|
+
fcmToken: fcmToken,
|
|
401
|
+
isSkipDevices: true, // Customer mode — skip device registration
|
|
402
|
+
});
|
|
544
403
|
```
|
|
545
404
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
559
|
-
}
|
|
405
|
+
### Option 2: API Key
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
await initCallWithApiKey({
|
|
409
|
+
fullName: string, // Display name
|
|
410
|
+
usrUuid: string, // User UUID from OMICALL
|
|
411
|
+
apiKey: string, // API key from OMICALL dashboard
|
|
412
|
+
isVideo: boolean, // Enable video capability
|
|
413
|
+
phone: string, // Phone number
|
|
414
|
+
fcmToken: string, // Firebase token for push notifications
|
|
415
|
+
projectId?: string, // OMICALL project ID (optional)
|
|
416
|
+
});
|
|
560
417
|
```
|
|
561
418
|
|
|
562
|
-
|
|
419
|
+
---
|
|
563
420
|
|
|
564
|
-
|
|
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
|
-
-
|
|
421
|
+
## Call Flows
|
|
569
422
|
|
|
570
|
-
|
|
423
|
+
### Outgoing Call Flow
|
|
571
424
|
|
|
572
425
|
```
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
426
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
427
|
+
│ JS App │ │ Native │ │ SIP/PBX │
|
|
428
|
+
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
|
429
|
+
│ │ │
|
|
430
|
+
│ startCall() │ │
|
|
431
|
+
├────────────────────►│ │
|
|
432
|
+
│ │ SIP INVITE │
|
|
433
|
+
│ ├────────────────────►│
|
|
434
|
+
│ │ │
|
|
435
|
+
│ calling (1) │ 180 Ringing │
|
|
436
|
+
│◄────────────────────┤◄────────────────────┤
|
|
437
|
+
│ │ │
|
|
438
|
+
│ early (3) │ 183 Progress │
|
|
439
|
+
│◄────────────────────┤◄────────────────────┤
|
|
440
|
+
│ │ │
|
|
441
|
+
│ connecting (4) │ 200 OK │
|
|
442
|
+
│◄────────────────────┤◄────────────────────┤
|
|
443
|
+
│ │ │
|
|
444
|
+
│ confirmed (5) │ ACK │
|
|
445
|
+
│◄────────────────────┤────────────────────►│
|
|
446
|
+
│ │ │
|
|
447
|
+
│ ══════ Active Call (RTP audio/video) ══════
|
|
448
|
+
│ │ │
|
|
449
|
+
│ endCall() │ │
|
|
450
|
+
├────────────────────►│ BYE │
|
|
451
|
+
│ ├────────────────────►│
|
|
452
|
+
│ disconnected (6) │ 200 OK │
|
|
453
|
+
│◄────────────────────┤◄────────────────────┤
|
|
454
|
+
│ │ │
|
|
577
455
|
```
|
|
578
456
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
### 🚀 Request permission
|
|
582
|
-
|
|
583
|
-
**📌 We need you request permission about call before make call:**
|
|
584
|
-
|
|
585
|
-
- ✅ You can use <a href="https://github.com/zoontek/react-native-permissions">react-native-permissions</a> to do this
|
|
457
|
+
### Incoming Call — App in Foreground
|
|
586
458
|
|
|
587
459
|
```
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
460
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
461
|
+
│ JS App │ │ Native │ │ SIP/PBX │
|
|
462
|
+
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
|
463
|
+
│ │ │
|
|
464
|
+
│ │ SIP INVITE │
|
|
465
|
+
│ │◄────────────────────┤
|
|
466
|
+
│ │ 180 Ringing │
|
|
467
|
+
│ ├────────────────────►│
|
|
468
|
+
│ │ │
|
|
469
|
+
│ incoming (2) │ │
|
|
470
|
+
│◄────────────────────┤ (event emitted) │
|
|
471
|
+
│ │ │
|
|
472
|
+
│ ┌──────────────┐ │ │
|
|
473
|
+
│ │ Show Call UI │ │ │
|
|
474
|
+
│ │ [Accept][Deny]│ │ │
|
|
475
|
+
│ └──────────────┘ │ │
|
|
476
|
+
│ │ │
|
|
477
|
+
│ joinCall() │ │
|
|
478
|
+
├────────────────────►│ 200 OK │
|
|
479
|
+
│ ├────────────────────►│
|
|
480
|
+
│ confirmed (5) │ ACK │
|
|
481
|
+
│◄────────────────────┤◄────────────────────┤
|
|
482
|
+
│ │ │
|
|
483
|
+
│ ══════ Active Call (RTP audio/video) ══════
|
|
484
|
+
│ │ │
|
|
598
485
|
```
|
|
599
486
|
|
|
600
|
-
###
|
|
601
|
-
|
|
602
|
-
**📌 For Android (SDK), additional permissions are required:**
|
|
603
|
-
|
|
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().
|
|
609
|
-
|
|
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
|
-
/>
|
|
487
|
+
### Incoming Call — App in Background / Killed
|
|
627
488
|
|
|
489
|
+
```
|
|
490
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
491
|
+
│ JS App │ │ Native │ │Push Svc │ │ SIP/PBX │
|
|
492
|
+
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
|
|
493
|
+
│ │ │ │
|
|
494
|
+
│ │ │ Push Notify │
|
|
495
|
+
│ │ │◄──────────────┤
|
|
496
|
+
│ │ │ │
|
|
497
|
+
|
|
498
|
+
┌───────────────── iOS (VoIP Push) ──────────────────┐
|
|
499
|
+
│ │ │ │ │ │
|
|
500
|
+
│ │ │ PushKit VoIP │ │ │
|
|
501
|
+
│ │ │◄──────────────┤ │ │
|
|
502
|
+
│ │ │ │ │ │
|
|
503
|
+
│ │ │ Show CallKit │ │ │
|
|
504
|
+
│ │ │ ┌──────────┐ │ │ │
|
|
505
|
+
│ │ │ │ System │ │ │ │
|
|
506
|
+
│ │ │ │ Call UI │ │ │ │
|
|
507
|
+
│ │ │ │[Slide ►] │ │ │ │
|
|
508
|
+
│ │ │ └──────────┘ │ │ │
|
|
509
|
+
│ │ │ │ │ │
|
|
510
|
+
│ │ App launched │ │ │ │
|
|
511
|
+
│ │◄──────────────┤ │ │ │
|
|
512
|
+
│ │ incoming (2) │ │ │ │
|
|
513
|
+
│ │◄──────────────┤ │ │ │
|
|
514
|
+
└───────────────────────────────────────────────────────┘
|
|
515
|
+
|
|
516
|
+
┌───────────────── Android (FCM) ────────────────────┐
|
|
517
|
+
│ │ │ │ │ │
|
|
518
|
+
│ │ │ FCM Message │ │ │
|
|
519
|
+
│ │ │◄──────────────┤ │ │
|
|
520
|
+
│ │ │ │ │ │
|
|
521
|
+
│ │ │ Start Foreground Service │ │
|
|
522
|
+
│ │ │ ┌──────────────────────┐ │ │
|
|
523
|
+
│ │ │ │ Full-screen Notif │ │ │
|
|
524
|
+
│ │ │ │ [Accept] [Decline] │ │ │
|
|
525
|
+
│ │ │ └──────────────────────┘ │ │
|
|
526
|
+
│ │ │ │ │ │
|
|
527
|
+
│ │ App launched │ │ │ │
|
|
528
|
+
│ │◄──────────────┤ │ │ │
|
|
529
|
+
│ │ incoming (2) │ │ │ │
|
|
530
|
+
│ │◄──────────────┤ │ │ │
|
|
531
|
+
└───────────────────────────────────────────────────────┘
|
|
532
|
+
|
|
533
|
+
│ │ │ │
|
|
534
|
+
│ joinCall() │ │ │
|
|
535
|
+
├──────────────►│ 200 OK │ │
|
|
536
|
+
│ ├──────────────────────────────►│
|
|
537
|
+
│ confirmed (5)│ │ │
|
|
538
|
+
│◄──────────────┤ │ │
|
|
539
|
+
│ │ │ │
|
|
628
540
|
```
|
|
629
541
|
|
|
630
|
-
|
|
542
|
+
### Missed Call Flow
|
|
631
543
|
|
|
632
544
|
```
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
545
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
546
|
+
│ JS App │ │ Native │ │ SIP/PBX │
|
|
547
|
+
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
|
548
|
+
│ │ │
|
|
549
|
+
│ │ SIP INVITE │
|
|
550
|
+
│ │◄────────────────────┤
|
|
551
|
+
│ incoming (2) │ │
|
|
552
|
+
│◄────────────────────┤ │
|
|
553
|
+
│ │ │
|
|
554
|
+
│ (user ignores / timeout / caller hangs up)
|
|
555
|
+
│ │ │
|
|
556
|
+
│ │ CANCEL │
|
|
557
|
+
│ │◄────────────────────┤
|
|
558
|
+
│ disconnected (6) │ 200 OK │
|
|
559
|
+
│◄────────────────────┤────────────────────►│
|
|
560
|
+
│ │ │
|
|
561
|
+
│ │ Show Missed Call │
|
|
562
|
+
│ │ Notification │
|
|
563
|
+
│ │ │
|
|
564
|
+
│ (user taps notif) │ │
|
|
565
|
+
│ │ │
|
|
566
|
+
│ onClickMissedCall │ │
|
|
567
|
+
│◄────────────────────┤ │
|
|
568
|
+
│ │ │
|
|
636
569
|
```
|
|
637
|
-
#### 🚀 OMIKIT-Plugin functions 🚀
|
|
638
|
-
<br>
|
|
639
|
-
|
|
640
|
-
📌 **startServices()**
|
|
641
|
-
|
|
642
|
-
✅ Description:
|
|
643
|
-
|
|
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';
|
|
651
570
|
|
|
652
|
-
|
|
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.
|
|
571
|
+
### Call Transfer Flow
|
|
658
572
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
573
|
+
```
|
|
574
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
575
|
+
│ JS App │ │ Native │ │ SIP/PBX │
|
|
576
|
+
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
|
577
|
+
│ │ │
|
|
578
|
+
│ ══════ Active Call with Party A ══════
|
|
579
|
+
│ │ │
|
|
580
|
+
│ transferCall(B) │ │
|
|
581
|
+
├────────────────────►│ SIP REFER → B │
|
|
582
|
+
│ ├────────────────────►│
|
|
583
|
+
│ │ │
|
|
584
|
+
│ │ 202 Accepted │
|
|
585
|
+
│ │◄────────────────────┤
|
|
586
|
+
│ │ │
|
|
587
|
+
│ disconnected (6) │ BYE (from A) │
|
|
588
|
+
│◄────────────────────┤◄────────────────────┤
|
|
589
|
+
│ │ │
|
|
590
|
+
│ ══════ Party A now talks to B ══════
|
|
591
|
+
│ │ │
|
|
592
|
+
```
|
|
663
593
|
|
|
664
|
-
|
|
665
|
-
This means the user must grant these permissions before initiating a call or any service that relies on them.
|
|
594
|
+
### Reject / Drop Call Flow
|
|
666
595
|
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
596
|
+
```
|
|
597
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
598
|
+
│ JS App │ │ Native │ │ SIP/PBX │
|
|
599
|
+
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
|
600
|
+
│ │ │
|
|
601
|
+
│ incoming (2) │ SIP INVITE │
|
|
602
|
+
│◄────────────────────┤◄────────────────────┤
|
|
603
|
+
│ │ │
|
|
604
|
+
┌── rejectCall() ──┐ │
|
|
605
|
+
│ Decline this │ 486 Busy Here │
|
|
606
|
+
│ device only ├─────────────────────────►│
|
|
607
|
+
└───────────────────┘ (other devices ring) │
|
|
608
|
+
│ │ │
|
|
609
|
+
┌── dropCall() ────┐ │
|
|
610
|
+
│ Decline + stop │ 603 Decline │
|
|
611
|
+
│ ALL devices ├─────────────────────────►│
|
|
612
|
+
└───────────────────┘ (PBX stops all ringing) │
|
|
613
|
+
│ │ │
|
|
614
|
+
```
|
|
676
615
|
|
|
677
|
-
|
|
678
|
-
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## API Reference
|
|
619
|
+
|
|
620
|
+
### Service & Auth
|
|
621
|
+
|
|
622
|
+
| Function | Returns | Description |
|
|
623
|
+
|----------|---------|-------------|
|
|
624
|
+
| `startServices()` | `Promise<boolean>` | Initialize SDK. Call once on app launch |
|
|
625
|
+
| `initCallWithUserPassword(data)` | `Promise<boolean>` | Login with SIP username/password |
|
|
626
|
+
| `initCallWithApiKey(data)` | `Promise<boolean>` | Login with API key |
|
|
627
|
+
| `logout()` | `Promise<boolean>` | Logout and unregister SIP |
|
|
628
|
+
|
|
629
|
+
### Call Control
|
|
630
|
+
|
|
631
|
+
| Function | Returns | Description |
|
|
632
|
+
|----------|---------|-------------|
|
|
633
|
+
| `startCall({ phoneNumber, isVideo })` | `Promise<{ status, message, _id }>` | Initiate outgoing call |
|
|
634
|
+
| `startCallWithUuid({ usrUuid, isVideo })` | `Promise<boolean>` | Call by user UUID |
|
|
635
|
+
| `joinCall()` | `Promise<any>` | Accept incoming call |
|
|
636
|
+
| `endCall()` | `Promise<any>` | End active call (sends SIP BYE) |
|
|
637
|
+
| `rejectCall()` | `Promise<boolean>` | Reject on this device only (486) |
|
|
638
|
+
| `dropCall()` | `Promise<boolean>` | Reject + stop ringing on ALL devices (603) |
|
|
639
|
+
| `transferCall({ phoneNumber })` | `Promise<boolean>` | Blind transfer to another number |
|
|
640
|
+
| `getInitialCall()` | `Promise<any>` | Get pending call data on cold start |
|
|
641
|
+
|
|
642
|
+
### Media Control
|
|
643
|
+
|
|
644
|
+
| Function | Returns | Description |
|
|
645
|
+
|----------|---------|-------------|
|
|
646
|
+
| `toggleMute()` | `Promise<boolean\|null>` | Toggle microphone mute |
|
|
647
|
+
| `toggleSpeaker()` | `Promise<boolean>` | Toggle speakerphone |
|
|
648
|
+
| `toggleHold()` | `Promise<void>` | Toggle call hold |
|
|
649
|
+
| `onHold({ holdStatus })` | `Promise<boolean>` | Set hold state explicitly |
|
|
650
|
+
| `sendDTMF({ character })` | `Promise<boolean>` | Send DTMF tone (0-9, *, #) |
|
|
651
|
+
| `getAudio()` | `Promise<any>` | List available audio devices |
|
|
652
|
+
| `setAudio({ portType })` | `Promise<void>` | Set audio output device |
|
|
653
|
+
| `getCurrentAudio()` | `Promise<any>` | Get current audio device |
|
|
654
|
+
|
|
655
|
+
### Video Control
|
|
656
|
+
|
|
657
|
+
| Function | Returns | Description |
|
|
658
|
+
|----------|---------|-------------|
|
|
659
|
+
| `toggleOmiVideo()` | `Promise<boolean>` | Toggle video stream on/off |
|
|
660
|
+
| `switchOmiCamera()` | `Promise<boolean>` | Switch front/back camera |
|
|
661
|
+
| `registerVideoEvent()` | `Promise<boolean>` | Start receiving remote video frames |
|
|
662
|
+
| `removeVideoEvent()` | `Promise<boolean>` | Stop receiving remote video frames |
|
|
663
|
+
|
|
664
|
+
### User & Info
|
|
665
|
+
|
|
666
|
+
| Function | Returns | Description |
|
|
667
|
+
|----------|---------|-------------|
|
|
668
|
+
| `getCurrentUser()` | `Promise<any>` | Get logged-in user details |
|
|
669
|
+
| `getGuestUser()` | `Promise<any>` | Get guest/remote user details |
|
|
670
|
+
| `getUserInfo(phone)` | `Promise<any>` | Look up user by phone number |
|
|
671
|
+
|
|
672
|
+
### Getter Functions (v4.0.1+)
|
|
673
|
+
|
|
674
|
+
| Function | Returns | Description |
|
|
675
|
+
|----------|---------|-------------|
|
|
676
|
+
| `getProjectId()` | `Promise<string\|null>` | Current project ID |
|
|
677
|
+
| `getAppId()` | `Promise<string\|null>` | Current app ID |
|
|
678
|
+
| `getDeviceId()` | `Promise<string\|null>` | Current device ID |
|
|
679
|
+
| `getFcmToken()` | `Promise<string\|null>` | FCM push token |
|
|
680
|
+
| `getSipInfo()` | `Promise<string\|null>` | SIP info (`user@realm`) |
|
|
681
|
+
| `getVoipToken()` | `Promise<string\|null>` | VoIP token (iOS only) |
|
|
682
|
+
|
|
683
|
+
### Notification Control
|
|
684
|
+
|
|
685
|
+
| Function | Returns | Description |
|
|
686
|
+
|----------|---------|-------------|
|
|
687
|
+
| `configPushNotification(data)` | `Promise<any>` | Configure push notification settings |
|
|
688
|
+
| `hideSystemNotificationSafely()` | `Promise<boolean>` | Hide notification without unregistering |
|
|
689
|
+
| `hideSystemNotificationOnly()` | `Promise<boolean>` | Hide notification only |
|
|
690
|
+
| `hideSystemNotificationAndUnregister(reason)` | `Promise<boolean>` | Hide + unregister with reason |
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
## Events
|
|
695
|
+
|
|
696
|
+
Use `omiEmitter` to listen for events emitted by the native SDK.
|
|
697
|
+
|
|
698
|
+
```typescript
|
|
699
|
+
import { omiEmitter, OmiCallEvent } from 'omikit-plugin';
|
|
700
|
+
```
|
|
679
701
|
|
|
680
|
-
|
|
702
|
+
### Event Reference
|
|
681
703
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
704
|
+
| Event | Payload | Description |
|
|
705
|
+
|-------|---------|-------------|
|
|
706
|
+
| `onCallStateChanged` | `{ status, callerNumber, isVideo, incoming, codeEndCall }` | Call lifecycle changes |
|
|
707
|
+
| `onMuted` | `boolean` | Microphone mute toggled |
|
|
708
|
+
| `onSpeaker` | `boolean` | Speaker toggled |
|
|
709
|
+
| `onHold` | `boolean` | Hold state changed |
|
|
710
|
+
| `onRemoteVideoReady` | — | Remote video stream is ready |
|
|
711
|
+
| `onClickMissedCall` | `{ callerNumber }` | User tapped missed call notification |
|
|
712
|
+
| `onSwitchboardAnswer` | `{ data }` | Switchboard answered |
|
|
713
|
+
| `onCallQuality` | `{ quality, stat }` | Call quality metrics (see [Quality & Diagnostics](#quality--diagnostics)) |
|
|
714
|
+
| `onAudioChange` | `{ data }` | Audio device changed |
|
|
715
|
+
| `onRequestPermissionAndroid` | `{ permissions }` | Permission request needed (Android only) |
|
|
686
716
|
|
|
687
|
-
|
|
688
|
-
permissions.push(PERMISSIONS.ANDROID.RECORD_AUDIO);
|
|
717
|
+
### Usage Example
|
|
689
718
|
|
|
690
|
-
|
|
719
|
+
```typescript
|
|
720
|
+
import { omiEmitter, OmiCallEvent, OmiCallState } from 'omikit-plugin';
|
|
691
721
|
|
|
692
|
-
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
|
|
722
|
+
useEffect(() => {
|
|
723
|
+
const subscriptions = [
|
|
724
|
+
// Call state changes
|
|
725
|
+
omiEmitter.addListener(OmiCallEvent.onCallStateChanged, (data) => {
|
|
726
|
+
console.log('State:', data.status, 'Caller:', data.callerNumber);
|
|
696
727
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
728
|
+
if (data.status === OmiCallState.incoming) {
|
|
729
|
+
// Navigate to incoming call screen
|
|
730
|
+
}
|
|
731
|
+
if (data.status === OmiCallState.confirmed) {
|
|
732
|
+
// Call connected
|
|
733
|
+
}
|
|
734
|
+
if (data.status === OmiCallState.disconnected) {
|
|
735
|
+
// Call ended, check data.codeEndCall for reason
|
|
736
|
+
}
|
|
737
|
+
}),
|
|
738
|
+
|
|
739
|
+
// Mute state
|
|
740
|
+
omiEmitter.addListener(OmiCallEvent.onMuted, (isMuted) => {
|
|
741
|
+
setMuted(isMuted);
|
|
742
|
+
}),
|
|
743
|
+
|
|
744
|
+
// Speaker state
|
|
745
|
+
omiEmitter.addListener(OmiCallEvent.onSpeaker, (isOn) => {
|
|
746
|
+
setSpeaker(isOn);
|
|
747
|
+
}),
|
|
748
|
+
|
|
749
|
+
// Missed call notification tapped
|
|
750
|
+
omiEmitter.addListener(OmiCallEvent.onClickMissedCall, (data) => {
|
|
751
|
+
// Navigate to call history or callback
|
|
752
|
+
}),
|
|
753
|
+
|
|
754
|
+
// Call quality & diagnostics
|
|
755
|
+
omiEmitter.addListener(OmiCallEvent.onCallQuality, ({ quality, stat }) => {
|
|
756
|
+
console.log('Quality level:', quality); // 0=Good, 1=Medium, 2=Bad
|
|
757
|
+
if (stat) {
|
|
758
|
+
console.log('MOS:', stat.mos, 'Jitter:', stat.jitter, 'Latency:', stat.latency);
|
|
759
|
+
}
|
|
760
|
+
}),
|
|
761
|
+
];
|
|
701
762
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
763
|
+
return () => subscriptions.forEach(sub => sub.remove());
|
|
764
|
+
}, []);
|
|
705
765
|
```
|
|
706
766
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
767
|
+
---
|
|
768
|
+
|
|
769
|
+
## Enums
|
|
770
|
+
|
|
771
|
+
### OmiCallState
|
|
772
|
+
|
|
773
|
+
| Value | Name | Description |
|
|
774
|
+
|-------|------|-------------|
|
|
775
|
+
| 0 | `unknown` | Initial/unknown state |
|
|
776
|
+
| 1 | `calling` | Outgoing call initiated, waiting for response |
|
|
777
|
+
| 2 | `incoming` | Incoming call received |
|
|
778
|
+
| 3 | `early` | Early media (183 Session Progress) |
|
|
779
|
+
| 4 | `connecting` | 200 OK received, establishing media |
|
|
780
|
+
| 5 | `confirmed` | Call active, RTP media flowing |
|
|
781
|
+
| 6 | `disconnected` | Call ended |
|
|
782
|
+
| 7 | `hold` | Call on hold |
|
|
783
|
+
|
|
784
|
+
### OmiStartCallStatus
|
|
785
|
+
|
|
786
|
+
| Value | Name | Description |
|
|
787
|
+
|-------|------|-------------|
|
|
788
|
+
| 0 | `invalidUuid` | Invalid user UUID |
|
|
789
|
+
| 1 | `invalidPhoneNumber` | Invalid phone number format |
|
|
790
|
+
| 2 | `samePhoneNumber` | Calling your own number |
|
|
791
|
+
| 3 | `maxRetry` | Max retry attempts exceeded |
|
|
792
|
+
| 4 | `permissionDenied` | General permission denied |
|
|
793
|
+
| 450 | `permissionMicrophone` | Microphone permission needed |
|
|
794
|
+
| 451 | `permissionCamera` | Camera permission needed |
|
|
795
|
+
| 452 | `permissionOverlay` | Overlay permission needed |
|
|
796
|
+
| 5 | `couldNotFindEndpoint` | SIP endpoint not found |
|
|
797
|
+
| 6 | `accountRegisterFailed` | SIP registration failed |
|
|
798
|
+
| 7 | `startCallFailed` | Call initiation failed |
|
|
799
|
+
| 8 | `startCallSuccess` | Call started successfully (Android) |
|
|
800
|
+
| 407 | `startCallSuccessIOS` | Call started successfully (iOS) |
|
|
801
|
+
| 9 | `haveAnotherCall` | Another call is in progress |
|
|
802
|
+
| 10 | `accountTurnOffNumberInternal` | Internal number has been deactivated |
|
|
803
|
+
| 11 | `noNetwork` | No network connection available |
|
|
804
|
+
|
|
805
|
+
### OmiAudioType
|
|
806
|
+
|
|
807
|
+
| Value | Name | Description |
|
|
808
|
+
|-------|------|-------------|
|
|
809
|
+
| 0 | `receiver` | Phone earpiece |
|
|
810
|
+
| 1 | `speaker` | Speakerphone |
|
|
811
|
+
| 2 | `bluetooth` | Bluetooth device |
|
|
812
|
+
| 3 | `headphones` | Wired headphones |
|
|
813
|
+
|
|
814
|
+
### End Call Status Codes (`codeEndCall`)
|
|
815
|
+
|
|
816
|
+
When a call ends (state = `disconnected`), the `codeEndCall` field in the `onCallStateChanged` event payload contains the status code indicating why the call ended.
|
|
817
|
+
|
|
818
|
+
#### Standard SIP Codes
|
|
819
|
+
|
|
820
|
+
| Code | Description |
|
|
821
|
+
|------|-------------|
|
|
822
|
+
| 200 | Normal call ending |
|
|
823
|
+
| 408 | Call timeout — no answer |
|
|
824
|
+
| 480 | Temporarily unavailable |
|
|
825
|
+
| 486 | Busy (or call rejected via `rejectCall()`) |
|
|
826
|
+
| 487 | Call cancelled before being answered |
|
|
827
|
+
| 500 | Server error |
|
|
828
|
+
| 503 | Server unavailable |
|
|
829
|
+
|
|
830
|
+
#### OMICALL Extended Codes
|
|
831
|
+
|
|
832
|
+
| Code | Description |
|
|
833
|
+
|------|-------------|
|
|
834
|
+
| 600 | Call declined |
|
|
835
|
+
| 601 | Call ended by customer |
|
|
836
|
+
| 602 | Call answered / ended by another agent |
|
|
837
|
+
| 603 | Call declined (via `dropCall()` — stops ringing on ALL devices) |
|
|
838
|
+
|
|
839
|
+
#### Business Rule Codes (PBX)
|
|
840
|
+
|
|
841
|
+
| Code | Description |
|
|
842
|
+
|------|-------------|
|
|
843
|
+
| 850 | Exceeded concurrent call limit |
|
|
844
|
+
| 851 | Exceeded call limit |
|
|
845
|
+
| 852 | No service plan assigned — contact provider |
|
|
846
|
+
| 853 | Internal number has been deactivated |
|
|
847
|
+
| 854 | Number is in DNC (Do Not Call) list |
|
|
848
|
+
| 855 | Exceeded call limit for trial plan |
|
|
849
|
+
| 856 | Exceeded minute limit for trial plan |
|
|
850
|
+
| 857 | Number blocked in configuration |
|
|
851
|
+
| 858 | Unknown or unconfigured number prefix |
|
|
852
|
+
| 859 | No available number for Viettel direction — contact provider |
|
|
853
|
+
| 860 | No available number for Vinaphone direction — contact provider |
|
|
854
|
+
| 861 | No available number for Mobifone direction — contact provider |
|
|
855
|
+
| 862 | Number prefix temporarily locked for Viettel |
|
|
856
|
+
| 863 | Number prefix temporarily locked for Vinaphone |
|
|
857
|
+
| 864 | Number prefix temporarily locked for Mobifone |
|
|
858
|
+
| 865 | Advertising call outside allowed time window — try again later |
|
|
859
|
+
|
|
860
|
+
**Common scenarios:**
|
|
716
861
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
862
|
+
```
|
|
863
|
+
User hangs up normally → 200
|
|
864
|
+
Caller cancels before answer → 487
|
|
865
|
+
Callee rejects (this device) → 486 (rejectCall)
|
|
866
|
+
Callee rejects (all devices) → 603 (dropCall)
|
|
867
|
+
Callee busy on another call → 486
|
|
868
|
+
No answer / timeout → 408 or 480
|
|
869
|
+
Answered by another agent → 602
|
|
870
|
+
Exceeded concurrent call limit → 850
|
|
871
|
+
Number in DNC list → 854
|
|
721
872
|
```
|
|
722
873
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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 🚀 🚀
|
|
874
|
+
**Usage:**
|
|
875
|
+
|
|
876
|
+
```typescript
|
|
877
|
+
omiEmitter.addListener(OmiCallEvent.onCallStateChanged, (data) => {
|
|
878
|
+
if (data.status === OmiCallState.disconnected) {
|
|
879
|
+
const code = data.codeEndCall;
|
|
880
|
+
|
|
881
|
+
if (code === 200) {
|
|
882
|
+
console.log('Call ended normally');
|
|
883
|
+
} else if (code === 487) {
|
|
884
|
+
console.log('Call was cancelled');
|
|
885
|
+
} else if (code === 602) {
|
|
886
|
+
console.log('Call was answered by another agent');
|
|
887
|
+
} else if (code >= 850 && code <= 865) {
|
|
888
|
+
console.log('Business rule error:', code);
|
|
889
|
+
// Show user-friendly message based on code
|
|
890
|
+
} else {
|
|
891
|
+
console.log('Call ended with code:', code);
|
|
892
|
+
}
|
|
768
893
|
}
|
|
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
894
|
});
|
|
895
|
+
```
|
|
875
896
|
|
|
876
|
-
|
|
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
|
|
904
|
-
|
|
905
|
-
```javascript
|
|
906
|
-
import { startCall } from 'omikit-plugin';
|
|
897
|
+
---
|
|
907
898
|
|
|
908
|
-
|
|
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
|
-
```
|
|
899
|
+
## Video Calls
|
|
914
900
|
|
|
915
|
-
|
|
901
|
+
### Setup
|
|
916
902
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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
|
-
```
|
|
903
|
+
```typescript
|
|
904
|
+
import { OmiLocalCamera, OmiRemoteCamera } from 'omikit-plugin';
|
|
905
|
+
```
|
|
924
906
|
|
|
925
|
-
|
|
907
|
+
### Video Components
|
|
926
908
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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 |
|
|
909
|
+
```tsx
|
|
910
|
+
// Local camera preview (your camera)
|
|
911
|
+
<OmiLocalCamera style={{ width: 120, height: 160 }} />
|
|
940
912
|
|
|
941
|
-
|
|
913
|
+
// Remote camera view (other party's video)
|
|
914
|
+
<OmiRemoteCamera style={{ width: '100%', height: '100%' }} />
|
|
915
|
+
```
|
|
942
916
|
|
|
917
|
+
### Video Call Flow
|
|
943
918
|
|
|
944
|
-
|
|
919
|
+
```typescript
|
|
920
|
+
// 1. Register for video events BEFORE starting call
|
|
921
|
+
await registerVideoEvent();
|
|
945
922
|
|
|
946
|
-
|
|
923
|
+
// 2. Start video call
|
|
924
|
+
await startCall({ phoneNumber: '0901234567', isVideo: true });
|
|
947
925
|
|
|
948
|
-
|
|
949
|
-
|
|
926
|
+
// 3. Toggle video during call
|
|
927
|
+
await toggleOmiVideo(); // on/off video stream
|
|
928
|
+
await switchOmiCamera(); // front/back camera
|
|
950
929
|
|
|
951
|
-
//
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
isVideo: false // Set to true for a video call, false for an audio call
|
|
930
|
+
// 4. Listen for remote video ready
|
|
931
|
+
omiEmitter.addListener(OmiCallEvent.onRemoteVideoReady, () => {
|
|
932
|
+
// Remote video is now available - show OmiRemoteCamera
|
|
955
933
|
});
|
|
956
934
|
|
|
957
|
-
//
|
|
935
|
+
// 5. Cleanup when call ends
|
|
936
|
+
await removeVideoEvent();
|
|
958
937
|
```
|
|
959
938
|
|
|
960
|
-
|
|
939
|
+
---
|
|
961
940
|
|
|
962
|
-
|
|
941
|
+
## Push Notifications
|
|
963
942
|
|
|
964
|
-
|
|
943
|
+
### Configuration
|
|
965
944
|
|
|
966
|
-
```
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
945
|
+
```typescript
|
|
946
|
+
// Call after startServices(), before or after login
|
|
947
|
+
await configPushNotification({
|
|
948
|
+
// Your platform-specific push configuration
|
|
949
|
+
});
|
|
970
950
|
```
|
|
971
951
|
|
|
972
|
-
|
|
952
|
+
### How Push Works
|
|
973
953
|
|
|
954
|
+
#### iOS — VoIP Push (PushKit)
|
|
974
955
|
|
|
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
956
|
```
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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
|
-
}
|
|
957
|
+
PBX ──► APNS ──► PushKit ──► App wakes up
|
|
958
|
+
├── Report to CallKit
|
|
959
|
+
├── Show system call UI
|
|
960
|
+
└── Register SIP & connect
|
|
1007
961
|
```
|
|
1008
962
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
963
|
+
- Uses PushKit VoIP push for reliable delivery even when app is killed
|
|
964
|
+
- CallKit provides native system call UI (slide to answer)
|
|
965
|
+
- No user-visible notification — CallKit handles the UI
|
|
1012
966
|
|
|
1013
|
-
|
|
1014
|
-
import {dropCall} from 'omikit-plugin';
|
|
967
|
+
#### Android — FCM
|
|
1015
968
|
|
|
1016
|
-
const value = await dropCall(); // return true/false
|
|
1017
|
-
|
|
1018
969
|
```
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
```javascript
|
|
1025
|
-
import {toggleMute} from 'omikit-plugin';
|
|
1026
|
-
|
|
1027
|
-
toggleMute();
|
|
970
|
+
PBX ──► FCM ──► App receives data message
|
|
971
|
+
├── Start foreground service
|
|
972
|
+
├── Show full-screen notification
|
|
973
|
+
└── Register SIP & connect
|
|
1028
974
|
```
|
|
1029
975
|
|
|
1030
|
-
|
|
976
|
+
- Uses FCM data message (not notification message)
|
|
977
|
+
- Foreground service keeps the app alive during the call
|
|
978
|
+
- Full-screen intent for lock screen call UI
|
|
1031
979
|
|
|
1032
|
-
|
|
980
|
+
### Notification Management
|
|
1033
981
|
|
|
1034
|
-
```
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
toggleSpeaker();
|
|
1038
|
-
```
|
|
982
|
+
```typescript
|
|
983
|
+
// Hide the system notification without affecting SIP registration
|
|
984
|
+
await hideSystemNotificationSafely();
|
|
1039
985
|
|
|
1040
|
-
|
|
986
|
+
// Hide notification only
|
|
987
|
+
await hideSystemNotificationOnly();
|
|
1041
988
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
```javascript
|
|
1045
|
-
import {toggleHold} from 'omikit-plugin';
|
|
1046
|
-
|
|
1047
|
-
toggleHold();
|
|
989
|
+
// Hide notification and unregister SIP (with reason for analytics)
|
|
990
|
+
await hideSystemNotificationAndUnregister('user_dismissed');
|
|
1048
991
|
```
|
|
1049
992
|
|
|
1050
|
-
|
|
993
|
+
---
|
|
1051
994
|
|
|
1052
|
-
|
|
995
|
+
## Permissions (Android)
|
|
1053
996
|
|
|
1054
|
-
|
|
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';
|
|
997
|
+
Android 15+ requires explicit runtime permissions for VoIP functionality.
|
|
1057
998
|
|
|
1058
|
-
|
|
1059
|
-
character: text,
|
|
1060
|
-
});
|
|
1061
|
-
```
|
|
999
|
+
### Quick Setup
|
|
1062
1000
|
|
|
1063
|
-
|
|
1001
|
+
```typescript
|
|
1002
|
+
import {
|
|
1003
|
+
checkAndRequestPermissions,
|
|
1004
|
+
checkPermissionStatus,
|
|
1005
|
+
requestPermissionsByCodes,
|
|
1006
|
+
requestSystemAlertWindowPermission,
|
|
1007
|
+
openSystemAlertSetting,
|
|
1008
|
+
} from 'omikit-plugin';
|
|
1064
1009
|
|
|
1065
|
-
|
|
1010
|
+
// Check and request all permissions at once
|
|
1011
|
+
const granted = await checkAndRequestPermissions(isVideo);
|
|
1066
1012
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1013
|
+
// Check current permission status
|
|
1014
|
+
const status = await checkPermissionStatus();
|
|
1015
|
+
// Returns: { microphone: boolean, camera: boolean, overlay: boolean, ... }
|
|
1070
1016
|
```
|
|
1071
1017
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
```
|
|
1075
|
-
{
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1018
|
+
### Handle Permission Errors from startCall
|
|
1019
|
+
|
|
1020
|
+
```typescript
|
|
1021
|
+
const result = await startCall({ phoneNumber, isVideo: false });
|
|
1022
|
+
|
|
1023
|
+
switch (result.status) {
|
|
1024
|
+
case 450: // OmiStartCallStatus.permissionMicrophone
|
|
1025
|
+
await requestPermissionsByCodes([450]);
|
|
1026
|
+
break;
|
|
1027
|
+
case 451: // OmiStartCallStatus.permissionCamera
|
|
1028
|
+
await requestPermissionsByCodes([451]);
|
|
1029
|
+
break;
|
|
1030
|
+
case 452: // OmiStartCallStatus.permissionOverlay
|
|
1031
|
+
await requestSystemAlertWindowPermission();
|
|
1032
|
+
// or open system settings directly:
|
|
1033
|
+
await openSystemAlertSetting();
|
|
1034
|
+
break;
|
|
1080
1035
|
}
|
|
1081
1036
|
```
|
|
1082
1037
|
|
|
1083
|
-
|
|
1038
|
+
### Permission Codes
|
|
1084
1039
|
|
|
1085
|
-
|
|
1040
|
+
| Code | Permission | Required for |
|
|
1041
|
+
|------|-----------|--------------|
|
|
1042
|
+
| 450 | Microphone | All calls |
|
|
1043
|
+
| 451 | Camera | Video calls |
|
|
1044
|
+
| 452 | Overlay (SYSTEM_ALERT_WINDOW) | Incoming call popup on lock screen |
|
|
1086
1045
|
|
|
1087
|
-
|
|
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
|
-
```
|
|
1046
|
+
> **Note:** On iOS, permissions are handled via `Info.plist` and system prompts. The above functions return `true` on iOS.
|
|
1102
1047
|
|
|
1103
|
-
|
|
1048
|
+
---
|
|
1104
1049
|
|
|
1105
|
-
|
|
1050
|
+
## Quality & Diagnostics
|
|
1106
1051
|
|
|
1107
|
-
|
|
1108
|
-
import {getUserInfo} from 'omikit-plugin';
|
|
1109
|
-
final user = await ggetUserInfo("111");
|
|
1110
|
-
```
|
|
1052
|
+
The `onCallQuality` event provides real-time call quality metrics during an active call.
|
|
1111
1053
|
|
|
1112
|
-
|
|
1054
|
+
### Event Payload
|
|
1113
1055
|
|
|
1114
|
-
```
|
|
1056
|
+
```typescript
|
|
1115
1057
|
{
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1058
|
+
quality: number; // 0 = Good, 1 = Medium, 2 = Bad
|
|
1059
|
+
stat: {
|
|
1060
|
+
mos: number; // Mean Opinion Score (1.0 – 5.0)
|
|
1061
|
+
jitter: number; // Jitter in milliseconds
|
|
1062
|
+
latency: number; // Round-trip latency in milliseconds
|
|
1063
|
+
packetLoss: number; // Packet loss percentage (0 – 100)
|
|
1064
|
+
lcn?: number; // Loss Connect Number (Android only)
|
|
1065
|
+
}
|
|
1122
1066
|
}
|
|
1123
1067
|
```
|
|
1124
|
-
|
|
1125
|
-
📌 **endCall()**
|
|
1126
1068
|
|
|
1127
|
-
|
|
1069
|
+
### MOS Score Thresholds
|
|
1128
1070
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1071
|
+
| MOS Range | Quality | Description |
|
|
1072
|
+
|-----------|---------|-------------|
|
|
1073
|
+
| ≥ 4.0 | Excellent | Clear, no perceptible issues |
|
|
1074
|
+
| 3.5 – 4.0 | Good | Minor impairments, generally clear |
|
|
1075
|
+
| 3.0 – 3.5 | Fair | Noticeable degradation |
|
|
1076
|
+
| 2.0 – 3.0 | Poor | Significant degradation |
|
|
1077
|
+
| < 2.0 | Bad | Nearly unusable |
|
|
1133
1078
|
|
|
1079
|
+
### Network Freeze Detection
|
|
1134
1080
|
|
|
1135
|
-
|
|
1081
|
+
When `lcn` (Loss Connect Number) increases consecutively, it indicates potential network freeze — useful for showing a "weak network" warning in the UI.
|
|
1136
1082
|
|
|
1137
|
-
|
|
1083
|
+
### Usage Example
|
|
1138
1084
|
|
|
1139
|
-
|
|
1085
|
+
```typescript
|
|
1086
|
+
import { omiEmitter, OmiCallEvent } from 'omikit-plugin';
|
|
1140
1087
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1088
|
+
omiEmitter.addListener(OmiCallEvent.onCallQuality, ({ quality, stat }) => {
|
|
1089
|
+
// quality: 0 = Good, 1 = Medium, 2 = Bad
|
|
1090
|
+
if (quality >= 2 && stat) {
|
|
1091
|
+
console.warn(
|
|
1092
|
+
`Poor call quality — MOS: ${stat.mos}, Jitter: ${stat.jitter}ms, ` +
|
|
1093
|
+
`Latency: ${stat.latency}ms, Loss: ${stat.packetLoss}%`
|
|
1094
|
+
);
|
|
1095
|
+
// Show weak network warning to user
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1144
1098
|
```
|
|
1145
1099
|
|
|
1100
|
+
---
|
|
1146
1101
|
|
|
1147
|
-
|
|
1102
|
+
## Advanced Features
|
|
1148
1103
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
```javascript
|
|
1152
|
-
import {logout} from 'omikit-plugin';
|
|
1153
|
-
logout();
|
|
1154
|
-
```
|
|
1104
|
+
### Check Credentials Without Connecting
|
|
1155
1105
|
|
|
1156
|
-
|
|
1106
|
+
Validate credentials without establishing a SIP connection:
|
|
1157
1107
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1108
|
+
```typescript
|
|
1109
|
+
const result = await checkCredentials({
|
|
1110
|
+
userName: 'sip_user',
|
|
1111
|
+
password: 'sip_password',
|
|
1112
|
+
realm: 'your_realm',
|
|
1113
|
+
});
|
|
1114
|
+
// result: { success: boolean, statusCode: number, message: string }
|
|
1165
1115
|
```
|
|
1166
1116
|
|
|
1117
|
+
### Register With Options
|
|
1167
1118
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
✅ Description: Open to enable system alert window (only Android).
|
|
1119
|
+
Full control over registration behavior:
|
|
1171
1120
|
|
|
1172
|
-
```
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
}
|
|
1121
|
+
```typescript
|
|
1122
|
+
const result = await registerWithOptions({
|
|
1123
|
+
// Registration options
|
|
1124
|
+
});
|
|
1125
|
+
// result: { success: boolean, statusCode: number, message: string }
|
|
1178
1126
|
```
|
|
1179
1127
|
|
|
1180
|
-
|
|
1128
|
+
### Keep-Alive
|
|
1181
1129
|
|
|
1182
|
-
|
|
1130
|
+
Monitor and maintain SIP connection:
|
|
1183
1131
|
|
|
1184
|
-
```
|
|
1185
|
-
|
|
1132
|
+
```typescript
|
|
1133
|
+
// Check current keep-alive status
|
|
1134
|
+
const status = await getKeepAliveStatus();
|
|
1186
1135
|
|
|
1187
|
-
|
|
1188
|
-
|
|
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
|
-
});
|
|
1136
|
+
// Manually trigger a keep-alive ping
|
|
1137
|
+
await triggerKeepAlivePing();
|
|
1193
1138
|
```
|
|
1194
1139
|
|
|
1195
|
-
|
|
1140
|
+
### Cold Start Call Handling
|
|
1196
1141
|
|
|
1197
|
-
|
|
1142
|
+
When the app is launched from a push notification:
|
|
1198
1143
|
|
|
1199
|
-
```
|
|
1200
|
-
|
|
1144
|
+
```typescript
|
|
1145
|
+
// Call this in your app's entry point
|
|
1146
|
+
const initialCall = await getInitialCall();
|
|
1201
1147
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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
|
-
});
|
|
1148
|
+
if (initialCall) {
|
|
1149
|
+
// There's a pending call — navigate to call screen
|
|
1150
|
+
console.log('Pending call from:', initialCall.callerNumber);
|
|
1151
|
+
}
|
|
1212
1152
|
```
|
|
1213
1153
|
|
|
1214
|
-
|
|
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
|
-
```
|
|
1154
|
+
---
|
|
1246
1155
|
|
|
1247
|
-
|
|
1156
|
+
## Troubleshooting
|
|
1248
1157
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1158
|
+
| Problem | Cause | Solution |
|
|
1159
|
+
|---------|-------|----------|
|
|
1160
|
+
| `LINKING_ERROR` on launch | Native module not linked | Run `pod install` (iOS) or rebuild the app |
|
|
1161
|
+
| Login returns `false` | Wrong SIP credentials or network | Verify `userName`, `password`, `realm`, `host` |
|
|
1162
|
+
| `accountRegisterFailed` (status 6) | SIP registration failed | Check `realm` and `host` params; verify network connectivity |
|
|
1163
|
+
| No incoming calls | Push not configured | Ensure FCM (Android) / PushKit VoIP (iOS) is set up |
|
|
1164
|
+
| No incoming calls on iOS (killed) | Missing Background Mode | Enable "Voice over IP" in Background Modes |
|
|
1165
|
+
| Incoming call on Android — no UI | Missing overlay permission | Call `requestSystemAlertWindowPermission()` |
|
|
1166
|
+
| `startCall` returns 450/451/452 | Missing runtime permissions | Call `requestPermissionsByCodes([code])` |
|
|
1167
|
+
| No audio during call | Audio route issue | Check `getAudio()` and `setAudio()` |
|
|
1168
|
+
| Video not showing | Video event not registered | Call `registerVideoEvent()` before the call |
|
|
1169
|
+
| NativeEventEmitter warning (iOS) | Old RN bridge issue | Upgrade to v4.0.x with New Architecture |
|
|
1170
|
+
| `Invalid local URI` in logs | Empty proxy/host in login | Pass `host` parameter in `initCallWithUserPassword` |
|
|
1171
|
+
| Build error with New Arch | Codegen not configured | Ensure `codegenConfig` exists in `package.json` |
|
|
1253
1172
|
|
|
1254
|
-
|
|
1173
|
+
---
|
|
1255
1174
|
|
|
1256
|
-
|
|
1257
|
-
import {refreshRemoteCamera} from 'omikit-plugin';
|
|
1258
|
-
refreshRemoteCamera();
|
|
1259
|
-
```
|
|
1175
|
+
## Documentation
|
|
1260
1176
|
|
|
1261
|
-
|
|
1177
|
+
Full documentation in [`./docs/`](./docs/):
|
|
1262
1178
|
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1179
|
+
- [Project Overview & PDR](./docs/project-overview-pdr.md)
|
|
1180
|
+
- [Codebase Summary](./docs/codebase-summary.md)
|
|
1181
|
+
- [System Architecture](./docs/system-architecture.md)
|
|
1182
|
+
- [Code Standards](./docs/code-standards.md)
|
|
1183
|
+
- [Roadmap](./docs/project-roadmap.md)
|
|
1267
1184
|
|
|
1268
|
-
|
|
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
|
-
```
|
|
1185
|
+
## License
|
|
1417
1186
|
|
|
1418
|
-
|
|
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
|
|
1187
|
+
MIT — [ViHAT Group](https://github.com/VIHATTeam)
|