omikit-plugin 3.2.81 → 3.2.84

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.
@@ -34,6 +34,8 @@ import vn.vihat.omicall.omisdk.utils.OmiSDKUtils
34
34
  import vn.vihat.omicall.omisdk.utils.OmiStartCallStatus
35
35
  import vn.vihat.omicall.omisdk.utils.SipServiceConstants
36
36
  import vn.vihat.omicall.omisdk.utils.Utils
37
+ import java.util.Timer
38
+ import java.util.TimerTask
37
39
 
38
40
 
39
41
  class OmikitPluginModule(reactContext: ReactApplicationContext?) :
@@ -51,7 +53,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
51
53
 
52
54
  override fun incomingReceived(callerId: Int?, phoneNumber: String?, isVideo: Boolean?) {
53
55
  isIncoming = true;
54
- Log.d("OMISDK", "=>> START INCOMING CALL REVICED => ")
55
56
 
56
57
  val typeNumber = OmiKitUtils().checkTypeNumber(phoneNumber ?: "")
57
58
 
@@ -82,15 +83,17 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
82
83
 
83
84
  val typeNumber = OmiKitUtils().checkTypeNumber(phoneNumber ?: "")
84
85
 
85
- val map: WritableMap = WritableNativeMap().apply {
86
- putString("callerNumber", phoneNumber ?: "")
87
- putBoolean("isVideo", isVideo ?: true)
88
- putBoolean("incoming", isIncoming) // 🔹 Kiểm tra lỗi chính tả biến này
89
- putString("transactionId", transactionId ?: "")
90
- putInt("status", CallState.confirmed.value)
91
- putString("typeNumber", typeNumber)
92
- }
93
-
86
+ // Sử dụng safe WritableMap creation
87
+ val eventData = mapOf(
88
+ "callerNumber" to (phoneNumber ?: ""),
89
+ "isVideo" to (isVideo ?: true),
90
+ "incoming" to isIncoming,
91
+ "transactionId" to (transactionId ?: ""),
92
+ "status" to CallState.confirmed.value,
93
+ "typeNumber" to typeNumber
94
+ )
95
+
96
+ val map = createSafeWritableMap(eventData)
94
97
  sendEvent(CALL_STATE_CHANGED, map)
95
98
  }, 200)
96
99
  }
@@ -106,21 +109,24 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
106
109
  val phoneNumber = (call["destination_number"] as? String) ?: (call["source_number"] as? String) ?: ""
107
110
  val typeNumber = OmiKitUtils().checkTypeNumber(phoneNumber)
108
111
 
109
- val map: WritableMap = WritableNativeMap().apply {
110
- putString("direction", call["direction"] as? String ?: "")
111
- putString("transactionId", call["transaction_id"] as? String ?: "")
112
- putString("sourceNumber", call["source_number"] as? String ?: "")
113
- putString("destinationNumber", call["destination_number"] as? String ?: "")
114
- putDouble("timeStartToAnswer", timeStartToAnswer.toDouble())
115
- putDouble("timeEnd", timeEnd.toDouble())
116
- putString("sipUser", call["sip_user"] as? String ?: "")
117
- putInt("codeEndCall", statusCode)
118
- putString("disposition", call["disposition"] as? String ?: "")
119
- putInt("status", CallState.disconnected.value)
120
- putString("typeNumber", typeNumber)
121
- }
112
+ // Sử dụng safe WritableMap creation
113
+ val eventData = mapOf(
114
+ "direction" to (call["direction"] as? String ?: ""),
115
+ "transactionId" to (call["transaction_id"] as? String ?: ""),
116
+ "sourceNumber" to (call["source_number"] as? String ?: ""),
117
+ "destinationNumber" to (call["destination_number"] as? String ?: ""),
118
+ "timeStartToAnswer" to timeStartToAnswer.toDouble(),
119
+ "timeEnd" to timeEnd.toDouble(),
120
+ "sipUser" to (call["sip_user"] as? String ?: ""),
121
+ "codeEndCall" to statusCode,
122
+ "disposition" to (call["disposition"] as? String ?: ""),
123
+ "status" to CallState.disconnected.value,
124
+ "typeNumber" to typeNumber
125
+ )
126
+
127
+ val map = createSafeWritableMap(eventData)
122
128
 
123
- Log.d("OMISDK RN", "=>> onCallEnd => $map")
129
+ Log.d("OMISDK RN", "=>> onCallEnd => ")
124
130
  sendEvent(CALL_STATE_CHANGED, map)
125
131
  }
126
132
 
@@ -151,14 +157,17 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
151
157
  val prePhoneNumber = OmiClient.prePhoneNumber ?: ""
152
158
  val typeNumber = OmiKitUtils().checkTypeNumber(prePhoneNumber)
153
159
 
154
- val map: WritableMap = WritableNativeMap().apply {
155
- putString("callerNumber", if (callDirection == "inbound") prePhoneNumber else "")
156
- putBoolean("isVideo", NotificationService.isVideo)
157
- putString("transactionId", transactionId ?: "")
158
- putInt("status", if (callDirection == "inbound") CallState.incoming.value else CallState.early.value)
159
- putBoolean("incoming", callDirection == "inbound")
160
- putString("typeNumber", typeNumber)
161
- }
160
+ // Sử dụng safe WritableMap creation
161
+ val eventData = mapOf(
162
+ "callerNumber" to if (callDirection == "inbound") prePhoneNumber else "",
163
+ "isVideo" to NotificationService.isVideo,
164
+ "transactionId" to (transactionId ?: ""),
165
+ "status" to if (callDirection == "inbound") CallState.incoming.value else CallState.early.value,
166
+ "incoming" to (callDirection == "inbound"),
167
+ "typeNumber" to typeNumber
168
+ )
169
+
170
+ val map = createSafeWritableMap(eventData)
162
171
 
163
172
  Log.d("OMISDK", if (callDirection == "inbound") "=>> ON INCOMING CALL => " else "=>> ON RINGING CALL => ")
164
173
  sendEvent(CALL_STATE_CHANGED, map)
@@ -288,17 +297,173 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
288
297
 
289
298
  override fun initialize() {
290
299
  super.initialize()
300
+
301
+
291
302
  reactApplicationContext!!.addActivityEventListener(this)
292
303
  Handler(Looper.getMainLooper()).post {
293
304
  OmiClient.getInstance(reactApplicationContext!!).addCallStateListener(this)
305
+
306
+ // ✅ Add listener cho AUTO-UNREGISTER status
307
+ OmiClient.getInstance(reactApplicationContext!!).addCallStateListener(autoUnregisterListener)
308
+
294
309
  OmiClient.getInstance(reactApplicationContext!!).setDebug(false)
295
310
  }
296
311
  }
297
312
 
313
+
298
314
  @ReactMethod
299
315
  fun startServices(promise: Promise) {
300
- OmiClient.getInstance(reactApplicationContext!!).addAccountListener(accountListener)
301
- promise.resolve(true)
316
+ try {
317
+ // ✅ Prepare audio system trước khi start services
318
+ prepareAudioSystem()
319
+
320
+ OmiClient.getInstance(reactApplicationContext!!).addAccountListener(accountListener)
321
+
322
+ // ✅ Start services - không cần prevent auto-unregister với Silent API
323
+ OmiClient.getInstance(reactApplicationContext!!).setDebug(false)
324
+ promise.resolve(true)
325
+ } catch (e: Exception) {
326
+ Log.e("OmikitPlugin", "❌ Error in startServices: ${e.message}", e)
327
+ promise.resolve(false)
328
+ }
329
+ }
330
+
331
+ // ✅ Helper function để sử dụng API mới (DEPRECATED - sử dụng Silent API thay thế)
332
+ private fun preventAutoUnregisterCrash(reason: String) {
333
+ try {
334
+ Log.w("OmikitPlugin", "⚠️ DEPRECATED: preventAutoUnregisterCrash() - Use Silent Registration API instead")
335
+ OmiClient.getInstance(reactApplicationContext!!).preventAutoUnregister(reason)
336
+ } catch (e: Exception) {
337
+ Log.e("OmikitPlugin", "❌ Failed to prevent AUTO-UNREGISTER: ${e.message}", e)
338
+ }
339
+ }
340
+
341
+ // ✅ Method để check status AUTO-UNREGISTER (DEPRECATED)
342
+ @ReactMethod
343
+ fun getAutoUnregisterStatus(promise: Promise) {
344
+ Log.w("OmikitPlugin", "⚠️ DEPRECATED: getAutoUnregisterStatus() - Use Silent Registration API instead")
345
+ try {
346
+ OmiClient.getInstance(reactApplicationContext!!).getAutoUnregisterStatus { isScheduled, timeUntilExecution ->
347
+ try {
348
+ val status = mapOf(
349
+ "isScheduled" to isScheduled,
350
+ "timeUntilExecution" to timeUntilExecution,
351
+ "timestamp" to System.currentTimeMillis()
352
+ )
353
+ promise.resolve(Arguments.makeNativeMap(status))
354
+ } catch (e: Exception) {
355
+ Log.e("OmikitPlugin", "❌ Error in getAutoUnregisterStatus callback: ${e.message}", e)
356
+ promise.resolve(null)
357
+ }
358
+ }
359
+ } catch (e: Exception) {
360
+ Log.e("OmikitPlugin", "❌ Error calling getAutoUnregisterStatus: ${e.message}", e)
361
+ promise.resolve(null)
362
+ }
363
+ }
364
+
365
+ // ✅ Method để manually prevent AUTO-UNREGISTER (DEPRECATED)
366
+ @ReactMethod
367
+ fun preventAutoUnregister(reason: String, promise: Promise) {
368
+ Log.w("OmikitPlugin", "⚠️ DEPRECATED: preventAutoUnregister() - Use Silent Registration API instead")
369
+ try {
370
+ preventAutoUnregisterCrash(reason)
371
+ promise.resolve(true)
372
+ } catch (e: Exception) {
373
+ Log.e("OmikitPlugin", "❌ Manual prevent failed: ${e.message}", e)
374
+ promise.resolve(false)
375
+ }
376
+ }
377
+
378
+ // ✅ Convenience methods cho các scenario phổ biến (DEPRECATED)
379
+ @ReactMethod
380
+ fun prepareForIncomingCall(promise: Promise) {
381
+ Log.w("OmikitPlugin", "⚠️ DEPRECATED: prepareForIncomingCall() - Use Silent Registration API instead")
382
+ try {
383
+ OmiClient.getInstance(reactApplicationContext!!).prepareForIncomingCall()
384
+ promise.resolve(true)
385
+ } catch (e: Exception) {
386
+ Log.e("OmikitPlugin", "❌ Prepare for incoming call failed: ${e.message}", e)
387
+ promise.resolve(false)
388
+ }
389
+ }
390
+
391
+ @ReactMethod
392
+ fun prepareForOutgoingCall(promise: Promise) {
393
+ Log.w("OmikitPlugin", "⚠️ DEPRECATED: prepareForOutgoingCall() - Use Silent Registration API instead")
394
+ try {
395
+ OmiClient.getInstance(reactApplicationContext!!).prepareForOutgoingCall()
396
+ promise.resolve(true)
397
+ } catch (e: Exception) {
398
+ Log.e("OmikitPlugin", "❌ Prepare for outgoing call failed: ${e.message}", e)
399
+ promise.resolve(false)
400
+ }
401
+ }
402
+
403
+ private fun prepareAudioSystem() {
404
+ try {
405
+ // ✅ Check network connectivity first
406
+ if (!isNetworkAvailable()) {
407
+ return
408
+ }
409
+
410
+ // Release any existing audio focus
411
+ val audioManager = reactApplicationContext?.getSystemService(android.content.Context.AUDIO_SERVICE) as? android.media.AudioManager
412
+ audioManager?.let {
413
+ // Reset audio mode
414
+ it.mode = android.media.AudioManager.MODE_NORMAL
415
+ }
416
+
417
+ // Small delay để audio system ổn định
418
+ Thread.sleep(200)
419
+
420
+ } catch (e: Exception) {
421
+ Log.w("OmikitPlugin", "⚠️ Audio preparation warning: ${e.message}")
422
+ }
423
+ }
424
+
425
+ private fun isNetworkAvailable(): Boolean {
426
+ return try {
427
+ val connectivityManager = reactApplicationContext?.getSystemService(android.content.Context.CONNECTIVITY_SERVICE) as? android.net.ConnectivityManager
428
+ val activeNetwork = connectivityManager?.activeNetworkInfo
429
+ val isConnected = activeNetwork?.isConnectedOrConnecting == true
430
+ isConnected
431
+ } catch (e: Exception) {
432
+ Log.w("OmikitPlugin", "⚠️ Network check failed: ${e.message}")
433
+ true // Assume network is available if check fails
434
+ }
435
+ }
436
+
437
+ // ✅ Safe wrapper cho OMISIP calls để tránh SIGSEGV
438
+ private fun <T> safePjsipCall(operation: String, block: () -> T): T? {
439
+ return try {
440
+ val result = block()
441
+ result
442
+ } catch (e: Exception) {
443
+ Log.e("OmikitPlugin", "❌ Safe OMISIP call failed: $operation - ${e.message}", e)
444
+ null
445
+ }
446
+ }
447
+
448
+ // ✅ Helper function để tạo WritableMap an toàn
449
+ private fun createSafeWritableMap(data: Map<String, Any?>): WritableMap {
450
+ val map = WritableNativeMap()
451
+ try {
452
+ data.forEach { (key, value) ->
453
+ when (value) {
454
+ is String -> map.putString(key, value)
455
+ is Int -> map.putInt(key, value)
456
+ is Double -> map.putDouble(key, value)
457
+ is Boolean -> map.putBoolean(key, value)
458
+ is Long -> map.putDouble(key, value.toDouble())
459
+ null -> map.putNull(key)
460
+ else -> map.putString(key, value.toString())
461
+ }
462
+ }
463
+ } catch (e: Exception) {
464
+ Log.e("OmikitPlugin", "❌ Error creating WritableMap: ${e.message}", e)
465
+ }
466
+ return map
302
467
  }
303
468
 
304
469
  @RequiresApi(Build.VERSION_CODES.M)
@@ -365,7 +530,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
365
530
  @ReactMethod
366
531
  fun initCallWithUserPassword(data: ReadableMap, promise: Promise) {
367
532
  mainScope.launch {
368
- var loginResult = false
369
533
  val userName = data.getString("userName")
370
534
  val password = data.getString("password")
371
535
  val realm = data.getString("realm")
@@ -374,26 +538,52 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
374
538
  val firebaseToken = data.getString("fcmToken")
375
539
  val projectId = data.getString("projectId") ?: ""
376
540
 
541
+ // Validate required parameters
542
+ if (userName.isNullOrEmpty() || password.isNullOrEmpty() || realm.isNullOrEmpty() || firebaseToken.isNullOrEmpty()) {
543
+ Log.e("OmikitPlugin", "❌ Missing required parameters for SIP registration")
544
+ promise.resolve(false)
545
+ return@launch
546
+ }
547
+
377
548
  withContext(Dispatchers.Default) {
378
549
  try {
379
- if (userName != null && password != null && realm != null && firebaseToken != null) {
380
- loginResult =
381
- OmiClient.register(
382
- userName,
383
- password,
384
- realm,
385
- isVideo ?: true,
386
- firebaseToken,
387
- host,
388
- projectId
389
- )
390
- promise.resolve(loginResult)
550
+ // Cleanup trước khi register
551
+ try {
552
+ OmiClient.getInstance(reactApplicationContext!!).logout()
553
+ delay(500) // Chờ cleanup hoàn tất
554
+ } catch (e: Exception) {
555
+ Log.w("OmikitPlugin", "⚠️ Cleanup warning (expected): ${e.message}")
391
556
  }
392
- } catch (_: Throwable) {
393
- promise.resolve(loginResult)
557
+
558
+ // ✅ Sử dụng Silent Registration API mới từ OmiSDK 2.3.67
559
+ Log.d("OmikitPlugin", "🔇 Using Silent Registration API for user: $userName")
560
+
561
+ OmiClient.getInstance(reactApplicationContext!!).silentRegister(
562
+ userName = userName,
563
+ password = password,
564
+ realm = realm,
565
+ isVideo = isVideo ?: true,
566
+ firebaseToken = firebaseToken,
567
+ host = host,
568
+ projectId = projectId
569
+ ) { success, statusCode, message ->
570
+ Log.d("OmikitPlugin", "🔇 Silent registration callback - success: $success, status: $statusCode, message: $message")
571
+
572
+ if (success) {
573
+ Log.d("OmikitPlugin", "✅ Silent registration successful - no notification, no auto-unregister")
574
+ } else {
575
+ Log.e("OmikitPlugin", "❌ Silent registration failed: $message")
576
+ }
577
+
578
+ // ✅ Resolve promise với kết quả từ callback
579
+ promise.resolve(success)
580
+ }
581
+
582
+ } catch (e: Exception) {
583
+ Log.e("OmikitPlugin", "❌ Error during silent registration: ${e.message}", e)
584
+ promise.resolve(false)
394
585
  }
395
586
  }
396
- promise.resolve(loginResult)
397
587
  }
398
588
  }
399
589
 
@@ -422,6 +612,13 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
422
612
  firebaseToken,
423
613
  projectId
424
614
  )
615
+
616
+ // ✅ Sử dụng API mới để ngăn chặn AUTO-UNREGISTER sau khi register thành công
617
+ if (loginResult) {
618
+ Log.d("OmikitPlugin", "🛡️ Preventing AUTO-UNREGISTER after successful API key registration")
619
+ preventAutoUnregisterCrash("Successful API key registration - userName: $usrName")
620
+ }
621
+
425
622
  promise.resolve(true)
426
623
  }
427
624
  } catch (_: Throwable) {
@@ -509,9 +706,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
509
706
  currentActivity?.runOnUiThread {
510
707
  val phoneNumber = data.getString("phoneNumber") as String
511
708
  val isVideo = data.getBoolean("isVideo") ?: false;
709
+
512
710
  val startCallResult =
513
711
  OmiClient.getInstance(reactApplicationContext!!).startCall(phoneNumber, isVideo)
514
- Log.d("OMISDK", "=>> startCallResult START CALL => $startCallResult")
515
712
  var statusCalltemp = startCallResult.value as Int;
516
713
  if (startCallResult.value == 200 || startCallResult.value == 407) {
517
714
  statusCalltemp = 8
@@ -525,7 +722,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
525
722
  map.putInt("status", 4)
526
723
  map.putString("_id", "")
527
724
  map.putString("message", messageCall(406) as String)
528
- Log.d("OMISDK", "=>> ON START CALL FAIL BECAUSE NEED PERMISSION => $map")
529
725
  promise.resolve(map)
530
726
  }
531
727
  }
@@ -544,6 +740,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
544
740
  try {
545
741
  val uuid = data.getString("usrUuid") as String
546
742
  val isVideo = data.getBoolean("isVideo")
743
+
547
744
  callResult = OmiClient.getInstance(reactApplicationContext!!)
548
745
  .startCallWithUuid(uuid = uuid, isVideo = isVideo)
549
746
  } catch (_: Throwable) {
@@ -703,9 +900,10 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
703
900
  var callResult: Any? = null
704
901
  withContext(Dispatchers.Default) {
705
902
  try {
903
+ // ✅ Gọi trực tiếp getCurrentUser() trong coroutine context
706
904
  callResult = OmiClient.getInstance(reactApplicationContext!!).getCurrentUser()
707
- } catch (_: Throwable) {
708
-
905
+ } catch (e: Throwable) {
906
+ Log.e("OmikitPlugin", "❌ getCurrentUser error: ${e.message}", e)
709
907
  }
710
908
  }
711
909
  if (callResult != null && callResult is Map<*, *>) {
@@ -830,7 +1028,11 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
830
1028
  const val NAME = "OmikitPlugin"
831
1029
 
832
1030
  fun onDestroy() {
833
-
1031
+ try {
1032
+ // Cleanup OmiClient resources safely
1033
+ } catch (e: Exception) {
1034
+ Log.e("OmikitPlugin", "❌ Error during cleanup: ${e.message}", e)
1035
+ }
834
1036
  }
835
1037
 
836
1038
  fun onResume(act: ReactActivity) {
@@ -886,19 +1088,61 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
886
1088
  }
887
1089
  }
888
1090
 
889
- fun sendEvent(eventName: String?, params: Any?) {
890
- if (eventName == null) {
891
- Log.e("OmikitPlugin", "❌ eventName is null or empty. Không thể gửi event.")
1091
+ // ✅ Di chuyển sendEvent vào trong class để có thể access reactApplicationContext
1092
+ private fun sendEvent(eventName: String?, params: Any?) {
1093
+ if (eventName == null) {
1094
+ Log.e("OmikitPlugin", "❌ eventName is null or empty. Không thể gửi event.")
1095
+ return
1096
+ }
1097
+
1098
+ try {
1099
+ // ✅ Kiểm tra reactApplicationContext
1100
+ if (reactApplicationContext == null) {
1101
+ Log.e("OmikitPlugin", "❌ reactApplicationContext is null")
1102
+ return
1103
+ }
1104
+
1105
+ if (!reactApplicationContext.hasActiveReactInstance()) {
1106
+ Log.w("OmikitPlugin", "⚠️ ReactApplicationContext không có active React instance")
892
1107
  return
893
1108
  }
894
- if (currentActivity != null) {
895
- currentActivity!!.runOnUiThread {
896
- reactApplicationContext.getJSModule(RCTNativeAppEventEmitter::class.java)
1109
+
1110
+ // ✅ Sử dụng RCTDeviceEventEmitter cho Android (tương thích với DeviceEventEmitter)
1111
+ try {
1112
+ reactApplicationContext
1113
+ .getJSModule(com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
1114
+ .emit(eventName, params)
1115
+ } catch (e1: Exception) {
1116
+ // ✅ Fallback to RCTNativeAppEventEmitter
1117
+ try {
1118
+ reactApplicationContext
1119
+ .getJSModule(com.facebook.react.modules.core.RCTNativeAppEventEmitter::class.java)
897
1120
  .emit(eventName, params)
898
- Log.d("OmikitPlugin", "✅ Event $eventName đã được gửi thành công!")
1121
+ } catch (e2: Exception) {
1122
+ Log.e("OmikitPlugin", "❌ Both event emitters failed: RCTDevice: ${e1.message}, RCTNativeApp: ${e2.message}")
899
1123
  }
900
1124
  }
1125
+
1126
+ } catch (e: Exception) {
1127
+ Log.e("OmikitPlugin", "❌ Error sending event $eventName: ${e.message}", e)
901
1128
  }
1129
+ }
1130
+
1131
+ // ✅ Thêm method để React Native biết các event được hỗ trợ
1132
+ override fun getConstants(): MutableMap<String, Any> {
1133
+ return hashMapOf(
1134
+ "CALL_STATE_CHANGED" to CALL_STATE_CHANGED,
1135
+ "MUTED" to MUTED,
1136
+ "HOLD" to HOLD,
1137
+ "SPEAKER" to SPEAKER,
1138
+ "CALL_QUALITY" to CALL_QUALITY,
1139
+ "AUDIO_CHANGE" to AUDIO_CHANGE,
1140
+ "SWITCHBOARD_ANSWER" to SWITCHBOARD_ANSWER,
1141
+ "REQUEST_PERMISSION" to REQUEST_PERMISSION,
1142
+ "CLICK_MISSED_CALL" to CLICK_MISSED_CALL,
1143
+ "AUTO_UNREGISTER_STATUS" to "AUTO_UNREGISTER_STATUS"
1144
+ )
1145
+ }
902
1146
 
903
1147
  private fun requestPermission(isVideo: Boolean) {
904
1148
  var permissions = arrayOf(
@@ -920,38 +1164,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
920
1164
  )
921
1165
  }
922
1166
 
923
- // private fun getPending(): {
924
- // val pendingCall = OmiKitUtils().getPendingCall(reactApplicationContext)
925
- // OmiKitUtils().clearPendingCall(reactApplicationContext)
926
- // val isPending =
927
- // pendingCall[PREFS_IS_PENDING] as Boolean
928
- // val receiveTime = pendingCall[RECEIVE_TIME] as Long
929
- //
930
- // if ( isPending && System.currentTimeMillis() - receiveTime < 25000) {
931
- // val callId = pendingCall[PREFS_CALL_ID] as Int
932
- // val phoneNumber = pendingCall[PREFS_PHONE_NUMBER] as String
933
- // val isVideo = pendingCall[PREFS_IS_VIDEO] as Boolean
934
- // val startTime = pendingCall[PREFS_START_TIME] as Long
935
- // val uuid = pendingCall[PREFS_UUID] as String
936
- // val isReopen = pendingCall[PREFS_IS_REOPEN] as Boolean
937
- // val isAccepted = pendingCall[PREFS_IS_ACCEPTED] as Boolean
938
- //
939
- // if (isReopen) {
940
- // onCallEstablished(
941
- // callId, phoneNumber, isVideo, startTime,
942
- // uuid
943
- // )
944
- // } else if (isAccepted) {
945
- // OmiClient.getInstance(reactApplicationContext!!).pickUp()
946
- // onCallEstablished(
947
- // callId, phoneNumber, isVideo, startTime,
948
- // uuid
949
- // )
950
- // } else {
951
- // incomingReceived(callId, phoneNumber, isVideo)
952
- // }
953
- // }
954
- // }
955
1167
  override fun onActivityResult(p0: Activity?, p1: Int, p2: Int, p3: Intent?) {
956
1168
  }
957
1169
 
@@ -967,4 +1179,262 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
967
1179
  }
968
1180
  }
969
1181
  }
1182
+
1183
+ override fun onCatalystInstanceDestroy() {
1184
+ super.onCatalystInstanceDestroy()
1185
+ try {
1186
+ // ✅ Cleanup resources
1187
+ } catch (e: Exception) {
1188
+ Log.e("OmikitPlugin", "❌ Error during module cleanup: ${e.message}", e)
1189
+ }
1190
+ }
1191
+
1192
+ // ✅ Thêm listener cho AUTO-UNREGISTER status
1193
+ private val autoUnregisterListener = object : OmiListener {
1194
+ override fun onAutoUnregisterStatus(isScheduled: Boolean, timeUntilExecution: Long) {
1195
+ // ✅ Ngăn chặn nếu sắp thực hiện (< 3 giây)
1196
+ if (isScheduled && timeUntilExecution > 0 && timeUntilExecution < 3000) {
1197
+ Log.w("OmikitPlugin", "🚨 AUTO-UNREGISTER sắp thực hiện trong ${timeUntilExecution}ms - ngăn chặn khẩn cấp!")
1198
+ preventAutoUnregisterCrash("Emergency prevention from listener - ${timeUntilExecution}ms remaining")
1199
+ }
1200
+
1201
+ // ✅ Gửi event cho React Native
1202
+ try {
1203
+ val statusData = mapOf(
1204
+ "isScheduled" to isScheduled,
1205
+ "timeUntilExecution" to timeUntilExecution,
1206
+ "timestamp" to System.currentTimeMillis()
1207
+ )
1208
+ val map = createSafeWritableMap(statusData)
1209
+ sendEvent("AUTO_UNREGISTER_STATUS", map)
1210
+ } catch (e: Exception) {
1211
+ Log.e("OmikitPlugin", "❌ Error sending AUTO_UNREGISTER_STATUS event: ${e.message}", e)
1212
+ }
1213
+ }
1214
+
1215
+ // ✅ Implement các method khác của OmiListener (delegate to main listener)
1216
+ override fun incomingReceived(callerId: Int?, phoneNumber: String?, isVideo: Boolean?) {
1217
+ this@OmikitPluginModule.incomingReceived(callerId, phoneNumber, isVideo)
1218
+ }
1219
+
1220
+ override fun onCallEstablished(callerId: Int, phoneNumber: String?, isVideo: Boolean?, startTime: Long, transactionId: String?) {
1221
+ this@OmikitPluginModule.onCallEstablished(callerId, phoneNumber, isVideo, startTime, transactionId)
1222
+ }
1223
+
1224
+ override fun onCallEnd(callInfo: MutableMap<String, Any?>, statusCode: Int) {
1225
+ this@OmikitPluginModule.onCallEnd(callInfo, statusCode)
1226
+ }
1227
+
1228
+ override fun onConnecting() {
1229
+ this@OmikitPluginModule.onConnecting()
1230
+ }
1231
+
1232
+ override fun onDescriptionError() {
1233
+ this@OmikitPluginModule.onDescriptionError()
1234
+ }
1235
+
1236
+ override fun onFcmReceived(uuid: String, userName: String, avatar: String) {
1237
+ this@OmikitPluginModule.onFcmReceived(uuid, userName, avatar)
1238
+ }
1239
+
1240
+ override fun onRinging(callerId: Int, transactionId: String?) {
1241
+ this@OmikitPluginModule.onRinging(callerId, transactionId)
1242
+ }
1243
+
1244
+ override fun networkHealth(stat: Map<String, *>, quality: Int) {
1245
+ this@OmikitPluginModule.networkHealth(stat, quality)
1246
+ }
1247
+
1248
+ override fun onAudioChanged(audioInfo: Map<String, Any>) {
1249
+ this@OmikitPluginModule.onAudioChanged(audioInfo)
1250
+ }
1251
+
1252
+ override fun onHold(isHold: Boolean) {
1253
+ this@OmikitPluginModule.onHold(isHold)
1254
+ }
1255
+
1256
+ override fun onMuted(isMuted: Boolean) {
1257
+ this@OmikitPluginModule.onMuted(isMuted)
1258
+ }
1259
+
1260
+ override fun onOutgoingStarted(callerId: Int, phoneNumber: String?, isVideo: Boolean?) {
1261
+ this@OmikitPluginModule.onOutgoingStarted(callerId, phoneNumber, isVideo)
1262
+ }
1263
+
1264
+ override fun onSwitchBoardAnswer(sip: String) {
1265
+ this@OmikitPluginModule.onSwitchBoardAnswer(sip)
1266
+ }
1267
+
1268
+ override fun onRegisterCompleted(statusCode: Int) {
1269
+ this@OmikitPluginModule.onRegisterCompleted(statusCode)
1270
+ }
1271
+
1272
+ override fun onRequestPermission(permissions: Array<String>) {
1273
+ this@OmikitPluginModule.onRequestPermission(permissions)
1274
+ }
1275
+
1276
+ override fun onVideoSize(width: Int, height: Int) {
1277
+ this@OmikitPluginModule.onVideoSize(width, height)
1278
+ }
1279
+ }
1280
+
1281
+ // ✅ Helper function để hide notification một cách an toàn
1282
+ @ReactMethod
1283
+ fun hideSystemNotificationSafely(promise: Promise) {
1284
+ try {
1285
+ // ✅ Delay 2 giây để đảm bảo registration hoàn tất
1286
+ Handler(Looper.getMainLooper()).postDelayed({
1287
+ try {
1288
+ // ✅ Gọi function hide notification với error handling
1289
+ OmiClient.getInstance(reactApplicationContext!!).hideSystemNotificationAndUnregister("Registration check completed")
1290
+ Log.d("OmikitPlugin", "✅ Successfully hidden system notification and unregistered")
1291
+ promise.resolve(true)
1292
+ } catch (e: Exception) {
1293
+ Log.e("OmikitPlugin", "❌ Failed to hide system notification: ${e.message}", e)
1294
+ promise.resolve(false)
1295
+ }
1296
+ }, 2000) // Delay 2 giây
1297
+ } catch (e: Exception) {
1298
+ Log.e("OmikitPlugin", "❌ Error in hideSystemNotificationSafely: ${e.message}", e)
1299
+ promise.resolve(false)
1300
+ }
1301
+ }
1302
+
1303
+ // ✅ Function để chỉ ẩn notification mà không unregister
1304
+ @ReactMethod
1305
+ fun hideSystemNotificationOnly(promise: Promise) {
1306
+ try {
1307
+ OmiClient.getInstance(reactApplicationContext!!).hideSystemNotification()
1308
+ Log.d("OmikitPlugin", "✅ Successfully hidden system notification (keeping registration)")
1309
+ promise.resolve(true)
1310
+ } catch (e: Exception) {
1311
+ Log.e("OmikitPlugin", "❌ Failed to hide system notification only: ${e.message}", e)
1312
+ promise.resolve(false)
1313
+ }
1314
+ }
1315
+
1316
+ // ✅ Function để ẩn notification và unregister với custom reason
1317
+ @ReactMethod
1318
+ fun hideSystemNotificationAndUnregister(reason: String, promise: Promise) {
1319
+ try {
1320
+ OmiClient.getInstance(reactApplicationContext!!).hideSystemNotificationAndUnregister(reason)
1321
+ Log.d("OmikitPlugin", "✅ Successfully hidden notification and unregistered: $reason")
1322
+ promise.resolve(true)
1323
+ } catch (e: Exception) {
1324
+ Log.e("OmikitPlugin", "❌ Failed to hide notification and unregister: ${e.message}", e)
1325
+ promise.resolve(false)
1326
+ }
1327
+ }
1328
+
1329
+ // ✅ Function để chỉ kiểm tra credentials và tự động disconnect
1330
+ @ReactMethod
1331
+ fun checkCredentials(data: ReadableMap, promise: Promise) {
1332
+ mainScope.launch {
1333
+ val userName = data.getString("userName")
1334
+ val password = data.getString("password")
1335
+ val realm = data.getString("realm")
1336
+ val host = data.getString("host") ?: "vh.omicrm.com"
1337
+ val firebaseToken = data.getString("fcmToken")
1338
+ val projectId = data.getString("projectId") ?: ""
1339
+
1340
+ // Validate required parameters
1341
+ if (userName.isNullOrEmpty() || password.isNullOrEmpty() || realm.isNullOrEmpty() || firebaseToken.isNullOrEmpty()) {
1342
+ Log.e("OmikitPlugin", "❌ Missing required parameters for credential check")
1343
+ promise.resolve(mapOf("success" to false, "message" to "Missing required parameters"))
1344
+ return@launch
1345
+ }
1346
+
1347
+ withContext(Dispatchers.Default) {
1348
+ try {
1349
+ Log.d("OmikitPlugin", "🔍 Checking credentials for user: $userName")
1350
+
1351
+ OmiClient.getInstance(reactApplicationContext!!).checkCredentials(
1352
+ userName = userName,
1353
+ password = password,
1354
+ realm = realm,
1355
+ firebaseToken = firebaseToken,
1356
+ host = host,
1357
+ projectId = projectId
1358
+ ) { success, statusCode, message ->
1359
+ Log.d("OmikitPlugin", "🔍 Credential check callback - success: $success, status: $statusCode, message: $message")
1360
+
1361
+ val result = mapOf(
1362
+ "success" to success,
1363
+ "statusCode" to statusCode,
1364
+ "message" to (message ?: "")
1365
+ )
1366
+
1367
+ promise.resolve(Arguments.makeNativeMap(result))
1368
+ }
1369
+
1370
+ } catch (e: Exception) {
1371
+ Log.e("OmikitPlugin", "❌ Error during credential check: ${e.message}", e)
1372
+ val errorResult = mapOf(
1373
+ "success" to false,
1374
+ "message" to e.message
1375
+ )
1376
+ promise.resolve(Arguments.makeNativeMap(errorResult))
1377
+ }
1378
+ }
1379
+ }
1380
+ }
1381
+
1382
+ // ✅ Function để register với full control options
1383
+ @ReactMethod
1384
+ fun registerWithOptions(data: ReadableMap, promise: Promise) {
1385
+ mainScope.launch {
1386
+ val userName = data.getString("userName")
1387
+ val password = data.getString("password")
1388
+ val realm = data.getString("realm")
1389
+ val host = data.getString("host") ?: "vh.omicrm.com"
1390
+ val isVideo = data.getBoolean("isVideo")
1391
+ val firebaseToken = data.getString("fcmToken")
1392
+ val projectId = data.getString("projectId") ?: ""
1393
+ val showNotification = data.getBoolean("showNotification") ?: true
1394
+ val enableAutoUnregister = data.getBoolean("enableAutoUnregister") ?: true
1395
+
1396
+ // Validate required parameters
1397
+ if (userName.isNullOrEmpty() || password.isNullOrEmpty() || realm.isNullOrEmpty() || firebaseToken.isNullOrEmpty()) {
1398
+ Log.e("OmikitPlugin", "❌ Missing required parameters for registration with options")
1399
+ promise.resolve(mapOf("success" to false, "message" to "Missing required parameters"))
1400
+ return@launch
1401
+ }
1402
+
1403
+ withContext(Dispatchers.Default) {
1404
+ try {
1405
+ Log.d("OmikitPlugin", "⚙️ Registering with options for user: $userName - showNotification: $showNotification, enableAutoUnregister: $enableAutoUnregister")
1406
+
1407
+ OmiClient.getInstance(reactApplicationContext!!).registerWithOptions(
1408
+ userName = userName,
1409
+ password = password,
1410
+ realm = realm,
1411
+ isVideo = isVideo ?: true,
1412
+ firebaseToken = firebaseToken,
1413
+ host = host,
1414
+ projectId = projectId,
1415
+ showNotification = showNotification,
1416
+ enableAutoUnregister = enableAutoUnregister
1417
+ ) { success, statusCode, message ->
1418
+ Log.d("OmikitPlugin", "⚙️ Registration with options callback - success: $success, status: $statusCode, message: $message")
1419
+
1420
+ val result = mapOf(
1421
+ "success" to success,
1422
+ "statusCode" to statusCode,
1423
+ "message" to (message ?: "")
1424
+ )
1425
+
1426
+ promise.resolve(Arguments.makeNativeMap(result))
1427
+ }
1428
+
1429
+ } catch (e: Exception) {
1430
+ Log.e("OmikitPlugin", "❌ Error during registration with options: ${e.message}", e)
1431
+ val errorResult = mapOf(
1432
+ "success" to false,
1433
+ "message" to e.message
1434
+ )
1435
+ promise.resolve(Arguments.makeNativeMap(errorResult))
1436
+ }
1437
+ }
1438
+ }
1439
+ }
970
1440
  }