omikit-plugin 3.3.29 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +994 -1217
  2. package/android/build.gradle +22 -72
  3. package/android/gradle.properties +4 -4
  4. package/android/src/main/java/com/omikitplugin/FLLocalCameraModule.kt +1 -1
  5. package/android/src/main/java/com/omikitplugin/FLRemoteCameraModule.kt +1 -1
  6. package/android/src/main/java/com/omikitplugin/OmikitPluginModule.kt +326 -356
  7. package/android/src/main/java/com/omikitplugin/constants/constant.kt +2 -1
  8. package/ios/CallProcess/CallManager.swift +45 -35
  9. package/ios/Constant/Constant.swift +1 -0
  10. package/ios/Library/OmikitPlugin.m +75 -1
  11. package/ios/Library/OmikitPlugin.swift +199 -16
  12. package/ios/OmikitPlugin-Protocol.h +161 -0
  13. package/lib/commonjs/NativeOmikitPlugin.js +9 -0
  14. package/lib/commonjs/NativeOmikitPlugin.js.map +1 -0
  15. package/lib/commonjs/index.js +11 -0
  16. package/lib/commonjs/index.js.map +1 -1
  17. package/lib/commonjs/omi_audio_type.js +20 -0
  18. package/lib/commonjs/omi_audio_type.js.map +1 -0
  19. package/lib/commonjs/omi_local_camera.js +18 -2
  20. package/lib/commonjs/omi_local_camera.js.map +1 -1
  21. package/lib/commonjs/omi_remote_camera.js +18 -2
  22. package/lib/commonjs/omi_remote_camera.js.map +1 -1
  23. package/lib/commonjs/omi_start_call_status.js +30 -0
  24. package/lib/commonjs/omi_start_call_status.js.map +1 -1
  25. package/lib/commonjs/omikit.js +125 -14
  26. package/lib/commonjs/omikit.js.map +1 -1
  27. package/lib/module/NativeOmikitPlugin.js +3 -0
  28. package/lib/module/NativeOmikitPlugin.js.map +1 -0
  29. package/lib/module/index.js +1 -0
  30. package/lib/module/index.js.map +1 -1
  31. package/lib/module/omi_audio_type.js +14 -0
  32. package/lib/module/omi_audio_type.js.map +1 -0
  33. package/lib/module/omi_local_camera.js +19 -2
  34. package/lib/module/omi_local_camera.js.map +1 -1
  35. package/lib/module/omi_remote_camera.js +19 -2
  36. package/lib/module/omi_remote_camera.js.map +1 -1
  37. package/lib/module/omi_start_call_status.js +30 -0
  38. package/lib/module/omi_start_call_status.js.map +1 -1
  39. package/lib/module/omikit.js +119 -15
  40. package/lib/module/omikit.js.map +1 -1
  41. package/omikit-plugin.podspec +26 -24
  42. package/package.json +11 -2
  43. package/src/NativeOmikitPlugin.ts +160 -0
  44. package/src/index.tsx +2 -1
  45. package/src/omi_audio_type.tsx +9 -0
  46. package/src/omi_local_camera.tsx +17 -3
  47. package/src/omi_remote_camera.tsx +17 -3
  48. package/src/omi_start_call_status.tsx +29 -10
  49. package/src/omikit.tsx +118 -28
  50. package/src/types/index.d.ts +111 -11
@@ -120,7 +120,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
120
120
  private val mainScope = CoroutineScope(Dispatchers.Main)
121
121
  private var isIncoming: Boolean = false
122
122
  private var isAnswerCall: Boolean = false
123
- private var permissionPromise: Promise? = null
123
+ @Volatile private var permissionPromise: Promise? = null
124
124
 
125
125
  // Call state management to prevent concurrent calls
126
126
  private var isCallInProgress: Boolean = false
@@ -134,7 +134,22 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
134
134
  override fun getName(): String {
135
135
  return NAME
136
136
  }
137
-
137
+
138
+ override fun getConstants(): MutableMap<String, Any> {
139
+ return mutableMapOf(
140
+ "CALL_STATE_CHANGED" to CALL_STATE_CHANGED,
141
+ "MUTED" to MUTED,
142
+ "HOLD" to HOLD,
143
+ "SPEAKER" to SPEAKER,
144
+ "REMOTE_VIDEO_READY" to REMOTE_VIDEO_READY,
145
+ "CLICK_MISSED_CALL" to CLICK_MISSED_CALL,
146
+ "SWITCHBOARD_ANSWER" to SWITCHBOARD_ANSWER,
147
+ "CALL_QUALITY" to CALL_QUALITY,
148
+ "AUDIO_CHANGE" to AUDIO_CHANGE,
149
+ "REQUEST_PERMISSION" to REQUEST_PERMISSION
150
+ )
151
+ }
152
+
138
153
  /**
139
154
  * Check if we can start a new call (no concurrent calls, cooldown passed)
140
155
  */
@@ -145,12 +160,11 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
145
160
 
146
161
  // Check if call is in progress or cooldown not passed
147
162
  if (isCallInProgress) {
148
- Log.d("OMISDK", "🚫 Call blocked: Call already in progress")
163
+
149
164
  return false
150
165
  }
151
166
 
152
167
  if (timeSinceLastCall < callCooldownMs) {
153
- Log.d("OMISDK", "🚫 Call blocked: Cooldown period (${callCooldownMs - timeSinceLastCall}ms remaining)")
154
168
  return false
155
169
  }
156
170
 
@@ -165,7 +179,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
165
179
  synchronized(callStateLock) {
166
180
  isCallInProgress = true
167
181
  lastCallTime = System.currentTimeMillis()
168
- Log.d("OMISDK", "📞 Call started, marking in progress")
169
182
  }
170
183
  }
171
184
 
@@ -175,7 +188,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
175
188
  private fun markCallEnded() {
176
189
  synchronized(callStateLock) {
177
190
  isCallInProgress = false
178
- Log.d("OMISDK", "📴 Call ended, clearing in progress flag")
179
191
  }
180
192
  }
181
193
 
@@ -183,11 +195,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
183
195
  private val handler = Handler(Looper.getMainLooper())
184
196
 
185
197
  override fun incomingReceived(callerId: Int?, phoneNumber: String?, isVideo: Boolean?) {
186
- Log.d("OMISDK", "=>> incomingReceived CALLED - BEFORE: isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
187
198
  isIncoming = true;
188
199
  isAnswerCall = false; // Reset answer state for new incoming call
189
- Log.d("OMISDK", "=>> incomingReceived AFTER SET - isIncoming: $isIncoming, isAnswerCall: $isAnswerCall, phoneNumber: $phoneNumber")
190
-
191
200
  val typeNumber = OmiKitUtils().checkTypeNumber(phoneNumber ?: "")
192
201
 
193
202
  val map: WritableMap = WritableNativeMap().apply {
@@ -210,11 +219,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
210
219
  transactionId: String?,
211
220
  ) {
212
221
  isAnswerCall = true
213
- Log.d("OMISDK", "=>> ON CALL ESTABLISHED => ")
214
222
 
215
223
  Handler(Looper.getMainLooper()).postDelayed({
216
- Log.d("OmikitReactNative", "onCallEstablished")
217
-
218
224
  val typeNumber = OmiKitUtils().checkTypeNumber(phoneNumber ?: "")
219
225
 
220
226
  // ✅ Sử dụng safe WritableMap creation
@@ -233,18 +239,13 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
233
239
  }
234
240
 
235
241
  override fun onCallEnd(callInfo: MutableMap<String, Any?>, statusCode: Int) {
236
- Log.d("OMISDK RN", "=>> onCallEnd CALLED - BEFORE RESET: isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
237
- Log.d("OMISDK RN", "=>> onCallEnd callInfo => $callInfo")
238
-
239
242
  // Reset call state variables
240
243
  isIncoming = false
241
244
  isAnswerCall = false
242
245
  // Clear call progress state when remote party ends call
243
246
  markCallEnded()
244
- Log.d("OMISDK", "=>> onCallEnd AFTER RESET - isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
245
-
246
247
  // Kiểm tra kiểu dữ liệu trước khi ép kiểu để tránh lỗi
247
- val call = callInfo ?: mutableMapOf()
248
+ val call = callInfo
248
249
 
249
250
  val timeStartToAnswer = (call["time_start_to_answer"] as? Long) ?: 0L
250
251
  val timeEnd = (call["time_end"] as? Long) ?: 0L
@@ -267,18 +268,14 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
267
268
  )
268
269
 
269
270
  val map = createSafeWritableMap(eventData)
270
-
271
- Log.d("OMISDK RN", "=>> onCallEnd => ")
272
271
  sendEvent(CALL_STATE_CHANGED, map)
273
272
  }
274
273
 
275
274
  override fun onConnecting() {
276
- Log.d("OMISDK", "=>> ON CONNECTING CALL => ")
277
-
278
275
  val map: WritableMap = WritableNativeMap().apply {
279
276
  putString("callerNumber", "")
280
277
  putBoolean("isVideo", NotificationService.isVideo)
281
- putBoolean("incoming", isIncoming ?: false)
278
+ putBoolean("incoming", isIncoming)
282
279
  putString("transactionId", "")
283
280
  putString("_id", "")
284
281
  putInt("status", CallState.connecting.value)
@@ -299,18 +296,12 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
299
296
  val prePhoneNumber = OmiClient.prePhoneNumber ?: ""
300
297
  val typeNumber = OmiKitUtils().checkTypeNumber(prePhoneNumber)
301
298
 
302
- Log.d("OMISDK", "=>> onRinging CALLED - BEFORE: isIncoming: $isIncoming, isAnswerCall: $isAnswerCall, callDirection: $callDirection")
303
-
304
299
  if (callDirection == "inbound") {
305
300
  isIncoming = true;
306
- Log.d("OMISDK", "=>> onRinging SET isIncoming = true for inbound call")
307
301
  } else if (callDirection == "outbound") {
308
302
  isIncoming = false;
309
- Log.d("OMISDK", "=>> onRinging SET isIncoming = false for outbound call")
310
303
  }
311
304
 
312
- Log.d("OMISDK", "=>> onRinging AFTER: isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
313
-
314
305
  // ✅ Sử dụng safe WritableMap creation
315
306
  val eventData = mapOf(
316
307
  "callerNumber" to if (callDirection == "inbound") prePhoneNumber else "",
@@ -322,8 +313,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
322
313
  )
323
314
 
324
315
  val map = createSafeWritableMap(eventData)
325
-
326
- Log.d("OMISDK", if (callDirection == "inbound") "=>> ON INCOMING CALL => " else "=>> ON RINGING CALL => ")
327
316
  sendEvent(CALL_STATE_CHANGED, map)
328
317
  }
329
318
 
@@ -331,13 +320,21 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
331
320
  override fun networkHealth(stat: Map<String, *>, quality: Int) {
332
321
  val map: WritableMap = WritableNativeMap()
333
322
  map.putInt("quality", quality)
323
+ // Pass full diagnostics from stat map
324
+ val statMap: WritableMap = WritableNativeMap()
325
+ (stat["mos"] as? Number)?.let { statMap.putDouble("mos", it.toDouble()) }
326
+ (stat["jitter"] as? Number)?.let { statMap.putDouble("jitter", it.toDouble()) }
327
+ (stat["latency"] as? Number)?.let { statMap.putDouble("latency", it.toDouble()) }
328
+ (stat["ppl"] as? Number)?.let { statMap.putDouble("packetLoss", it.toDouble()) }
329
+ (stat["lcn"] as? Number)?.let { statMap.putInt("lcn", it.toInt()) }
330
+ map.putMap("stat", statMap)
334
331
  sendEvent(CALL_QUALITY, map)
335
332
  }
336
333
 
337
334
  override fun onAudioChanged(audioInfo: Map<String, Any>) {
338
335
  val audio: WritableMap = WritableNativeMap()
339
- audio.putString("name", audioInfo["name"] as String)
340
- audio.putInt("type", audioInfo["type"] as Int)
336
+ audio.putString("name", audioInfo["name"] as? String ?: "")
337
+ audio.putInt("type", audioInfo["type"] as? Int ?: 0)
341
338
  val map: WritableMap = WritableNativeMap()
342
339
  val writeList = WritableNativeArray()
343
340
  writeList.pushMap(audio)
@@ -355,13 +352,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
355
352
  }
356
353
 
357
354
  override fun onOutgoingStarted(callerId: Int, phoneNumber: String?, isVideo: Boolean?) {
358
- Log.d("OMISDK", "=>> onOutgoingStarted CALLED - BEFORE: isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
359
-
360
355
  // For outgoing calls, set states appropriately
361
356
  isIncoming = false;
362
357
  isAnswerCall = false;
363
- Log.d("OMISDK", "=>> onOutgoingStarted AFTER SET - isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
364
-
365
358
  val typeNumber = OmiKitUtils().checkTypeNumber(phoneNumber ?: "")
366
359
 
367
360
  val map: WritableMap = WritableNativeMap().apply {
@@ -384,8 +377,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
384
377
  }
385
378
 
386
379
  override fun onRegisterCompleted(statusCode: Int) {
387
- Log.d("OMISDK", "=> ON REGISTER COMPLETED => status code: $statusCode")
388
-
389
380
  if (statusCode != 200) {
390
381
  val normalizedStatusCode = if (statusCode == 403) 853 else statusCode
391
382
  val typeNumber = ""
@@ -413,7 +404,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
413
404
  }
414
405
  })
415
406
  }
416
- Log.d("OMISDK", "=>> onRequestPermission => $map")
417
407
  sendEvent(REQUEST_PERMISSION, map)
418
408
 
419
409
  }
@@ -456,16 +446,14 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
456
446
 
457
447
  override fun initialize() {
458
448
  super.initialize()
459
-
460
-
449
+
450
+ moduleInstance = this
461
451
  reactApplicationContext!!.addActivityEventListener(this)
462
452
  Handler(Looper.getMainLooper()).post {
463
- OmiClient.getInstance(reactApplicationContext!!).addCallStateListener(this)
464
-
465
- // ✅ Add listener cho AUTO-UNREGISTER status
466
- OmiClient.getInstance(reactApplicationContext!!).addCallStateListener(autoUnregisterListener)
467
-
468
- OmiClient.getInstance(reactApplicationContext!!).setDebug(false)
453
+ val client = OmiClient.getInstance(reactApplicationContext!!)
454
+ client.addCallStateListener(this)
455
+ client.addCallStateListener(autoUnregisterListener)
456
+ client.setDebug(false)
469
457
  }
470
458
  }
471
459
 
@@ -482,7 +470,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
482
470
  OmiClient.getInstance(reactApplicationContext!!).setDebug(false)
483
471
  promise.resolve(true)
484
472
  } catch (e: Exception) {
485
- Log.e("OmikitPlugin", "❌ Error in startServices: ${e.message}", e)
486
473
  promise.resolve(false)
487
474
  }
488
475
  }
@@ -493,7 +480,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
493
480
  // ✅ Method để check status AUTO-UNREGISTER (DEPRECATED)
494
481
  @ReactMethod
495
482
  fun getAutoUnregisterStatus(promise: Promise) {
496
- Log.w("OmikitPlugin", "⚠️ DEPRECATED: getAutoUnregisterStatus() - Use Silent Registration API instead")
483
+
497
484
  try {
498
485
  OmiClient.getInstance(reactApplicationContext!!).getAutoUnregisterStatus { isScheduled, timeUntilExecution ->
499
486
  try {
@@ -504,12 +491,10 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
504
491
  )
505
492
  promise.resolve(Arguments.makeNativeMap(status))
506
493
  } catch (e: Exception) {
507
- Log.e("OmikitPlugin", "❌ Error in getAutoUnregisterStatus callback: ${e.message}", e)
508
494
  promise.resolve(null)
509
495
  }
510
496
  }
511
497
  } catch (e: Exception) {
512
- Log.e("OmikitPlugin", "❌ Error calling getAutoUnregisterStatus: ${e.message}", e)
513
498
  promise.resolve(null)
514
499
  }
515
500
  }
@@ -517,7 +502,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
517
502
  // ✅ Method để manually prevent AUTO-UNREGISTER (DEPRECATED)
518
503
  @ReactMethod
519
504
  fun preventAutoUnregister(reason: String, promise: Promise) {
520
- Log.w("OmikitPlugin", "⚠️ DEPRECATED: preventAutoUnregister() - No longer supported in new SDK version")
505
+
521
506
  // Function removed - no longer supported
522
507
  promise.resolve(false)
523
508
  }
@@ -525,47 +510,37 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
525
510
  // ✅ Convenience methods cho các scenario phổ biến (DEPRECATED)
526
511
  @ReactMethod
527
512
  fun prepareForIncomingCall(promise: Promise) {
528
- Log.w("OmikitPlugin", "⚠️ DEPRECATED: prepareForIncomingCall() - Use Silent Registration API instead")
529
513
  try {
530
514
  OmiClient.getInstance(reactApplicationContext!!).prepareForIncomingCall()
531
515
  promise.resolve(true)
532
516
  } catch (e: Exception) {
533
- Log.e("OmikitPlugin", "❌ Prepare for incoming call failed: ${e.message}", e)
534
517
  promise.resolve(false)
535
518
  }
536
519
  }
537
520
 
538
521
  @ReactMethod
539
522
  fun prepareForOutgoingCall(promise: Promise) {
540
- Log.w("OmikitPlugin", "⚠️ DEPRECATED: prepareForOutgoingCall() - Use Silent Registration API instead")
541
523
  try {
542
524
  OmiClient.getInstance(reactApplicationContext!!).prepareForOutgoingCall()
543
525
  promise.resolve(true)
544
526
  } catch (e: Exception) {
545
- Log.e("OmikitPlugin", "❌ Prepare for outgoing call failed: ${e.message}", e)
546
527
  promise.resolve(false)
547
528
  }
548
529
  }
549
530
 
550
531
  private fun prepareAudioSystem() {
551
532
  try {
552
- // ✅ Check network connectivity first
553
533
  if (!isNetworkAvailable()) {
554
534
  return
555
535
  }
556
-
536
+
557
537
  // Release any existing audio focus
558
538
  val audioManager = reactApplicationContext?.getSystemService(android.content.Context.AUDIO_SERVICE) as? android.media.AudioManager
559
539
  audioManager?.let {
560
- // Reset audio mode
561
540
  it.mode = android.media.AudioManager.MODE_NORMAL
562
541
  }
563
-
564
- // Small delay để audio system ổn định
565
- Thread.sleep(200)
566
-
567
542
  } catch (e: Exception) {
568
- Log.w("OmikitPlugin", "⚠️ Audio preparation warning: ${e.message}")
543
+ Log.w("OmikitPlugin", "Audio preparation warning: ${e.message}")
569
544
  }
570
545
  }
571
546
 
@@ -573,11 +548,17 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
573
548
  private fun isNetworkAvailable(): Boolean {
574
549
  return try {
575
550
  val connectivityManager = reactApplicationContext?.getSystemService(android.content.Context.CONNECTIVITY_SERVICE) as? android.net.ConnectivityManager
576
- val activeNetwork = connectivityManager?.activeNetworkInfo
577
- val isConnected = activeNetwork?.isConnectedOrConnecting == true
578
- isConnected
551
+ ?: return true
552
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
553
+ val network = connectivityManager.activeNetwork ?: return false
554
+ val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
555
+ capabilities.hasCapability(android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET)
556
+ } else {
557
+ @Suppress("DEPRECATION")
558
+ connectivityManager.activeNetworkInfo?.isConnectedOrConnecting == true
559
+ }
579
560
  } catch (e: Exception) {
580
- Log.w("OmikitPlugin", "⚠️ Network check failed: ${e.message}")
561
+ Log.w("OmikitPlugin", "Network check failed: ${e.message}")
581
562
  true // Assume network is available if check fails
582
563
  }
583
564
  }
@@ -614,22 +595,32 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
614
595
  return map
615
596
  }
616
597
 
617
- @RequiresApi(Build.VERSION_CODES.M)
618
598
  @ReactMethod
619
599
  fun systemAlertWindow(promise: Promise) {
600
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
601
+ promise.resolve(true)
602
+ return
603
+ }
620
604
  val result = Settings.canDrawOverlays(reactApplicationContext)
621
605
  promise.resolve(result)
622
606
  }
623
607
 
624
- @RequiresApi(Build.VERSION_CODES.M)
625
608
  @ReactMethod
626
609
  fun openSystemAlertSetting(promise: Promise) {
610
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
611
+ promise.resolve(true)
612
+ return
613
+ }
614
+ val ctx = reactApplicationContext ?: run {
615
+ promise.resolve(false)
616
+ return
617
+ }
627
618
  val intent = Intent(
628
619
  Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
629
- Uri.parse("package:" + reactApplicationContext.packageName)
620
+ Uri.parse("package:" + ctx.packageName)
630
621
  )
631
622
  intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
632
- reactApplicationContext.startActivity(intent)
623
+ ctx.startActivity(intent)
633
624
  promise.resolve(true)
634
625
  }
635
626
 
@@ -657,7 +648,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
657
648
  val audioNotificationDescription = data.getString("audioNotificationDescription") ?: "Cuộc gọi audio"
658
649
  val videoNotificationDescription = data.getString("videoNotificationDescription") ?: "Cuộc gọi video"
659
650
  val representName = data.getString("representName") ?: ""
660
- val isUserBusy = data.getBoolean("isUserBusy")
651
+ val isUserBusy = if (data.hasKey("isUserBusy")) data.getBoolean("isUserBusy") else false
661
652
 
662
653
  // Configure push notification with extracted parameters
663
654
  OmiClient.getInstance(context).configPushNotification(
@@ -681,15 +672,11 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
681
672
 
682
673
  // Configure decline call behavior
683
674
  OmiClient.getInstance(context).configureDeclineCallBehavior(isUserBusy)
684
-
685
- Log.d("OmikitPlugin", "✅ Push notification configured successfully")
686
675
  promise.resolve(true)
687
676
  } catch (e: Exception) {
688
- Log.e("OmikitPlugin", "❌ Error configuring push notification: ${e.message}", e)
689
677
  promise.reject("E_CONFIG_FAILED", "Failed to configure push notification", e)
690
678
  }
691
679
  } ?: run {
692
- Log.e("OmikitPlugin", "❌ Current activity is null")
693
680
  promise.reject("E_NULL_ACTIVITY", "Current activity is null")
694
681
  }
695
682
  } catch (e: Exception) {
@@ -704,32 +691,34 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
704
691
  val userName = data.getString("userName")
705
692
  val password = data.getString("password")
706
693
  val realm = data.getString("realm")
707
- val host = data.getString("host") ?: "vh.omicrm.com"
708
- val isVideo = data.getBoolean("isVideo")
694
+ val host = data.getString("host").let { if (it.isNullOrEmpty()) "vh.omicrm.com" else it }
695
+ val isVideo = if (data.hasKey("isVideo")) data.getBoolean("isVideo") else false
709
696
  val firebaseToken = data.getString("fcmToken")
710
697
  val projectId = data.getString("projectId") ?: ""
698
+ val isSkipDevices = if (data.hasKey("isSkipDevices")) data.getBoolean("isSkipDevices") else false
711
699
 
712
700
  // Validate required parameters
713
701
  if (!ValidationHelper.validateRequired(mapOf(
714
702
  "userName" to userName,
715
- "password" to password,
703
+ "password" to password,
716
704
  "realm" to realm,
717
705
  "fcmToken" to firebaseToken
718
706
  ), promise)) return@launch
719
707
 
720
708
  withContext(Dispatchers.Default) {
721
709
  try {
722
- // Cleanup trước khi register
710
+ // Cleanup before register using logout callback
723
711
  try {
724
- OmiClient.getInstance(reactApplicationContext!!).logout()
725
- delay(500) // Chờ cleanup hoàn tất
712
+ val logoutComplete = kotlinx.coroutines.CompletableDeferred<Unit>()
713
+ OmiClient.getInstance(reactApplicationContext!!).logout {
714
+ logoutComplete.complete(Unit)
715
+ }
716
+ // Wait for logout callback with timeout
717
+ kotlinx.coroutines.withTimeoutOrNull(3000) { logoutComplete.await() }
726
718
  } catch (e: Exception) {
727
- Log.w("OmikitPlugin", "⚠️ Cleanup warning (expected): ${e.message}")
719
+ Log.w("OmikitPlugin", "Cleanup warning (expected): ${e.message}")
728
720
  }
729
-
730
- // ✅ Sử dụng Silent Registration API mới từ OmiSDK 2.3.67
731
- Log.d("OmikitPlugin", "🔇 Using Silent Registration API for user: $userName")
732
-
721
+
733
722
  OmiClient.getInstance(reactApplicationContext!!).silentRegister(
734
723
  userName = userName ?: "",
735
724
  password = password ?: "",
@@ -737,26 +726,18 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
737
726
  isVideo = isVideo ?: true,
738
727
  firebaseToken = firebaseToken ?: "",
739
728
  host = host,
740
- projectId = projectId
729
+ projectId = projectId,
730
+ isSkipDevices = isSkipDevices
741
731
  ) { success, statusCode, message ->
742
- Log.d("OmikitPlugin", "🔇 Silent registration callback - success: $success, status: $statusCode, message: $message")
743
- if (success) {
744
- Log.d("OmikitPlugin", "✅ Silent registration successful - no notification, no auto-unregister")
745
- // ✅ Resolve promise với kết quả từ callback
746
- promise.resolve(success)
732
+ if (success || statusCode == 200) {
733
+ promise.resolve(true)
747
734
  } else {
748
- Log.e("OmikitPlugin", "❌ Silent registration failed: $message")
749
- if (statusCode == 200) {
750
- promise.resolve(true)
751
- } else {
752
- val (errorCode, errorMessage) = OmiRegistrationStatus.getError(statusCode)
753
- promise.reject(errorCode, "$errorMessage (Status: $statusCode)")
754
- }
735
+ val (errorCode, errorMessage) = OmiRegistrationStatus.getError(statusCode)
736
+ promise.reject(errorCode, "$errorMessage (Status: $statusCode)")
755
737
  }
756
738
  }
757
739
 
758
740
  } catch (e: Exception) {
759
- Log.e("OmikitPlugin", "❌ Error during silent registration: ${e.message}", e)
760
741
  promise.reject("ERROR_INITIALIZATION_EXCEPTION", "OMICALL initialization failed due to an unexpected error: ${e.message}. Please check your network connection and configuration.", e)
761
742
  }
762
743
  }
@@ -765,22 +746,18 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
765
746
 
766
747
  @ReactMethod
767
748
  fun initCallWithApiKey(data: ReadableMap, promise: Promise) {
768
- Log.d("OmikitPlugin", "🔑 initCallWithApiKey called")
769
749
  mainScope.launch {
770
750
  var loginResult = false
771
751
  val usrName = data.getString("fullName") ?: ""
772
752
  val usrUuid = data.getString("usrUuid") ?: ""
773
753
  val apiKey = data.getString("apiKey") ?: ""
774
- val isVideo = data.getBoolean("isVideo") ?: false
754
+ val isVideo = if (data.hasKey("isVideo")) data.getBoolean("isVideo") else false
775
755
  val phone = data.getString("phone")
776
756
  val firebaseToken = data.getString("fcmToken") ?: ""
777
757
  val projectId = data.getString("projectId") ?: ""
778
758
 
779
- Log.d("OmikitPlugin", "🔑 Parameters - usrName: $usrName, usrUuid: $usrUuid, isVideo: $isVideo")
780
-
781
759
  withContext(Dispatchers.Default) {
782
760
  try {
783
- Log.d("OmikitPlugin", "🔑 Starting validation")
784
761
  // Validate required parameters
785
762
  if (!ValidationHelper.validateRequired(mapOf(
786
763
  "fullName" to usrName,
@@ -792,8 +769,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
792
769
  return@withContext
793
770
  }
794
771
 
795
- Log.d("OmikitPlugin", "✅ Validation passed")
796
-
797
772
  // Check RECORD_AUDIO permission for Android 14+
798
773
  val hasRecordAudio = ContextCompat.checkSelfPermission(
799
774
  reactApplicationContext,
@@ -801,28 +776,22 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
801
776
  ) == PackageManager.PERMISSION_GRANTED
802
777
 
803
778
  if (!hasRecordAudio) {
804
- Log.e("OmikitPlugin", "❌ RECORD_AUDIO permission is required for Android 14+")
805
779
  promise.resolve(false)
806
780
  return@withContext
807
781
  }
808
-
809
- Log.d("OmikitPlugin", "✅ RECORD_AUDIO permission granted")
810
-
811
- // ✅ Cleanup trước khi register với mutex
782
+ // Cleanup before register using logout callback
812
783
  try {
813
- Log.d("OmikitPlugin", "🧹 Starting cleanup")
814
784
  omiClientMutex.withLock {
815
- OmiClient.getInstance(reactApplicationContext!!).logout()
785
+ val logoutComplete = kotlinx.coroutines.CompletableDeferred<Unit>()
786
+ OmiClient.getInstance(reactApplicationContext!!).logout {
787
+ logoutComplete.complete(Unit)
788
+ }
789
+ kotlinx.coroutines.withTimeoutOrNull(3000) { logoutComplete.await() }
816
790
  }
817
- delay(1000) // Chờ cleanup hoàn tất
818
- Log.d("OmikitPlugin", "✅ Cleanup completed")
819
791
  } catch (e: Exception) {
820
- Log.w("OmikitPlugin", "⚠️ Cleanup warning (expected): ${e.message}")
792
+ Log.w("OmikitPlugin", "Cleanup warning (expected): ${e.message}")
821
793
  }
822
794
 
823
- Log.d("OmikitPlugin", "🔑 Using API key registration for user: $usrName")
824
-
825
- Log.d("OmikitPlugin", "🔑 Calling OmiClient.registerWithApiKey...")
826
795
  omiClientMutex.withLock {
827
796
  loginResult = OmiClient.registerWithApiKey(
828
797
  apiKey ?: "",
@@ -834,16 +803,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
834
803
  projectId
835
804
  )
836
805
  }
837
-
838
- Log.d("OmikitPlugin", "🔑 OmiClient.registerWithApiKey returned: $loginResult")
839
-
840
- if (loginResult) {
841
- Log.d("OmikitPlugin", "✅ API key registration successful")
842
- promise.resolve(true)
843
- } else {
844
- Log.e("OmikitPlugin", "❌ API key registration failed")
845
- promise.resolve(false)
846
- }
806
+ promise.resolve(loginResult)
847
807
  } catch (e: Exception) {
848
808
  Log.e("OmikitPlugin", "❌ Error during API key registration: ${e.message}", e)
849
809
  promise.resolve(false)
@@ -855,21 +815,17 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
855
815
  @ReactMethod
856
816
  fun getInitialCall(counter: Int = 1, promise: Promise) {
857
817
  val context = reactApplicationContext ?: run {
858
- Log.e("getInitialCall", "❌ React context is null")
859
818
  promise.resolve(false)
860
819
  return
861
820
  }
862
821
 
863
822
  val call = Utils.getActiveCall(context)
864
- Log.d("getInitialCall RN", "📞 Active call: $call")
865
-
866
823
  if (call == null) {
867
824
  if (counter <= 0) {
868
825
  promise.resolve(false)
869
826
  } else {
870
827
  mainScope.launch {
871
- Log.d("getInitialCall RN", "🔄 Retrying in 2s... (Attempts left: $counter)")
872
- delay(1000) // Wait 2 seconds
828
+ delay(1000) // Wait 1 second before retry
873
829
  getInitialCall(counter - 1, promise) // Retry recursively
874
830
  }
875
831
  }
@@ -893,11 +849,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
893
849
  statusPendingCall == 5 // 5 = User clicked pickup (CONFIRMED)
894
850
 
895
851
  if (shouldAutoAnswer) {
896
- Log.d("getInitialCall RN", "🚀 AUTO-ANSWER: User clicked pickup (statusPendingCall=$statusPendingCall), answering call immediately")
897
852
  try {
898
853
  OmiClient.getInstance(context).pickUp()
899
- Log.d("getInitialCall RN", "✅ AUTO-ANSWER: Call answered successfully")
900
-
901
854
  // Status already cleared by getStatusPendingCall()
902
855
  } catch (e: Exception) {
903
856
  Log.e("getInitialCall RN", "❌ AUTO-ANSWER: Failed to answer call: ${e.message}", e)
@@ -922,8 +875,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
922
875
  promise.resolve(map)
923
876
 
924
877
  if (statusPendingCall == 2 && call.state != 5) {
925
- Log.d("getInitialCall RN", "🚀 Incoming Receive Triggered ($statusPendingCall)")
926
-
927
878
  val eventMap: WritableMap = WritableNativeMap().apply {
928
879
  putBoolean("isVideo", call.isVideo ?: false)
929
880
  putBoolean("incoming", true)
@@ -946,25 +897,34 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
946
897
  )
947
898
  val map: WritableMap = WritableNativeMap()
948
899
  if (audio == PackageManager.PERMISSION_GRANTED) {
949
- currentActivity?.runOnUiThread {
950
- val phoneNumber = data.getString("phoneNumber") as String
951
- val isVideo = data.getBoolean("isVideo") ?: false;
952
-
900
+ val activity = currentActivity
901
+ if (activity == null) {
902
+ promise.reject("E_NO_ACTIVITY", "Current activity is null")
903
+ return
904
+ }
905
+ activity.runOnUiThread {
906
+ val phoneNumber = data.getString("phoneNumber")
907
+ if (phoneNumber.isNullOrEmpty()) {
908
+ promise.reject("E_INVALID_PHONE", "Phone number is required")
909
+ return@runOnUiThread
910
+ }
911
+ val isVideo = if (data.hasKey("isVideo")) data.getBoolean("isVideo") else false
912
+
953
913
  val startCallResult =
954
914
  OmiClient.getInstance(reactApplicationContext!!).startCall(phoneNumber, isVideo)
955
- var statusCalltemp = startCallResult.value as Int;
915
+ var statusCalltemp = startCallResult.value as Int
956
916
  if (startCallResult.value == 200 || startCallResult.value == 407) {
957
917
  statusCalltemp = 8
958
918
  }
959
919
  map.putInt("status", statusCalltemp)
960
920
  map.putString("_id", "")
961
- map.putString("message", messageCall(startCallResult.value) as String)
921
+ map.putString("message", messageCall(startCallResult.value))
962
922
  promise.resolve(map)
963
923
  }
964
924
  } else {
965
925
  map.putInt("status", 4)
966
926
  map.putString("_id", "")
967
- map.putString("message", messageCall(406) as String)
927
+ map.putString("message", messageCall(406))
968
928
  promise.resolve(map)
969
929
  }
970
930
  }
@@ -976,28 +936,27 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
976
936
  reactApplicationContext!!,
977
937
  Manifest.permission.RECORD_AUDIO
978
938
  )
979
- Log.d("OMISDK", "📤 Start Call With UUID")
980
939
  val map: WritableMap = WritableNativeMap()
981
940
  if (audio == PackageManager.PERMISSION_GRANTED) {
982
941
  mainScope.launch {
983
942
  val uuid = data.getString("usrUuid") ?: ""
984
- val isVideo = data.getBoolean("isVideo") ?: false;
985
-
943
+ val isVideo = if (data.hasKey("isVideo")) data.getBoolean("isVideo") else false
944
+
986
945
  val startCallResult =
987
946
  OmiClient.getInstance(reactApplicationContext!!).startCallWithUuid(uuid, isVideo)
988
- var statusCalltemp = startCallResult.value as Int;
947
+ var statusCalltemp = startCallResult.value as Int
989
948
  if (startCallResult.value == 200 || startCallResult.value == 407) {
990
949
  statusCalltemp = 8
991
950
  }
992
951
  map.putInt("status", statusCalltemp)
993
952
  map.putString("_id", "")
994
- map.putString("message", messageCall(startCallResult.value) as String)
953
+ map.putString("message", messageCall(startCallResult.value))
995
954
  promise.resolve(map)
996
955
  }
997
956
  } else {
998
957
  map.putInt("status", 4)
999
958
  map.putString("_id", "")
1000
- map.putString("message", messageCall(406) as String)
959
+ map.putString("message", messageCall(406))
1001
960
  promise.resolve(map)
1002
961
  }
1003
962
  }
@@ -1008,7 +967,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1008
967
  val appContext = reactApplicationContext.applicationContext
1009
968
  val activity = currentActivity
1010
969
 
1011
- if (appContext == null) {
970
+ if (appContext == null) {
1012
971
  promise.reject("E_NULL_CONTEXT", "Application context is null")
1013
972
  return
1014
973
  }
@@ -1044,15 +1003,11 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1044
1003
 
1045
1004
  @ReactMethod
1046
1005
  fun rejectCall(promise: Promise) {
1047
- Log.d("OMISDK", "➡️ rejectCall called - isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
1048
1006
  if (isIncoming) {
1049
- Log.d("OMISDK", "📞 Incoming call")
1050
1007
  ValidationHelper.safeOmiClientAccess(reactApplicationContext!!) { omiClient ->
1051
1008
  if (!isAnswerCall) {
1052
- Log.d("OMISDK", "🚫 Declining call with declineWithCode(true)")
1053
1009
  omiClient.declineWithCode(true) // 486 Busy Here
1054
1010
  } else {
1055
- Log.d("OMISDK", "📴 Call already answered, hanging up")
1056
1011
  omiClient.hangUp()
1057
1012
  }
1058
1013
  }
@@ -1060,7 +1015,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1060
1015
  markCallEnded()
1061
1016
  promise.resolve(true)
1062
1017
  } else {
1063
- Log.d("OMISDK", "📤 Not incoming call, skipping reject")
1064
1018
  promise.resolve(false)
1065
1019
  }
1066
1020
  }
@@ -1120,7 +1074,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1120
1074
 
1121
1075
  @ReactMethod
1122
1076
  fun toggleSpeaker(promise: Promise) {
1123
- currentActivity?.runOnUiThread {
1077
+ val activity = currentActivity
1078
+ if (activity == null) { promise.resolve(null); return }
1079
+ activity.runOnUiThread {
1124
1080
  val newStatus = OmiClient.getInstance(reactApplicationContext!!).toggleSpeaker()
1125
1081
  promise.resolve(newStatus)
1126
1082
  sendEvent(SPEAKER, newStatus)
@@ -1129,7 +1085,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1129
1085
 
1130
1086
  @ReactMethod
1131
1087
  fun sendDTMF(data: ReadableMap, promise: Promise) {
1132
- currentActivity?.runOnUiThread {
1088
+ val activity = currentActivity
1089
+ if (activity == null) { promise.resolve(false); return }
1090
+ activity.runOnUiThread {
1133
1091
  val character = data.getString("character")
1134
1092
  var characterCode: Int? = character?.toIntOrNull()
1135
1093
  if (character == "*") {
@@ -1147,7 +1105,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1147
1105
 
1148
1106
  @ReactMethod
1149
1107
  fun switchOmiCamera(promise: Promise) {
1150
- currentActivity?.runOnUiThread {
1108
+ val activity = currentActivity
1109
+ if (activity == null) { promise.resolve(false); return }
1110
+ activity.runOnUiThread {
1151
1111
  OmiClient.getInstance(reactApplicationContext!!).switchCamera()
1152
1112
  promise.resolve(true)
1153
1113
  }
@@ -1155,7 +1115,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1155
1115
 
1156
1116
  @ReactMethod
1157
1117
  fun toggleOmiVideo(promise: Promise) {
1158
- currentActivity?.runOnUiThread {
1118
+ val activity = currentActivity
1119
+ if (activity == null) { promise.resolve(false); return }
1120
+ activity.runOnUiThread {
1159
1121
  OmiClient.getInstance(reactApplicationContext!!).toggleCamera()
1160
1122
  promise.resolve(true)
1161
1123
  }
@@ -1261,14 +1223,111 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1261
1223
  }
1262
1224
  }
1263
1225
 
1226
+ // MARK: - Getter Functions
1227
+ @ReactMethod
1228
+ fun getProjectId(promise: Promise) {
1229
+ try {
1230
+ val info = OmiClient.registrationInfo
1231
+ if (info?.projectId != null) {
1232
+ promise.resolve(info.projectId)
1233
+ return
1234
+ }
1235
+ // Fallback: get from Firebase project ID
1236
+ val firebaseProjectId = try {
1237
+ com.google.firebase.FirebaseApp.getInstance().options.projectId
1238
+ } catch (e: Exception) { null }
1239
+ promise.resolve(firebaseProjectId)
1240
+ } catch (e: Throwable) {
1241
+ promise.resolve(null)
1242
+ }
1243
+ }
1244
+
1245
+ @ReactMethod
1246
+ fun getAppId(promise: Promise) {
1247
+ try {
1248
+ val info = OmiClient.registrationInfo
1249
+ if (info?.appId != null) {
1250
+ promise.resolve(info.appId)
1251
+ return
1252
+ }
1253
+ // Fallback: get package name of the host app
1254
+ promise.resolve(reactApplicationContext?.packageName)
1255
+ } catch (e: Throwable) {
1256
+ promise.resolve(null)
1257
+ }
1258
+ }
1259
+
1260
+ @ReactMethod
1261
+ fun getDeviceId(promise: Promise) {
1262
+ try {
1263
+ val info = OmiClient.registrationInfo
1264
+ if (info?.deviceId != null) {
1265
+ promise.resolve(info.deviceId)
1266
+ return
1267
+ }
1268
+ // Fallback: get Android ID directly
1269
+ val androidId = try {
1270
+ Settings.Secure.getString(
1271
+ reactApplicationContext?.contentResolver,
1272
+ Settings.Secure.ANDROID_ID
1273
+ )
1274
+ } catch (e: Exception) { null }
1275
+ promise.resolve(androidId)
1276
+ } catch (e: Throwable) {
1277
+ promise.resolve(null)
1278
+ }
1279
+ }
1280
+
1281
+ @ReactMethod
1282
+ fun getFcmToken(promise: Promise) {
1283
+ try {
1284
+ val info = OmiClient.registrationInfo
1285
+ if (info?.firebaseToken != null) {
1286
+ promise.resolve(info.firebaseToken)
1287
+ return
1288
+ }
1289
+ // Fallback: get FCM token directly from Firebase Messaging
1290
+ try {
1291
+ com.google.firebase.messaging.FirebaseMessaging.getInstance().token
1292
+ .addOnSuccessListener { token -> promise.resolve(token) }
1293
+ .addOnFailureListener { promise.resolve(null) }
1294
+ } catch (e: Exception) {
1295
+ promise.resolve(null)
1296
+ }
1297
+ } catch (e: Throwable) {
1298
+ promise.resolve(null)
1299
+ }
1300
+ }
1301
+
1302
+ @ReactMethod
1303
+ fun getSipInfo(promise: Promise) {
1304
+ try {
1305
+ val sipUser = OmiClient.getInstance(reactApplicationContext!!).getSipUser()
1306
+ val sipRealm = OmiClient.getInstance(reactApplicationContext!!).getSipRealm()
1307
+ if (!sipUser.isNullOrEmpty() && !sipRealm.isNullOrEmpty()) {
1308
+ promise.resolve("$sipUser@$sipRealm")
1309
+ } else {
1310
+ promise.resolve(sipUser)
1311
+ }
1312
+ } catch (e: Throwable) {
1313
+ promise.resolve(null)
1314
+ }
1315
+ }
1316
+
1317
+ @ReactMethod
1318
+ fun getVoipToken(promise: Promise) {
1319
+ // VoIP token is iOS only, Android returns null
1320
+ promise.resolve(null)
1321
+ }
1322
+
1264
1323
  @ReactMethod
1265
1324
  fun getAudio(promise: Promise) {
1266
1325
  val inputs = OmiClient.getInstance(reactApplicationContext!!).getAudioOutputs()
1267
1326
  val writeList = WritableNativeArray()
1268
1327
  inputs.forEach {
1269
1328
  val map = WritableNativeMap()
1270
- map.putString("name", it["name"] as String)
1271
- map.putInt("type", it["type"] as Int)
1329
+ map.putString("name", it["name"] as? String ?: "")
1330
+ map.putInt("type", it["type"] as? Int ?: 0)
1272
1331
  writeList.pushMap(map)
1273
1332
  }
1274
1333
  promise.resolve(writeList)
@@ -1278,8 +1337,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1278
1337
  fun getCurrentAudio(promise: Promise) {
1279
1338
  val currentAudio = OmiClient.getInstance(reactApplicationContext!!).getCurrentAudio()
1280
1339
  val map: WritableMap = WritableNativeMap()
1281
- map.putString("name", currentAudio["name"] as String)
1282
- map.putInt("type", currentAudio["type"] as Int)
1340
+ map.putString("name", currentAudio["name"] as? String ?: "")
1341
+ map.putInt("type", currentAudio["type"] as? Int ?: 0)
1283
1342
  val writeList = WritableNativeArray()
1284
1343
  writeList.pushMap(map)
1285
1344
  promise.resolve(writeList)
@@ -1294,14 +1353,16 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1294
1353
 
1295
1354
  @ReactMethod
1296
1355
  fun transferCall(data: ReadableMap, promise: Promise) {
1297
- currentActivity?.runOnUiThread {
1356
+ val activity = currentActivity
1357
+ if (activity == null) { promise.resolve(false); return }
1358
+ activity.runOnUiThread {
1298
1359
  val phone = data.getString("phoneNumber")
1299
- Log.d("phone", "phone transferCall ==>> ${phone} ")
1300
- if (reactApplicationContext != null) {
1301
- Log.d("phone", "phone transferCall reactApplicationContext ==>> ${phone} ")
1302
- OmiClient.getInstance(reactApplicationContext!!).forwardCallTo(phone as String)
1303
- promise.resolve(true)
1360
+ if (phone.isNullOrEmpty()) {
1361
+ promise.reject("E_INVALID_PHONE", "Phone number is required for transfer")
1362
+ return@runOnUiThread
1304
1363
  }
1364
+ OmiClient.getInstance(reactApplicationContext!!).forwardCallTo(phone)
1365
+ promise.resolve(true)
1305
1366
  }
1306
1367
  }
1307
1368
 
@@ -1310,6 +1371,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1310
1371
  const val REQUEST_PERMISSIONS_CODE = 1001
1311
1372
  const val REQUEST_OVERLAY_PERMISSION_CODE = 1002
1312
1373
 
1374
+ // Singleton reference for companion methods to access module instance
1375
+ @Volatile var moduleInstance: OmikitPluginModule? = null
1376
+
1313
1377
  fun onDestroy() {
1314
1378
  try {
1315
1379
  // Cleanup OmiClient resources safely
@@ -1320,7 +1384,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1320
1384
 
1321
1385
  fun onResume(act: ReactActivity) {
1322
1386
  act.let { context ->
1323
- Log.d("OMISDK_REACT", "=>> onResume => ")
1324
1387
  OmiClient.getInstance(context, true)
1325
1388
  OmiClient.isAppReady = true;
1326
1389
  }
@@ -1349,37 +1412,28 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1349
1412
  act: ReactActivity
1350
1413
  ) {
1351
1414
  try {
1352
- val deniedPermissions = mutableListOf<String>()
1353
1415
  val grantedPermissions = mutableListOf<String>()
1354
-
1416
+
1355
1417
  for (i in permissions.indices) {
1356
1418
  if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
1357
1419
  grantedPermissions.add(permissions[i])
1358
- } else {
1359
- deniedPermissions.add(permissions[i])
1360
1420
  }
1361
1421
  }
1362
-
1363
- Log.d("OmikitPlugin", "✅ Granted: ${grantedPermissions.joinToString()}")
1364
- if (deniedPermissions.isNotEmpty()) {
1365
- Log.w("OmikitPlugin", "❌ Denied: ${deniedPermissions.joinToString()}")
1366
- }
1367
-
1422
+
1368
1423
  // Check if we have essential permissions for VoIP
1369
1424
  val hasRecordAudio = grantedPermissions.contains(Manifest.permission.RECORD_AUDIO)
1370
1425
  val hasCallPhone = grantedPermissions.contains(Manifest.permission.CALL_PHONE)
1371
1426
  val hasModifyAudio = grantedPermissions.contains(Manifest.permission.MODIFY_AUDIO_SETTINGS)
1372
-
1427
+
1373
1428
  val canProceed = hasRecordAudio && hasCallPhone && hasModifyAudio
1374
-
1375
- if (canProceed) {
1376
- Log.d("OmikitPlugin", "🎉 Essential VoIP permissions granted!")
1377
- } else {
1378
- Log.e("OmikitPlugin", "⚠️ Missing essential VoIP permissions - app may not work properly")
1379
- }
1380
-
1429
+
1430
+ // Resolve the stored permission promise
1431
+ moduleInstance?.permissionPromise?.resolve(canProceed)
1432
+ moduleInstance?.permissionPromise = null
1381
1433
  } catch (e: Exception) {
1382
- Log.e("OmikitPlugin", "Error handling permission results: ${e.message}", e)
1434
+ Log.e("OmikitPlugin", "Error handling permission results: ${e.message}", e)
1435
+ moduleInstance?.permissionPromise?.resolve(false)
1436
+ moduleInstance?.permissionPromise = null
1383
1437
  }
1384
1438
  }
1385
1439
 
@@ -1392,7 +1446,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1392
1446
  try {
1393
1447
  val isIncoming = intent.getBooleanExtra(SipServiceConstants.ACTION_IS_INCOMING_CALL, false)
1394
1448
  if (!isIncoming) {
1395
- Log.d("PICKUP-FIX", "Not an incoming call intent, skipping")
1396
1449
  return
1397
1450
  }
1398
1451
 
@@ -1400,20 +1453,17 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1400
1453
  SipServiceConstants.ACTION_ACCEPT_INCOMING_CALL, false
1401
1454
  )
1402
1455
 
1403
- Log.d("PICKUP-FIX", "🚀 Early intent handler - isIncoming: $isIncoming, isAccepted: $isAcceptedCall")
1404
1456
 
1405
1457
  // Save to SharedPreferences so getInitialCall() can detect it later
1406
1458
  // setStatusPendingCall(true) → saves status=5 (CONFIRMED)
1407
1459
  // setStatusPendingCall(false) → saves status=2 (INCOMING)
1408
1460
  OmiKitUtils().setStatusPendingCall(act, isAcceptedCall)
1409
- Log.d("PICKUP-FIX", "✅ Saved pickup state to SharedPreferences (isAccepted=$isAcceptedCall)")
1410
1461
 
1411
1462
  if (isAcceptedCall) {
1412
1463
  // Try to answer immediately if possible (may fail if SDK not ready)
1413
1464
  try {
1414
1465
  OmiClient.getInstance(act, true)?.let { client ->
1415
1466
  client.pickUp()
1416
- Log.d("PICKUP-FIX", "✅ Successfully answered call immediately")
1417
1467
  } ?: Log.w("PICKUP-FIX", "⚠️ OmiClient not ready, will auto-answer in getInitialCall()")
1418
1468
  } catch (e: Exception) {
1419
1469
  Log.w("PICKUP-FIX", "⚠️ Cannot answer immediately (SDK not ready): ${e.message}. Will auto-answer in getInitialCall()")
@@ -1440,7 +1490,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1440
1490
  )
1441
1491
  if (isReopenCall) {
1442
1492
  val activeCall = Utils.getActiveCall(context)
1443
- OmikitPluginModule(context).onCallEstablished(
1493
+ // Use singleton module instance instead of creating a throwaway one
1494
+ moduleInstance?.onCallEstablished(
1444
1495
  activeCall?.id ?: 0,
1445
1496
  activeCall?.remoteNumber,
1446
1497
  activeCall?.isVideo,
@@ -1453,7 +1504,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1453
1504
  }
1454
1505
  OmiKitUtils().setStatusPendingCall(context, isAcceptedCall)
1455
1506
  }
1456
-
1457
1507
  }
1458
1508
  }
1459
1509
  }
@@ -1461,19 +1511,16 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1461
1511
  // ✅ Di chuyển sendEvent vào trong class để có thể access reactApplicationContext
1462
1512
  private fun sendEvent(eventName: String?, params: Any?) {
1463
1513
  if (eventName == null) {
1464
- Log.e("OmikitPlugin", "❌ eventName is null or empty. Không thể gửi event.")
1465
1514
  return
1466
1515
  }
1467
1516
 
1468
1517
  try {
1469
1518
  // ✅ Kiểm tra reactApplicationContext
1470
1519
  if (reactApplicationContext == null) {
1471
- Log.e("OmikitPlugin", "❌ reactApplicationContext is null")
1472
1520
  return
1473
1521
  }
1474
1522
 
1475
1523
  if (!reactApplicationContext.hasActiveReactInstance()) {
1476
- Log.w("OmikitPlugin", "⚠️ ReactApplicationContext không có active React instance")
1477
1524
  return
1478
1525
  }
1479
1526
 
@@ -1498,45 +1545,29 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1498
1545
  }
1499
1546
  }
1500
1547
 
1501
- // ✅ Thêm method để React Native biết các event được hỗ trợ
1502
- override fun getConstants(): MutableMap<String, Any> {
1503
- return hashMapOf(
1504
- "CALL_STATE_CHANGED" to CALL_STATE_CHANGED,
1505
- "MUTED" to MUTED,
1506
- "HOLD" to HOLD,
1507
- "SPEAKER" to SPEAKER,
1508
- "CALL_QUALITY" to CALL_QUALITY,
1509
- "AUDIO_CHANGE" to AUDIO_CHANGE,
1510
- "SWITCHBOARD_ANSWER" to SWITCHBOARD_ANSWER,
1511
- "REQUEST_PERMISSION" to REQUEST_PERMISSION,
1512
- "CLICK_MISSED_CALL" to CLICK_MISSED_CALL,
1513
- "AUTO_UNREGISTER_STATUS" to "AUTO_UNREGISTER_STATUS"
1514
- )
1515
- }
1516
-
1517
1548
  @ReactMethod
1518
1549
  fun checkAndRequestPermissions(isVideo: Boolean, promise: Promise) {
1519
1550
  try {
1520
1551
  val missingPermissions = getMissingPermissions(isVideo)
1521
1552
 
1522
1553
  if (missingPermissions.isEmpty()) {
1523
- Log.d("OmikitPlugin", "✅ All permissions already granted")
1524
1554
  promise.resolve(true)
1525
1555
  return
1526
1556
  }
1527
1557
 
1528
- Log.d("OmikitPlugin", "📋 Missing permissions: ${missingPermissions.joinToString()}")
1529
-
1530
1558
  // Store promise for callback
1531
1559
  permissionPromise = promise
1532
1560
 
1561
+ val activity = reactApplicationContext?.currentActivity ?: run {
1562
+ promise.resolve(false)
1563
+ return
1564
+ }
1533
1565
  ActivityCompat.requestPermissions(
1534
- reactApplicationContext.currentActivity!!,
1566
+ activity,
1535
1567
  missingPermissions.toTypedArray(),
1536
1568
  REQUEST_PERMISSIONS_CODE,
1537
1569
  )
1538
1570
  } catch (e: Exception) {
1539
- Log.e("OmikitPlugin", "❌ Error checking permissions: ${e.message}", e)
1540
1571
  promise.resolve(false)
1541
1572
  }
1542
1573
  }
@@ -1621,15 +1652,16 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1621
1652
  true
1622
1653
  }
1623
1654
 
1624
- permissionStatus["essentialGranted"] = grantedEssential
1625
- permissionStatus["essentialMissing"] = missingEssential
1626
- permissionStatus["canMakeVoipCalls"] = missingEssential.isEmpty()
1627
- permissionStatus["foregroundServicePermissions"] = foregroundServiceStatus
1628
- permissionStatus["canDrawOverlays"] = canDrawOverlays
1629
- permissionStatus["androidVersion"] = Build.VERSION.SDK_INT
1630
- permissionStatus["targetSdk"] = reactApplicationContext.applicationInfo.targetSdkVersion
1631
-
1632
- val map = Arguments.makeNativeMap(permissionStatus)
1655
+ val map: WritableMap = WritableNativeMap()
1656
+ map.putArray("essentialGranted", Arguments.fromList(grantedEssential.toList()))
1657
+ map.putArray("essentialMissing", Arguments.fromList(missingEssential.toList()))
1658
+ map.putBoolean("canMakeVoipCalls", missingEssential.isEmpty())
1659
+ val fgServiceMap: WritableMap = WritableNativeMap()
1660
+ foregroundServiceStatus.forEach { (k, v) -> fgServiceMap.putBoolean(k, v) }
1661
+ map.putMap("foregroundServicePermissions", fgServiceMap)
1662
+ map.putBoolean("canDrawOverlays", canDrawOverlays)
1663
+ map.putInt("androidVersion", Build.VERSION.SDK_INT)
1664
+ map.putInt("targetSdk", reactApplicationContext.applicationInfo.targetSdkVersion)
1633
1665
  promise.resolve(map)
1634
1666
 
1635
1667
  } catch (e: Exception) {
@@ -1704,48 +1736,54 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1704
1736
  // Store promise for callback
1705
1737
  permissionPromise = promise
1706
1738
 
1707
- Log.d("OmikitPlugin", "🔐 Requesting permissions for codes ${permissionCodes.joinToString()}: ${permissionsToRequest.joinToString()}")
1708
-
1739
+ val activity = reactApplicationContext?.currentActivity ?: run {
1740
+ promise.reject("E_NULL_ACTIVITY", "Current activity is null")
1741
+ return
1742
+ }
1709
1743
  ActivityCompat.requestPermissions(
1710
- reactApplicationContext.currentActivity!!,
1744
+ activity,
1711
1745
  permissionsToRequest.toTypedArray(),
1712
1746
  REQUEST_PERMISSIONS_CODE
1713
1747
  )
1714
1748
 
1715
1749
  } catch (e: Exception) {
1716
- Log.e("OmikitPlugin", "❌ Error requesting permissions by codes: ${e.message}", e)
1717
1750
  promise.reject("ERROR_PERMISSION_REQUEST", "Failed to request permissions: ${e.message}")
1718
1751
  }
1719
1752
  }
1720
1753
 
1721
1754
  private fun requestPermission(isVideo: Boolean) {
1722
1755
  val missingPermissions = getMissingPermissions(isVideo)
1723
-
1756
+
1724
1757
  if (missingPermissions.isEmpty()) {
1725
- Log.d("OmikitPlugin", "✅ All permissions already granted")
1726
1758
  return
1727
1759
  }
1728
-
1729
- Log.d("OmikitPlugin", "📋 Requesting missing permissions for Android ${Build.VERSION.SDK_INT}: ${missingPermissions.joinToString()}")
1730
-
1760
+
1761
+ val activity = reactApplicationContext?.currentActivity ?: return
1731
1762
  ActivityCompat.requestPermissions(
1732
- reactApplicationContext.currentActivity!!,
1763
+ activity,
1733
1764
  missingPermissions.toTypedArray(),
1734
1765
  REQUEST_PERMISSIONS_CODE,
1735
1766
  )
1736
1767
  }
1737
1768
 
1738
- override fun onActivityResult(p0: Activity?, p1: Int, p2: Int, p3: Intent?) {
1769
+ override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
1770
+ if (requestCode == REQUEST_OVERLAY_PERMISSION_CODE) {
1771
+ val granted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
1772
+ Settings.canDrawOverlays(reactApplicationContext)
1773
+ } else {
1774
+ true
1775
+ }
1776
+ permissionPromise?.resolve(granted)
1777
+ permissionPromise = null
1778
+ }
1739
1779
  }
1740
1780
 
1741
- override fun onNewIntent(p0: Intent?) {
1742
- if (p0 != null && p0.hasExtra(SipServiceConstants.PARAM_IS_MISSED_CALL)) {
1743
- //do your Stuff
1744
-
1745
- if (p0.getBooleanExtra(SipServiceConstants.PARAM_IS_MISSED_CALL, false)) {
1781
+ override fun onNewIntent(intent: Intent) {
1782
+ if (intent.hasExtra(SipServiceConstants.PARAM_IS_MISSED_CALL)) {
1783
+ if (intent.getBooleanExtra(SipServiceConstants.PARAM_IS_MISSED_CALL, false)) {
1746
1784
  val map: WritableMap = WritableNativeMap()
1747
- map.putString("callerNumber", p0.getStringExtra(SipServiceConstants.PARAM_NUMBER) ?: "")
1748
- map.putBoolean("isVideo", p0.getBooleanExtra(SipServiceConstants.PARAM_IS_VIDEO, false))
1785
+ map.putString("callerNumber", intent.getStringExtra(SipServiceConstants.PARAM_NUMBER) ?: "")
1786
+ map.putBoolean("isVideo", intent.getBooleanExtra(SipServiceConstants.PARAM_IS_VIDEO, false))
1749
1787
  sendEvent(CLICK_MISSED_CALL, map)
1750
1788
  }
1751
1789
  }
@@ -1760,16 +1798,10 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1760
1798
  }
1761
1799
  }
1762
1800
 
1763
- // Thêm listener cho AUTO-UNREGISTER status
1801
+ // Auto-unregister listener - only handles onAutoUnregisterStatus
1802
+ // Other callbacks are NO-OP to prevent double-firing events (Fix #1)
1764
1803
  private val autoUnregisterListener = object : OmiListener {
1765
1804
  override fun onAutoUnregisterStatus(isScheduled: Boolean, timeUntilExecution: Long) {
1766
- // ✅ Auto-unregister prevention removed - no longer supported in new SDK
1767
- if (isScheduled && timeUntilExecution > 0 && timeUntilExecution < 3000) {
1768
- Log.w("OmikitPlugin", "🚨 AUTO-UNREGISTER sắp thực hiện trong ${timeUntilExecution}ms - SDK tự xử lý")
1769
- // preventAutoUnregisterCrash deprecated - SDK handles automatically
1770
- }
1771
-
1772
- // ✅ Gửi event cho React Native
1773
1805
  try {
1774
1806
  val statusData = mapOf(
1775
1807
  "isScheduled" to isScheduled,
@@ -1779,94 +1811,48 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1779
1811
  val map = createSafeWritableMap(statusData)
1780
1812
  sendEvent("AUTO_UNREGISTER_STATUS", map)
1781
1813
  } catch (e: Exception) {
1782
- Log.e("OmikitPlugin", "Error sending AUTO_UNREGISTER_STATUS event: ${e.message}", e)
1814
+ Log.e("OmikitPlugin", "Error sending AUTO_UNREGISTER_STATUS event: ${e.message}", e)
1783
1815
  }
1784
1816
  }
1785
-
1786
- // Implement các method khác của OmiListener (delegate to main listener)
1787
- override fun incomingReceived(callerId: Int?, phoneNumber: String?, isVideo: Boolean?) {
1788
- this@OmikitPluginModule.incomingReceived(callerId, phoneNumber, isVideo)
1789
- }
1790
-
1791
- override fun onCallEstablished(callerId: Int, phoneNumber: String?, isVideo: Boolean?, startTime: Long, transactionId: String?) {
1792
- this@OmikitPluginModule.onCallEstablished(callerId, phoneNumber, isVideo, startTime, transactionId)
1793
- }
1794
-
1795
- override fun onCallEnd(callInfo: MutableMap<String, Any?>, statusCode: Int) {
1796
- this@OmikitPluginModule.onCallEnd(callInfo, statusCode)
1797
- }
1798
-
1799
- override fun onConnecting() {
1800
- this@OmikitPluginModule.onConnecting()
1801
- }
1802
-
1803
- override fun onDescriptionError() {
1804
- this@OmikitPluginModule.onDescriptionError()
1805
- }
1806
-
1807
- override fun onFcmReceived(uuid: String, userName: String, avatar: String) {
1808
- this@OmikitPluginModule.onFcmReceived(uuid, userName, avatar)
1809
- }
1810
-
1811
- override fun onRinging(callerId: Int, transactionId: String?) {
1812
- this@OmikitPluginModule.onRinging(callerId, transactionId)
1813
- }
1814
-
1815
- override fun networkHealth(stat: Map<String, *>, quality: Int) {
1816
- this@OmikitPluginModule.networkHealth(stat, quality)
1817
- }
1818
-
1819
- override fun onAudioChanged(audioInfo: Map<String, Any>) {
1820
- this@OmikitPluginModule.onAudioChanged(audioInfo)
1821
- }
1822
-
1823
- override fun onHold(isHold: Boolean) {
1824
- this@OmikitPluginModule.onHold(isHold)
1825
- }
1826
-
1827
- override fun onMuted(isMuted: Boolean) {
1828
- this@OmikitPluginModule.onMuted(isMuted)
1829
- }
1830
-
1831
- override fun onOutgoingStarted(callerId: Int, phoneNumber: String?, isVideo: Boolean?) {
1832
- this@OmikitPluginModule.onOutgoingStarted(callerId, phoneNumber, isVideo)
1833
- }
1834
-
1835
- override fun onSwitchBoardAnswer(sip: String) {
1836
- this@OmikitPluginModule.onSwitchBoardAnswer(sip)
1837
- }
1838
-
1839
- override fun onRegisterCompleted(statusCode: Int) {
1840
- this@OmikitPluginModule.onRegisterCompleted(statusCode)
1841
- }
1842
-
1843
- override fun onRequestPermission(permissions: Array<String>) {
1844
- this@OmikitPluginModule.onRequestPermission(permissions)
1845
- }
1846
-
1847
- override fun onVideoSize(width: Int, height: Int) {
1848
- this@OmikitPluginModule.onVideoSize(width, height)
1849
- }
1817
+
1818
+ // NO-OP: main module listener handles these - avoid double-firing
1819
+ override fun incomingReceived(callerId: Int?, phoneNumber: String?, isVideo: Boolean?) {}
1820
+ override fun onCallEstablished(callerId: Int, phoneNumber: String?, isVideo: Boolean?, startTime: Long, transactionId: String?) {}
1821
+ override fun onCallEnd(callInfo: MutableMap<String, Any?>, statusCode: Int) {}
1822
+ override fun onConnecting() {}
1823
+ override fun onDescriptionError() {}
1824
+ override fun onFcmReceived(uuid: String, userName: String, avatar: String) {}
1825
+ override fun onRinging(callerId: Int, transactionId: String?) {}
1826
+ override fun networkHealth(stat: Map<String, *>, quality: Int) {}
1827
+ override fun onAudioChanged(audioInfo: Map<String, Any>) {}
1828
+ override fun onHold(isHold: Boolean) {}
1829
+ override fun onMuted(isMuted: Boolean) {}
1830
+ override fun onOutgoingStarted(callerId: Int, phoneNumber: String?, isVideo: Boolean?) {}
1831
+ override fun onSwitchBoardAnswer(sip: String) {}
1832
+ override fun onRegisterCompleted(statusCode: Int) {}
1833
+ override fun onRequestPermission(permissions: Array<String>) {}
1834
+ override fun onVideoSize(width: Int, height: Int) {}
1850
1835
  }
1851
1836
 
1852
1837
  // ✅ Helper function để hide notification một cách an toàn
1853
1838
  @ReactMethod
1854
1839
  fun hideSystemNotificationSafely(promise: Promise) {
1855
1840
  try {
1856
- // Delay 2 giây để đảm bảo registration hoàn tất
1857
- Handler(Looper.getMainLooper()).postDelayed({
1841
+ val context = reactApplicationContext ?: run {
1842
+ promise.resolve(false)
1843
+ return
1844
+ }
1845
+ // Delay to ensure registration completes before hiding
1846
+ mainScope.launch {
1858
1847
  try {
1859
- // ✅ Gọi function hide notification với error handling
1860
- OmiClient.getInstance(reactApplicationContext!!).hideSystemNotificationAndUnregister("Registration check completed")
1861
- Log.d("OmikitPlugin", "✅ Successfully hidden system notification and unregistered")
1848
+ delay(2000)
1849
+ OmiClient.getInstance(context).hideSystemNotificationAndUnregister("Registration check completed")
1862
1850
  promise.resolve(true)
1863
1851
  } catch (e: Exception) {
1864
- Log.e("OmikitPlugin", "❌ Failed to hide system notification: ${e.message}", e)
1865
1852
  promise.resolve(false)
1866
1853
  }
1867
- }, 2000) // Delay 2 giây
1854
+ }
1868
1855
  } catch (e: Exception) {
1869
- Log.e("OmikitPlugin", "❌ Error in hideSystemNotificationSafely: ${e.message}", e)
1870
1856
  promise.resolve(false)
1871
1857
  }
1872
1858
  }
@@ -1876,10 +1862,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1876
1862
  fun hideSystemNotificationOnly(promise: Promise) {
1877
1863
  try {
1878
1864
  OmiClient.getInstance(reactApplicationContext!!).hideSystemNotification()
1879
- Log.d("OmikitPlugin", "✅ Successfully hidden system notification (keeping registration)")
1880
1865
  promise.resolve(true)
1881
1866
  } catch (e: Exception) {
1882
- Log.e("OmikitPlugin", "❌ Failed to hide system notification only: ${e.message}", e)
1883
1867
  promise.resolve(false)
1884
1868
  }
1885
1869
  }
@@ -1889,10 +1873,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1889
1873
  fun hideSystemNotificationAndUnregister(reason: String, promise: Promise) {
1890
1874
  try {
1891
1875
  OmiClient.getInstance(reactApplicationContext!!).hideSystemNotificationAndUnregister(reason)
1892
- Log.d("OmikitPlugin", "✅ Successfully hidden notification and unregistered: $reason")
1893
1876
  promise.resolve(true)
1894
1877
  } catch (e: Exception) {
1895
- Log.e("OmikitPlugin", "❌ Failed to hide notification and unregister: ${e.message}", e)
1896
1878
  promise.resolve(false)
1897
1879
  }
1898
1880
  }
@@ -1904,21 +1886,18 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1904
1886
  val userName = data.getString("userName")
1905
1887
  val password = data.getString("password")
1906
1888
  val realm = data.getString("realm")
1907
- val host = data.getString("host") ?: "vh.omicrm.com"
1889
+ val host = data.getString("host").let { if (it.isNullOrEmpty()) "vh.omicrm.com" else it }
1908
1890
  val firebaseToken = data.getString("fcmToken")
1909
1891
  val projectId = data.getString("projectId") ?: ""
1910
1892
 
1911
1893
  // Validate required parameters
1912
1894
  if (userName.isNullOrEmpty() || password.isNullOrEmpty() || realm.isNullOrEmpty() || firebaseToken.isNullOrEmpty()) {
1913
- Log.e("OmikitPlugin", "❌ Missing required parameters for credential check")
1914
1895
  promise.resolve(mapOf("success" to false, "message" to "Missing required parameters"))
1915
1896
  return@launch
1916
1897
  }
1917
1898
 
1918
1899
  withContext(Dispatchers.Default) {
1919
1900
  try {
1920
- Log.d("OmikitPlugin", "🔍 Checking credentials for user: $userName")
1921
-
1922
1901
  OmiClient.getInstance(reactApplicationContext!!).checkCredentials(
1923
1902
  userName = userName ?: "",
1924
1903
  password = password ?: "",
@@ -1927,8 +1906,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1927
1906
  host = host,
1928
1907
  projectId = projectId
1929
1908
  ) { success, statusCode, message ->
1930
- Log.d("OmikitPlugin", "🔍 Credential check callback - success: $success, status: $statusCode, message: $message")
1931
-
1932
1909
  val result = mapOf(
1933
1910
  "success" to success,
1934
1911
  "statusCode" to statusCode,
@@ -1939,7 +1916,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1939
1916
  }
1940
1917
 
1941
1918
  } catch (e: Exception) {
1942
- Log.e("OmikitPlugin", "❌ Error during credential check: ${e.message}", e)
1943
1919
  val errorResult = mapOf(
1944
1920
  "success" to false,
1945
1921
  "message" to e.message
@@ -1957,24 +1933,21 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1957
1933
  val userName = data.getString("userName")
1958
1934
  val password = data.getString("password")
1959
1935
  val realm = data.getString("realm")
1960
- val host = data.getString("host") ?: "vh.omicrm.com"
1961
- val isVideo = data.getBoolean("isVideo")
1936
+ val host = data.getString("host").let { if (it.isNullOrEmpty()) "vh.omicrm.com" else it }
1937
+ val isVideo = if (data.hasKey("isVideo")) data.getBoolean("isVideo") else false
1962
1938
  val firebaseToken = data.getString("fcmToken")
1963
1939
  val projectId = data.getString("projectId") ?: ""
1964
- val showNotification = data.getBoolean("showNotification") ?: true
1965
- val enableAutoUnregister = data.getBoolean("enableAutoUnregister") ?: true
1940
+ val showNotification = if (data.hasKey("showNotification")) data.getBoolean("showNotification") else true
1941
+ val enableAutoUnregister = if (data.hasKey("enableAutoUnregister")) data.getBoolean("enableAutoUnregister") else true
1966
1942
 
1967
1943
  // Validate required parameters
1968
1944
  if (userName.isNullOrEmpty() || password.isNullOrEmpty() || realm.isNullOrEmpty() || firebaseToken.isNullOrEmpty()) {
1969
- Log.e("OmikitPlugin", "❌ Missing required parameters for registration with options")
1970
1945
  promise.resolve(mapOf("success" to false, "message" to "Missing required parameters"))
1971
1946
  return@launch
1972
1947
  }
1973
1948
 
1974
1949
  withContext(Dispatchers.Default) {
1975
1950
  try {
1976
- Log.d("OmikitPlugin", "⚙️ Registering with options for user: $userName - showNotification: $showNotification, enableAutoUnregister: $enableAutoUnregister")
1977
-
1978
1951
  OmiClient.getInstance(reactApplicationContext!!).registerWithOptions(
1979
1952
  userName = userName ?: "",
1980
1953
  password = password ?: "",
@@ -1986,8 +1959,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1986
1959
  showNotification = showNotification,
1987
1960
  enableAutoUnregister = enableAutoUnregister
1988
1961
  ) { success, statusCode, message ->
1989
- Log.d("OmikitPlugin", "⚙️ Registration with options callback - success: $success, status: $statusCode, message: $message")
1990
-
1991
1962
  val result = mapOf(
1992
1963
  "success" to success,
1993
1964
  "statusCode" to statusCode,
@@ -1998,7 +1969,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
1998
1969
  }
1999
1970
 
2000
1971
  } catch (e: Exception) {
2001
- Log.e("OmikitPlugin", "❌ Error during registration with options: ${e.message}", e)
2002
1972
  val errorResult = mapOf(
2003
1973
  "success" to false,
2004
1974
  "message" to e.message