omikit-plugin 4.1.6 → 4.1.7
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 +58 -4
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/omikitplugin/OmikitPluginModule.kt +133 -4
- package/ios/CallProcess/CallManager.swift +7 -3
- package/ios/Library/OmikitPlugin.m +15 -0
- package/ios/Library/OmikitPlugin.swift +123 -5
- package/lib/commonjs/NativeOmikitPlugin.js.map +1 -1
- package/lib/commonjs/omikit.js +156 -17
- package/lib/commonjs/omikit.js.map +1 -1
- package/lib/module/NativeOmikitPlugin.js.map +1 -1
- package/lib/module/omikit.js +152 -18
- package/lib/module/omikit.js.map +1 -1
- package/omikit-plugin.podspec +1 -1
- package/package.json +6 -2
- package/src/NativeOmikitPlugin.ts +9 -0
- package/src/omikit.tsx +180 -18
- package/src/types/index.d.ts +67 -1
package/README.md
CHANGED
|
@@ -54,8 +54,8 @@ The [omikit-plugin](https://www.npmjs.com/package/omikit-plugin) enables VoIP/SI
|
|
|
54
54
|
|
|
55
55
|
| Platform | SDK | Version |
|
|
56
56
|
|----------|-----|---------|
|
|
57
|
-
| Android |
|
|
58
|
-
| iOS | OmiKit | 1.11.
|
|
57
|
+
| Android | OMICore | 2.6.21 |
|
|
58
|
+
| iOS | OmiKit | 1.11.19 |
|
|
59
59
|
|
|
60
60
|
### Platform Requirements
|
|
61
61
|
|
|
@@ -1086,6 +1086,7 @@ await initCallWithUserPassword({
|
|
|
1086
1086
|
| `initCallWithUserPassword(data)` | `Promise<boolean>` | Login with SIP username/password |
|
|
1087
1087
|
| `initCallWithApiKey(data)` | `Promise<boolean>` | Login with API key |
|
|
1088
1088
|
| `logout()` | `Promise<boolean>` | Logout and unregister SIP |
|
|
1089
|
+
| `logoutAndWait()` | `Promise<boolean>` | (v4.1.7+) Logout that **resolves only after** the SDK finishes the backend `devices/remove` HTTP call and clears local state. Use before an immediate re-login to avoid race conditions |
|
|
1089
1090
|
|
|
1090
1091
|
### Call Control
|
|
1091
1092
|
|
|
@@ -1135,12 +1136,65 @@ await initCallWithUserPassword({
|
|
|
1135
1136
|
| Function | Returns | Description |
|
|
1136
1137
|
|----------|---------|-------------|
|
|
1137
1138
|
| `getProjectId()` | `Promise<string\|null>` | Current project ID |
|
|
1138
|
-
| `getAppId()` | `Promise<string\|null>` | Current app ID |
|
|
1139
|
-
| `getDeviceId()` | `Promise<string\|null>` | Current device ID |
|
|
1139
|
+
| `getAppId()` | `Promise<string\|null>` | Current app ID. (v4.1.7+ Android) Sourced from `OmiClient.getAppId()` to match `getOmiDevices()` payload |
|
|
1140
|
+
| `getDeviceId()` | `Promise<string\|null>` | Current device ID. (v4.1.7+ Android) Sourced from `OmiClient.getDeviceId()` to match `getOmiDevices()` payload |
|
|
1140
1141
|
| `getFcmToken()` | `Promise<string\|null>` | FCM push token |
|
|
1141
1142
|
| `getSipInfo()` | `Promise<string\|null>` | SIP info (`user@realm`) |
|
|
1142
1143
|
| `getVoipToken()` | `Promise<string\|null>` | VoIP token (iOS only) |
|
|
1143
1144
|
|
|
1145
|
+
### Backend Device Registration Check (v4.1.7+)
|
|
1146
|
+
|
|
1147
|
+
> Read-only diagnostics for verifying the local device record still exists on the OMI backend. Useful after app reinstall, account migration, or backend cleanup. The SDK **never** auto-logouts — your app decides the recovery action.
|
|
1148
|
+
|
|
1149
|
+
| Function | Returns | Description |
|
|
1150
|
+
|----------|---------|-------------|
|
|
1151
|
+
| `getOmiDevices()` | `Promise<OmiDeviceInfo[]>` | Fetch all devices registered on the OMI backend for the active SIP user. Empty array when not logged in / network error / parse error — never rejects |
|
|
1152
|
+
| `isCurrentDeviceRegistered()` | `Promise<boolean>` | `true` if the local `deviceId` + `appId` is in the backend list. Returns `false` early when not logged in (no HTTP call) |
|
|
1153
|
+
| `needsReLogin()` | `Promise<boolean>` | `true` when SIP user is set locally but the backend has no matching device (stale session — user must logout + login again) |
|
|
1154
|
+
| `findSipNumberByDeviceId(devices, deviceId)` | `string \| null` | Pure JS helper. Returns the `sipNumber` for the given device ID in the array, or `null` if not found |
|
|
1155
|
+
|
|
1156
|
+
**`OmiDeviceInfo` shape (camelCase, normalized at JS layer):**
|
|
1157
|
+
|
|
1158
|
+
```typescript
|
|
1159
|
+
type OmiDeviceInfo = {
|
|
1160
|
+
deviceId: string;
|
|
1161
|
+
token: string;
|
|
1162
|
+
deviceType: 'ios' | 'android' | string;
|
|
1163
|
+
voipToken: string;
|
|
1164
|
+
appId: string;
|
|
1165
|
+
createdTime: string;
|
|
1166
|
+
projectId: string;
|
|
1167
|
+
sipNumber: string;
|
|
1168
|
+
};
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
**Recommended usage:**
|
|
1172
|
+
|
|
1173
|
+
```typescript
|
|
1174
|
+
import {
|
|
1175
|
+
getOmiDevices,
|
|
1176
|
+
needsReLogin,
|
|
1177
|
+
findSipNumberByDeviceId,
|
|
1178
|
+
getDeviceId,
|
|
1179
|
+
logoutAndWait,
|
|
1180
|
+
} from 'omikit-plugin';
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
// Or: verify the SIP account matches the one bound to this device
|
|
1184
|
+
const devices = await getOmiDevices();
|
|
1185
|
+
const myDeviceId = await getDeviceId();
|
|
1186
|
+
const boundSip = findSipNumberByDeviceId(devices, myDeviceId ?? '');
|
|
1187
|
+
if (boundSip !== expectedUsername) {
|
|
1188
|
+
await logoutAndWait();
|
|
1189
|
+
// Then re-login with correct credentials
|
|
1190
|
+
}
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
**Notes:**
|
|
1194
|
+
|
|
1195
|
+
- Each call performs a fresh HTTP request — no caching. Call once after login / on foreground, not in tight loops.
|
|
1196
|
+
- Requires native SDK: iOS `OmiKit ≥ 1.11.19`, Android `OMICore ≥ 2.6.21`.
|
|
1197
|
+
|
|
1144
1198
|
### Notification Control
|
|
1145
1199
|
|
|
1146
1200
|
| Function | Returns | Description |
|
package/android/build.gradle
CHANGED
|
@@ -65,7 +65,7 @@ dependencies {
|
|
|
65
65
|
// OMISDK
|
|
66
66
|
implementation("androidx.work:work-runtime:2.8.1")
|
|
67
67
|
implementation "androidx.security:security-crypto:1.1.0-alpha06"
|
|
68
|
-
api "io.omicrm.vihat:omi-sdk:2.6.
|
|
68
|
+
api "io.omicrm.vihat:omi-sdk:2.6.22"
|
|
69
69
|
|
|
70
70
|
// React Native — resolved from consumer's node_modules
|
|
71
71
|
implementation "com.facebook.react:react-native:+"
|
|
@@ -1151,6 +1151,36 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1151
1151
|
}
|
|
1152
1152
|
}
|
|
1153
1153
|
|
|
1154
|
+
/// Logout and wait for the SDK to fully clean up before resolving.
|
|
1155
|
+
///
|
|
1156
|
+
/// `OmiClient.logout(onCompleted)` is a `suspend fun` that:
|
|
1157
|
+
/// - Fires HTTP `devices/remove` (fire-and-forget background)
|
|
1158
|
+
/// - Clears local SharedPreferences
|
|
1159
|
+
/// - Stops SipService and polls for PJSIP shutdown (up to 5s internally)
|
|
1160
|
+
/// - Invokes `onCompleted` only after the stack is fully down
|
|
1161
|
+
///
|
|
1162
|
+
/// We pass an empty callback purely to opt into the SDK's internal wait
|
|
1163
|
+
/// loop — without it the SDK skips waiting and returns immediately.
|
|
1164
|
+
/// The plugin's coroutine `await`s the suspend call, so the JS promise
|
|
1165
|
+
/// resolves only once the SDK signals completion (or its 5s internal
|
|
1166
|
+
/// timeout elapses).
|
|
1167
|
+
@ReactMethod
|
|
1168
|
+
fun logoutAndWait(promise: Promise) {
|
|
1169
|
+
val ctx = reactApplicationContext
|
|
1170
|
+
if (ctx == null) {
|
|
1171
|
+
promise.resolve(true)
|
|
1172
|
+
return
|
|
1173
|
+
}
|
|
1174
|
+
mainScope.launch {
|
|
1175
|
+
try {
|
|
1176
|
+
OmiClient.getInstance(ctx).logout { /* opt-in to internal wait */ }
|
|
1177
|
+
} catch (_: Throwable) {
|
|
1178
|
+
// ignored — local state still cleaned by the SDK, safe to proceed
|
|
1179
|
+
}
|
|
1180
|
+
promise.resolve(true)
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1154
1184
|
@ReactMethod
|
|
1155
1185
|
fun getCurrentUser(promise: Promise) {
|
|
1156
1186
|
mainScope.launch {
|
|
@@ -1230,13 +1260,22 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1230
1260
|
@ReactMethod
|
|
1231
1261
|
fun getAppId(promise: Promise) {
|
|
1232
1262
|
try {
|
|
1263
|
+
// Prefer SDK public API (OmiSDK 2.6.21+) — guarantees same value used when
|
|
1264
|
+
// adding device to backend, so consumers can match against getOmiDevices().
|
|
1265
|
+
val ctx = reactApplicationContext
|
|
1266
|
+
if (ctx != null) {
|
|
1267
|
+
val sdkAppId = OmiClient.getInstance(ctx).getAppId()
|
|
1268
|
+
if (sdkAppId.isNotEmpty()) {
|
|
1269
|
+
promise.resolve(sdkAppId)
|
|
1270
|
+
return
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1233
1273
|
val info = OmiClient.registrationInfo
|
|
1234
1274
|
if (info?.appId != null) {
|
|
1235
1275
|
promise.resolve(info.appId)
|
|
1236
1276
|
return
|
|
1237
1277
|
}
|
|
1238
|
-
|
|
1239
|
-
promise.resolve(reactApplicationContext?.packageName)
|
|
1278
|
+
promise.resolve(ctx?.packageName)
|
|
1240
1279
|
} catch (e: Throwable) {
|
|
1241
1280
|
promise.resolve(null)
|
|
1242
1281
|
}
|
|
@@ -1245,15 +1284,24 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1245
1284
|
@ReactMethod
|
|
1246
1285
|
fun getDeviceId(promise: Promise) {
|
|
1247
1286
|
try {
|
|
1287
|
+
// Prefer SDK public API (OmiSDK 2.6.21+) — guarantees same value used when
|
|
1288
|
+
// adding device to backend, so consumers can match against getOmiDevices().
|
|
1289
|
+
val ctx = reactApplicationContext
|
|
1290
|
+
if (ctx != null) {
|
|
1291
|
+
val sdkDeviceId = OmiClient.getInstance(ctx).getDeviceId()
|
|
1292
|
+
if (sdkDeviceId.isNotEmpty()) {
|
|
1293
|
+
promise.resolve(sdkDeviceId)
|
|
1294
|
+
return
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1248
1297
|
val info = OmiClient.registrationInfo
|
|
1249
1298
|
if (info?.deviceId != null) {
|
|
1250
1299
|
promise.resolve(info.deviceId)
|
|
1251
1300
|
return
|
|
1252
1301
|
}
|
|
1253
|
-
// Fallback: get Android ID directly
|
|
1254
1302
|
val androidId = try {
|
|
1255
1303
|
Settings.Secure.getString(
|
|
1256
|
-
|
|
1304
|
+
ctx?.contentResolver,
|
|
1257
1305
|
Settings.Secure.ANDROID_ID
|
|
1258
1306
|
)
|
|
1259
1307
|
} catch (e: Exception) { null }
|
|
@@ -1305,6 +1353,87 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1305
1353
|
promise.resolve(null)
|
|
1306
1354
|
}
|
|
1307
1355
|
|
|
1356
|
+
// MARK: - Backend Device Registration Check APIs (OmiSDK 2.6.20+)
|
|
1357
|
+
|
|
1358
|
+
/// Fetch the list of devices currently registered on OMI backend for the
|
|
1359
|
+
/// active SIP user. Returns empty array on logout / network failure.
|
|
1360
|
+
@ReactMethod
|
|
1361
|
+
fun getOmiDevices(promise: Promise) {
|
|
1362
|
+
val ctx = reactApplicationContext
|
|
1363
|
+
if (ctx == null) {
|
|
1364
|
+
promise.resolve(WritableNativeArray())
|
|
1365
|
+
return
|
|
1366
|
+
}
|
|
1367
|
+
mainScope.launch {
|
|
1368
|
+
val devices = withContext(Dispatchers.IO) {
|
|
1369
|
+
try {
|
|
1370
|
+
OmiClient.getInstance(ctx).getOmiDevices()
|
|
1371
|
+
} catch (_: Throwable) {
|
|
1372
|
+
emptyList()
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
val array: WritableArray = WritableNativeArray()
|
|
1376
|
+
for (d in devices) {
|
|
1377
|
+
val map: WritableMap = WritableNativeMap()
|
|
1378
|
+
map.putString("device_id", d.deviceId)
|
|
1379
|
+
map.putString("token", d.token)
|
|
1380
|
+
map.putString("device_type", d.deviceType)
|
|
1381
|
+
map.putString("voip_token", d.voipToken)
|
|
1382
|
+
map.putString("app_id", d.appId)
|
|
1383
|
+
// created_time may overflow Int — use Double for cross-bridge safety.
|
|
1384
|
+
if (d.createdTime != null) {
|
|
1385
|
+
map.putDouble("created_time", d.createdTime!!.toDouble())
|
|
1386
|
+
}
|
|
1387
|
+
map.putString("project_id", d.projectId)
|
|
1388
|
+
map.putString("sipNumber", d.sipNumber)
|
|
1389
|
+
array.pushMap(map)
|
|
1390
|
+
}
|
|
1391
|
+
promise.resolve(array)
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/// Verify whether THIS device is registered on backend for the current SIP
|
|
1396
|
+
/// user. Returns false early when not logged in.
|
|
1397
|
+
@ReactMethod
|
|
1398
|
+
fun isCurrentDeviceRegistered(promise: Promise) {
|
|
1399
|
+
val ctx = reactApplicationContext
|
|
1400
|
+
if (ctx == null) {
|
|
1401
|
+
promise.resolve(false)
|
|
1402
|
+
return
|
|
1403
|
+
}
|
|
1404
|
+
mainScope.launch {
|
|
1405
|
+
val registered = withContext(Dispatchers.IO) {
|
|
1406
|
+
try {
|
|
1407
|
+
OmiClient.getInstance(ctx).isCurrentDeviceRegistered()
|
|
1408
|
+
} catch (_: Throwable) {
|
|
1409
|
+
false
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
promise.resolve(registered)
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
/// Returns true when a SIP user is set locally but no matching device exists
|
|
1417
|
+
/// on backend — i.e. user must logout + login again to re-register.
|
|
1418
|
+
@ReactMethod
|
|
1419
|
+
fun needsReLogin(promise: Promise) {
|
|
1420
|
+
val ctx = reactApplicationContext
|
|
1421
|
+
if (ctx == null) {
|
|
1422
|
+
promise.resolve(false)
|
|
1423
|
+
return
|
|
1424
|
+
}
|
|
1425
|
+
mainScope.launch {
|
|
1426
|
+
val needs = withContext(Dispatchers.IO) {
|
|
1427
|
+
try {
|
|
1428
|
+
OmiClient.getInstance(ctx).needsReLogin()
|
|
1429
|
+
} catch (_: Throwable) {
|
|
1430
|
+
false
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
promise.resolve(needs)
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1308
1437
|
@ReactMethod
|
|
1309
1438
|
fun getAudio(promise: Promise) {
|
|
1310
1439
|
val inputs = OmiClient.getInstance(reactApplicationContext!!).getAudioOutputs()
|
|
@@ -621,12 +621,16 @@ func startCall(_ phoneNumber: String, isVideo: Bool, completion: @escaping (_: S
|
|
|
621
621
|
try? call.sendDTMF(character)
|
|
622
622
|
}
|
|
623
623
|
|
|
624
|
-
///
|
|
625
|
-
|
|
624
|
+
/// Toggle mute via CallKit so the native call UI (lock screen, banner) stays in sync.
|
|
625
|
+
/// Completion fires after performSetMutedCallAction: updates call.muted — safe to read there.
|
|
626
|
+
func toggleMute(completion: ((Error?) -> Void)? = nil) {
|
|
626
627
|
guard let call = getAvailableCall() else {
|
|
628
|
+
completion?(nil)
|
|
627
629
|
return
|
|
628
630
|
}
|
|
629
|
-
|
|
631
|
+
omiLib.callManager.toggleMute(for: call) { error in
|
|
632
|
+
completion?(error)
|
|
633
|
+
}
|
|
630
634
|
}
|
|
631
635
|
|
|
632
636
|
/// Toogle hold
|
|
@@ -86,6 +86,11 @@ RCT_EXTERN_METHOD(toggleOmiVideo:(RCTPromiseResolveBlock)resolve
|
|
|
86
86
|
RCT_EXTERN_METHOD(logout:(RCTPromiseResolveBlock)resolve
|
|
87
87
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
88
88
|
|
|
89
|
+
// Logout and wait — resolves ONLY after the SDK has finished its async cleanup
|
|
90
|
+
// (HTTP devices/remove + local state reset). Use this before re-login.
|
|
91
|
+
RCT_EXTERN_METHOD(logoutAndWait:(RCTPromiseResolveBlock)resolve
|
|
92
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
93
|
+
|
|
89
94
|
// Register video event
|
|
90
95
|
RCT_EXTERN_METHOD(registerVideoEvent:(RCTPromiseResolveBlock)resolve
|
|
91
96
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
@@ -135,6 +140,16 @@ RCT_EXTERN_METHOD(getAppId:(RCTPromiseResolveBlock)resolve
|
|
|
135
140
|
RCT_EXTERN_METHOD(getVoipToken:(RCTPromiseResolveBlock)resolve
|
|
136
141
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
137
142
|
|
|
143
|
+
// Backend device registration check APIs (OmiKit iOS 1.11.19 / OmiSDK Android 2.6.20)
|
|
144
|
+
RCT_EXTERN_METHOD(getOmiDevices:(RCTPromiseResolveBlock)resolve
|
|
145
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
146
|
+
|
|
147
|
+
RCT_EXTERN_METHOD(isCurrentDeviceRegistered:(RCTPromiseResolveBlock)resolve
|
|
148
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
149
|
+
|
|
150
|
+
RCT_EXTERN_METHOD(needsReLogin:(RCTPromiseResolveBlock)resolve
|
|
151
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
152
|
+
|
|
138
153
|
// Get audio
|
|
139
154
|
RCT_EXTERN_METHOD(getAudio:(RCTPromiseResolveBlock)resolve
|
|
140
155
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
@@ -158,12 +158,13 @@ public class OmikitPlugin: RCTEventEmitter {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
@objc(toggleMute:rejecter:)
|
|
161
|
-
func toggleMute(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
|
|
162
|
-
CallManager.shareInstance().toggleMute
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
func toggleMute(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
162
|
+
CallManager.shareInstance().toggleMute { [weak self] _ in
|
|
163
|
+
// Read call.muted AFTER CallKit completes — value is authoritative at this point.
|
|
164
|
+
let muted = CallManager.shareInstance().getAvailableCall()?.muted ?? false
|
|
165
|
+
resolve(muted)
|
|
166
|
+
self?.sendMuteStatus()
|
|
165
167
|
}
|
|
166
|
-
sendMuteStatus()
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
@objc(toggleSpeaker:rejecter:)
|
|
@@ -377,6 +378,21 @@ public class OmikitPlugin: RCTEventEmitter {
|
|
|
377
378
|
CallManager.shareInstance().logout()
|
|
378
379
|
resolve(true)
|
|
379
380
|
}
|
|
381
|
+
|
|
382
|
+
/// Logout and wait for the SDK to fully clean up.
|
|
383
|
+
/// Uses OmiKit 1.11.x `logoutWithCompletion:` which fires on main thread
|
|
384
|
+
/// after HTTP devices/remove returns AND local SIP state (`currentSip`) is
|
|
385
|
+
/// reset. Safe to call `initCallWithUserPassword` immediately after this resolves.
|
|
386
|
+
///
|
|
387
|
+
/// Resolves with the `success` flag from the SDK (true = backend confirmed
|
|
388
|
+
/// remove, false = local cleanup happened but server call failed — either
|
|
389
|
+
/// way the local state is now sane).
|
|
390
|
+
@objc(logoutAndWait:rejecter:)
|
|
391
|
+
func logoutAndWait(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
392
|
+
OmiClient.logout { success in
|
|
393
|
+
resolve(success)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
380
396
|
|
|
381
397
|
|
|
382
398
|
@objc(registerVideoEvent:rejecter:)
|
|
@@ -537,6 +553,44 @@ public class OmikitPlugin: RCTEventEmitter {
|
|
|
537
553
|
resolve(OmiClient.getVoipToken())
|
|
538
554
|
}
|
|
539
555
|
|
|
556
|
+
// MARK: - Backend Device Registration Check APIs (OmiKit 1.11.19)
|
|
557
|
+
|
|
558
|
+
/// Fetch the list of devices currently registered on OMI backend for active SIP user.
|
|
559
|
+
/// Runs on a background queue because OmiKit performs a synchronous HTTP request.
|
|
560
|
+
@objc(getOmiDevices:rejecter:)
|
|
561
|
+
func getOmiDevices(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
562
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
563
|
+
let devices = OmiClient.getOmiDevices()
|
|
564
|
+
DispatchQueue.main.async {
|
|
565
|
+
resolve(devices)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/// Verify whether THIS device is registered on backend for the current SIP user.
|
|
571
|
+
/// Hits the network (via getOmiDevices) — dispatch off main thread.
|
|
572
|
+
@objc(isCurrentDeviceRegistered:rejecter:)
|
|
573
|
+
func isCurrentDeviceRegistered(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
574
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
575
|
+
let registered = OmiClient.isCurrentDeviceRegistered()
|
|
576
|
+
DispatchQueue.main.async {
|
|
577
|
+
resolve(registered)
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/// Convenience guard built on top of isCurrentDeviceRegistered. Returns true
|
|
583
|
+
/// when a SIP user is set locally but no matching device exists on backend.
|
|
584
|
+
@objc(needsReLogin:rejecter:)
|
|
585
|
+
func needsReLogin(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
586
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
587
|
+
let needs = OmiClient.needsReLogin()
|
|
588
|
+
DispatchQueue.main.async {
|
|
589
|
+
resolve(needs)
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
540
594
|
// MARK: - Audio Methods
|
|
541
595
|
@objc(getAudio:rejecter:)
|
|
542
596
|
func getAudio(resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
@@ -638,6 +692,70 @@ public class OmikitPlugin: RCTEventEmitter {
|
|
|
638
692
|
]
|
|
639
693
|
}
|
|
640
694
|
|
|
695
|
+
// Event emitter — works across Old Architecture, New Architecture (Fabric),
|
|
696
|
+
// and bridgeless modes.
|
|
697
|
+
//
|
|
698
|
+
// Why the custom override:
|
|
699
|
+
// In NewArch, the JS side talks to the codegen TurboModule wrapper, which
|
|
700
|
+
// does NOT forward `addListener`/`removeListeners` to this Swift instance.
|
|
701
|
+
// RCTEventEmitter's private `_listenerCount` therefore stays at 0, and
|
|
702
|
+
// the default `sendEvent` short-circuits ("Sending X with no listeners
|
|
703
|
+
// registered") — dropping every event.
|
|
704
|
+
//
|
|
705
|
+
// The fix:
|
|
706
|
+
// 1. Forward `addListener`/`removeListeners` to super so the legacy
|
|
707
|
+
// counter is incremented when JS subscribes via the interop bridge.
|
|
708
|
+
// 2. Track an independent counter as a safety net for paths where super
|
|
709
|
+
// cannot increment (pure bridgeless callableJSModules-only mode).
|
|
710
|
+
// 3. In `sendEvent`, try `super.sendEvent` first (uses RN-wired dispatch
|
|
711
|
+
// for the current architecture), then fall back to direct calls to
|
|
712
|
+
// `RCTDeviceEventEmitter` via bridge or callableJSModules. JS code
|
|
713
|
+
// subscribes through the global `DeviceEventEmitter`, which is the
|
|
714
|
+
// same channel.
|
|
715
|
+
private var jsListenerCount: Int = 0
|
|
716
|
+
|
|
717
|
+
@objc public override func addListener(_ eventName: String!) {
|
|
718
|
+
super.addListener(eventName)
|
|
719
|
+
jsListenerCount += 1
|
|
720
|
+
if jsListenerCount == 1 {
|
|
721
|
+
startObserving()
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
@objc public override func removeListeners(_ count: Double) {
|
|
726
|
+
super.removeListeners(count)
|
|
727
|
+
jsListenerCount = max(0, jsListenerCount - Int(count))
|
|
728
|
+
if jsListenerCount == 0 {
|
|
729
|
+
stopObserving()
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
@objc public override func sendEvent(withName name: String!, body: Any!) {
|
|
734
|
+
// Preferred: super uses RN's wired dispatch for the active architecture.
|
|
735
|
+
// Requires the counter to be non-zero, which is guaranteed by our
|
|
736
|
+
// `addListener` forwarding above whenever the legacy bridge is involved.
|
|
737
|
+
if jsListenerCount > 0 {
|
|
738
|
+
super.sendEvent(withName: name, body: body)
|
|
739
|
+
return
|
|
740
|
+
}
|
|
741
|
+
// Fallback 1: bridge route — works on Old Arch and NewArch interop bridge.
|
|
742
|
+
if let bridge = self.bridge {
|
|
743
|
+
bridge.enqueueJSCall(
|
|
744
|
+
"RCTDeviceEventEmitter",
|
|
745
|
+
method: "emit",
|
|
746
|
+
args: body != nil ? [name as Any, body as Any] : [name as Any],
|
|
747
|
+
completion: nil
|
|
748
|
+
)
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
// Fallback 2: bridgeless mode — only callableJSModules is available.
|
|
752
|
+
self.callableJSModules?.invokeModule(
|
|
753
|
+
"RCTDeviceEventEmitter",
|
|
754
|
+
method: "emit",
|
|
755
|
+
withArgs: body != nil ? [name as Any, body as Any] : [name as Any]
|
|
756
|
+
)
|
|
757
|
+
}
|
|
758
|
+
|
|
641
759
|
// MARK: - Stub Methods for TurboModule Compatibility
|
|
642
760
|
// These methods are Android-only but required by Codegen spec
|
|
643
761
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_default","TurboModuleRegistry","get","exports","default"],"sourceRoot":"../../src","sources":["NativeOmikitPlugin.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_default","TurboModuleRegistry","get","exports","default"],"sourceRoot":"../../src","sources":["NativeOmikitPlugin.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GA8KpCC,gCAAmB,CAACC,GAAG,CAAO,cAAc,CAAC;AAAAC,OAAA,CAAAC,OAAA,GAAAJ,QAAA"}
|