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
@@ -30,11 +30,12 @@ const val CALL_STATE_CHANGED = "CALL_STATE_CHANGED"
30
30
  const val VIDEO = "VIDEO"
31
31
  const val SPEAKER = "SPEAKER"
32
32
  const val MUTED = "MUTED"
33
+ const val HOLD = "HOLD"
34
+ const val REMOTE_VIDEO_READY = "REMOTE_VIDEO_READY"
33
35
  const val CLICK_MISSED_CALL = "CLICK_MISSED_CALL"
34
36
  const val SWITCHBOARD_ANSWER = "SWITCHBOARD_ANSWER"
35
37
  const val CALL_QUALITY = "CALL_QUALITY"
36
38
  const val AUDIO_CHANGE = "AUDIO_CHANGE"
37
- const val HOLD = "HOLD"
38
39
  const val REQUEST_PERMISSION = "REQUEST_PERMISSION"
39
40
 
40
41
  //PREFFERENCES
@@ -13,9 +13,12 @@ import OmiKit
13
13
  import AVFoundation
14
14
 
15
15
  class CallManager {
16
-
16
+
17
17
  static private var instance: CallManager? = nil // Instance
18
- private let omiLib = OMISIPLib.sharedInstance()
18
+ // Use lazy initialization to avoid crash during New Architecture module loading
19
+ private lazy var omiLib: OMISIPLib = {
20
+ return OMISIPLib.sharedInstance()
21
+ }()
19
22
  var videoManager: OMIVideoViewManager?
20
23
  var isSpeaker = false
21
24
  private var guestPhone : String = ""
@@ -149,40 +152,36 @@ class CallManager {
149
152
 
150
153
 
151
154
  func initWithUserPasswordEndpoint(params: [String: Any]) -> Bool {
152
- // Kiểm tra thông tin đầu vào
155
+ // Validate required parameters
153
156
  guard let userName = params["userName"] as? String,
154
157
  let password = params["password"] as? String,
155
158
  let realm = params["realm"] as? String,
156
159
  let token = params["fcmToken"] as? String else {
157
- print("🚨 Lỗi: Thiếu thông tin đăng nhập!")
160
+ print("🚨 Missing login credentials!")
158
161
  return false
159
162
  }
160
163
 
164
+ // Use host as SIP proxy (matching Android behavior)
165
+ let host = (params["host"] as? String) ?? ""
166
+ let proxy = host.isEmpty ? "" : host
161
167
 
162
- // Nếu `projectId` giá trị, thiết lập Project ID cho FCM
168
+ // Set FCM project ID if provided
163
169
  if let projectID = params["projectId"] as? String, !projectID.isEmpty {
164
170
  OmiClient.setFcmProjectId(projectID)
165
171
  }
166
172
 
167
- // Thử khởi tạo OmiClient với username & password
168
- do {
169
- try OmiClient.initWithUsername(userName, password: password, realm: realm, proxy: "")
170
- } catch {
171
- print("🚨 Lỗi khởi tạo OmiClient: \(error.localizedDescription)")
172
- return false
173
- }
173
+ // Initialize OmiClient with username & password
174
+ let isSkipDevices = (params["isSkipDevices"] as? Bool) ?? false
175
+ OmiClient.initWithUsername(userName, password: password, realm: realm, proxy: proxy, isSkipDevices: isSkipDevices)
174
176
 
175
- // Thiết lập FCM Token cho user
177
+ // Set FCM token for push notifications
176
178
  OmiClient.setUserPushNotificationToken(token)
177
179
 
178
- // Đảm bảo requestPermission chạy trên main thread
180
+ // Request permissions on main thread
179
181
  let isVideo = (params["isVideo"] as? Bool) ?? false
180
182
  if isVideo {
181
183
  DispatchQueue.main.async { [weak self] in
182
- guard let strongSelf = self else {
183
- print("⚠️ Không thể gọi requestPermission vì self đã bị giải phóng!")
184
- return
185
- }
184
+ guard let strongSelf = self else { return }
186
185
  strongSelf.requestPermission(isVideo: isVideo)
187
186
  }
188
187
  }
@@ -249,25 +248,25 @@ class CallManager {
249
248
 
250
249
  func registerNotificationCenter(showMissedCall: Bool) {
251
250
  DispatchQueue.main.async { [weak self] in
252
- guard let self = self else { return }
253
- NotificationCenter.default.removeObserver(CallManager.instance!)
254
- NotificationCenter.default.addObserver(CallManager.instance!,
251
+ guard let self = self, let instance = CallManager.instance else { return }
252
+ NotificationCenter.default.removeObserver(instance)
253
+ NotificationCenter.default.addObserver(instance,
255
254
  selector: #selector(self.callStateChanged(_:)),
256
255
  name: NSNotification.Name.OMICallStateChanged,
257
256
  object: nil
258
257
  )
259
- NotificationCenter.default.addObserver(CallManager.instance!,
258
+ NotificationCenter.default.addObserver(instance,
260
259
  selector: #selector(self.callDealloc(_:)),
261
260
  name: NSNotification.Name.OMICallDealloc,
262
261
  object: nil
263
262
  )
264
- NotificationCenter.default.addObserver(CallManager.instance!,
263
+ NotificationCenter.default.addObserver(instance,
265
264
  selector: #selector(self.switchBoardAnswer(_:)),
266
265
  name: NSNotification.Name.OMICallSwitchBoardAnswer,
267
266
  object: nil
268
267
  )
269
- NotificationCenter.default.addObserver(CallManager.instance!, selector: #selector(self.updateNetworkHealth(_:)), name: NSNotification.Name.OMICallNetworkQuality, object: nil)
270
- NotificationCenter.default.addObserver(CallManager.instance!, selector: #selector(self.audioChanged(_:)), name: NSNotification.Name.OMICallAudioRouteChange, object: nil)
268
+ NotificationCenter.default.addObserver(instance, selector: #selector(self.updateNetworkHealth(_:)), name: NSNotification.Name.OMICallNetworkQuality, object: nil)
269
+ NotificationCenter.default.addObserver(instance, selector: #selector(self.audioChanged(_:)), name: NSNotification.Name.OMICallAudioRouteChange, object: nil)
271
270
  if (showMissedCall) {
272
271
  self.showMissedCall()
273
272
  }
@@ -275,8 +274,9 @@ class CallManager {
275
274
  }
276
275
 
277
276
  func registerVideoEvent() {
278
- DispatchQueue.main.async {
279
- NotificationCenter.default.addObserver(CallManager.instance!,
277
+ DispatchQueue.main.async { [weak self] in
278
+ guard let self = self, let instance = CallManager.instance else { return }
279
+ NotificationCenter.default.addObserver(instance,
280
280
  selector: #selector(self.videoUpdate(_:)),
281
281
  name: NSNotification.Name.OMICallVideoInfo,
282
282
  object: nil
@@ -286,7 +286,8 @@ class CallManager {
286
286
 
287
287
  func removeVideoEvent() {
288
288
  DispatchQueue.main.async {
289
- NotificationCenter.default.removeObserver(CallManager.instance!, name: NSNotification.Name.OMICallVideoInfo, object: nil)
289
+ guard let instance = CallManager.instance else { return }
290
+ NotificationCenter.default.removeObserver(instance, name: NSNotification.Name.OMICallVideoInfo, object: nil)
290
291
  }
291
292
  }
292
293
 
@@ -306,7 +307,16 @@ class CallManager {
306
307
  let state = userInfo[OMINotificationNetworkStatusKey] as? Int else {
307
308
  return;
308
309
  }
309
- OmikitPlugin.instance.sendEvent(withName: CALL_QUALITY, body: ["quality": state])
310
+ // Build stat map with full diagnostics
311
+ var stat: [String: Any] = [:]
312
+ if let mos = userInfo[OMINotificationMOSKey] as? Double { stat["mos"] = mos }
313
+ if let jitter = userInfo[OMINotificationJitterKey] as? Double { stat["jitter"] = jitter }
314
+ if let latency = userInfo[OMINotificationLatencyKey] as? Double { stat["latency"] = latency }
315
+ if let ppl = userInfo[OMINotificationPPLKey] as? Double { stat["packetLoss"] = ppl }
316
+ OmikitPlugin.instance?.sendEvent(withName: CALL_QUALITY, body: [
317
+ "quality": state,
318
+ "stat": stat
319
+ ])
310
320
  }
311
321
 
312
322
  @objc func videoUpdate(_ notification: NSNotification) {
@@ -316,7 +326,7 @@ class CallManager {
316
326
  }
317
327
  switch (state) {
318
328
  case 1:
319
- OmikitPlugin.instance.sendEvent(withName: REMOTE_VIDEO_READY, body: nil)
329
+ OmikitPlugin.instance?.sendEvent(withName: REMOTE_VIDEO_READY, body: nil)
320
330
  break
321
331
  default:
322
332
  break
@@ -329,13 +339,13 @@ class CallManager {
329
339
  return;
330
340
  }
331
341
  guestPhone = sip
332
- OmikitPlugin.instance.sendEvent(withName: SWITCHBOARD_ANSWER, body: ["sip": sip])
342
+ OmikitPlugin.instance?.sendEvent(withName: SWITCHBOARD_ANSWER, body: ["sip": sip])
333
343
  }
334
344
 
335
345
  @objc func callDealloc(_ notification: NSNotification) {
336
346
  if (tempCallInfo != nil) {
337
347
  tempCallInfo!["status"] = OMICallState.disconnected.rawValue
338
- OmikitPlugin.instance.sendEvent(withName: CALL_STATE_CHANGED, body: tempCallInfo!)
348
+ OmikitPlugin.instance?.sendEvent(withName: CALL_STATE_CHANGED, body: tempCallInfo!)
339
349
  }
340
350
  }
341
351
 
@@ -380,7 +390,7 @@ class CallManager {
380
390
  }
381
391
  isSpeaker = call.speaker
382
392
  lastStatusCall = "answered"
383
- OmikitPlugin.instance.sendMuteStatus()
393
+ OmikitPlugin.instance?.sendMuteStatus()
384
394
  break
385
395
  case OMICallState.incoming.rawValue:
386
396
  guestPhone = call.callerNumber ?? ""
@@ -518,7 +528,7 @@ func startCall(_ phoneNumber: String, isVideo: Bool, completion: @escaping (_: S
518
528
  let callInfo = [
519
529
  "status": OMICallState.disconnected.rawValue,
520
530
  ]
521
- OmikitPlugin.instance.sendEvent(withName: CALL_STATE_CHANGED, body: callInfo)
531
+ OmikitPlugin.instance?.sendEvent(withName: CALL_STATE_CHANGED, body: callInfo)
522
532
  return [:]
523
533
  }
524
534
  tempCallInfo = getCallInfo(call: call)
@@ -571,7 +581,7 @@ func startCall(_ phoneNumber: String, isVideo: Bool, completion: @escaping (_: S
571
581
  func toogleSpeaker() {
572
582
  let result = omiLib.callManager.audioController.toggleSpeaker();
573
583
  isSpeaker = result
574
- OmikitPlugin.instance.sendSpeakerStatus()
584
+ OmikitPlugin.instance?.sendSpeakerStatus()
575
585
  }
576
586
 
577
587
  func getAudioOutputs() -> [[String: String]] {
@@ -45,3 +45,4 @@ let CLICK_MISSED_CALL = "CLICK_MISSED_CALL"
45
45
  let SWITCHBOARD_ANSWER = "SWITCHBOARD_ANSWER"
46
46
  let CALL_QUALITY = "CALL_QUALITY"
47
47
  let AUDIO_CHANGE = "AUDIO_CHANGE"
48
+ let REQUEST_PERMISSION = "REQUEST_PERMISSION"
@@ -12,7 +12,8 @@ RCT_EXTERN_METHOD(configPushNotification:(id)data
12
12
  rejecter:(RCTPromiseRejectBlock)reject)
13
13
 
14
14
  // Get initial call
15
- RCT_EXTERN_METHOD(getInitialCall:(RCTPromiseResolveBlock)resolve
15
+ RCT_EXTERN_METHOD(getInitialCall:(id)data
16
+ resolver:(RCTPromiseResolveBlock)resolve
16
17
  rejecter:(RCTPromiseRejectBlock)reject)
17
18
 
18
19
  // Initialize call with user password
@@ -97,6 +98,25 @@ RCT_EXTERN_METHOD(getUserInfo:(id)data
97
98
  resolver:(RCTPromiseResolveBlock)resolve
98
99
  rejecter:(RCTPromiseRejectBlock)reject)
99
100
 
101
+ // Getter functions
102
+ RCT_EXTERN_METHOD(getProjectId:(RCTPromiseResolveBlock)resolve
103
+ rejecter:(RCTPromiseRejectBlock)reject)
104
+
105
+ RCT_EXTERN_METHOD(getSipInfo:(RCTPromiseResolveBlock)resolve
106
+ rejecter:(RCTPromiseRejectBlock)reject)
107
+
108
+ RCT_EXTERN_METHOD(getDeviceId:(RCTPromiseResolveBlock)resolve
109
+ rejecter:(RCTPromiseRejectBlock)reject)
110
+
111
+ RCT_EXTERN_METHOD(getFcmToken:(RCTPromiseResolveBlock)resolve
112
+ rejecter:(RCTPromiseRejectBlock)reject)
113
+
114
+ RCT_EXTERN_METHOD(getAppId:(RCTPromiseResolveBlock)resolve
115
+ rejecter:(RCTPromiseRejectBlock)reject)
116
+
117
+ RCT_EXTERN_METHOD(getVoipToken:(RCTPromiseResolveBlock)resolve
118
+ rejecter:(RCTPromiseRejectBlock)reject)
119
+
100
120
  // Get audio
101
121
  RCT_EXTERN_METHOD(getAudio:(RCTPromiseResolveBlock)resolve
102
122
  rejecter:(RCTPromiseRejectBlock)reject)
@@ -118,6 +138,60 @@ RCT_EXTERN_METHOD(transferCall:(id)data
118
138
  RCT_EXTERN_METHOD(rejectCall:(RCTPromiseResolveBlock)resolve
119
139
  rejecter:(RCTPromiseRejectBlock)reject)
120
140
 
141
+ // Hold
142
+ RCT_EXTERN_METHOD(onHold:(id)data
143
+ resolver:(RCTPromiseResolveBlock)resolve
144
+ rejecter:(RCTPromiseRejectBlock)reject)
145
+
146
+ // System notification (Android-only stubs)
147
+ RCT_EXTERN_METHOD(hideSystemNotificationSafely:(RCTPromiseResolveBlock)resolve
148
+ rejecter:(RCTPromiseRejectBlock)reject)
149
+
150
+ RCT_EXTERN_METHOD(hideSystemNotificationOnly:(RCTPromiseResolveBlock)resolve
151
+ rejecter:(RCTPromiseRejectBlock)reject)
152
+
153
+ RCT_EXTERN_METHOD(hideSystemNotificationAndUnregister:(id)data
154
+ resolver:(RCTPromiseResolveBlock)resolve
155
+ rejecter:(RCTPromiseRejectBlock)reject)
156
+
157
+ // Permission management (Android-only stubs)
158
+ RCT_EXTERN_METHOD(checkAndRequestPermissions:(id)data
159
+ resolver:(RCTPromiseResolveBlock)resolve
160
+ rejecter:(RCTPromiseRejectBlock)reject)
161
+
162
+ RCT_EXTERN_METHOD(checkPermissionStatus:(RCTPromiseResolveBlock)resolve
163
+ rejecter:(RCTPromiseRejectBlock)reject)
164
+
165
+ RCT_EXTERN_METHOD(requestPermissionsByCodes:(id)data
166
+ resolver:(RCTPromiseResolveBlock)resolve
167
+ rejecter:(RCTPromiseRejectBlock)reject)
168
+
169
+ // System alert window (Android-only stubs)
170
+ RCT_EXTERN_METHOD(systemAlertWindow:(RCTPromiseResolveBlock)resolve
171
+ rejecter:(RCTPromiseRejectBlock)reject)
172
+
173
+ RCT_EXTERN_METHOD(requestSystemAlertWindowPermission:(RCTPromiseResolveBlock)resolve
174
+ rejecter:(RCTPromiseRejectBlock)reject)
175
+
176
+ RCT_EXTERN_METHOD(openSystemAlertSetting:(RCTPromiseResolveBlock)resolve
177
+ rejecter:(RCTPromiseRejectBlock)reject)
178
+
179
+ // Credentials & registration
180
+ RCT_EXTERN_METHOD(checkCredentials:(id)data
181
+ resolver:(RCTPromiseResolveBlock)resolve
182
+ rejecter:(RCTPromiseRejectBlock)reject)
183
+
184
+ RCT_EXTERN_METHOD(registerWithOptions:(id)data
185
+ resolver:(RCTPromiseResolveBlock)resolve
186
+ rejecter:(RCTPromiseRejectBlock)reject)
187
+
188
+ // Keep alive (Android-only stubs)
189
+ RCT_EXTERN_METHOD(getKeepAliveStatus:(RCTPromiseResolveBlock)resolve
190
+ rejecter:(RCTPromiseRejectBlock)reject)
191
+
192
+ RCT_EXTERN_METHOD(triggerKeepAlivePing:(RCTPromiseResolveBlock)resolve
193
+ rejecter:(RCTPromiseRejectBlock)reject)
194
+
121
195
  // Required to run on the main thread
122
196
  + (BOOL)requiresMainQueueSetup
123
197
  {
@@ -2,22 +2,60 @@ import Foundation
2
2
  import React
3
3
  import OmiKit
4
4
 
5
+ #if RCT_NEW_ARCH_ENABLED
6
+ import React_Codegen
7
+ #endif
8
+
5
9
  @objc(OmikitPlugin)
6
10
  public class OmikitPlugin: RCTEventEmitter {
7
-
11
+
8
12
  @objc public static var instance : OmikitPlugin!
9
-
13
+
10
14
  public override init() {
11
15
  super.init()
12
16
  OmikitPlugin.instance = self
13
17
  }
18
+
19
+ // TurboModule conformance
20
+ #if RCT_NEW_ARCH_ENABLED
21
+ @objc public static func moduleName() -> String {
22
+ return "OmikitPlugin"
23
+ }
24
+ #endif
25
+
26
+ @objc public override static func moduleName() -> String! {
27
+ return "OmikitPlugin"
28
+ }
29
+
30
+ // Export constants for event names
31
+ @objc public override func constantsToExport() -> [AnyHashable : Any]! {
32
+ return [
33
+ "CALL_STATE_CHANGED": CALL_STATE_CHANGED,
34
+ "MUTED": MUTED,
35
+ "HOLD": HOLD,
36
+ "SPEAKER": SPEAKER,
37
+ "REMOTE_VIDEO_READY": REMOTE_VIDEO_READY,
38
+ "CLICK_MISSED_CALL": CLICK_MISSED_CALL,
39
+ "SWITCHBOARD_ANSWER": SWITCHBOARD_ANSWER,
40
+ "CALL_QUALITY": CALL_QUALITY,
41
+ "AUDIO_CHANGE": AUDIO_CHANGE,
42
+ "REQUEST_PERMISSION": REQUEST_PERMISSION
43
+ ]
44
+ }
14
45
 
15
46
 
16
47
  // MARK: - Service Methods
17
48
  @objc(startServices:rejecter:)
18
- func startServices(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
19
- CallManager.shareInstance().registerNotificationCenter(showMissedCall: true)
20
- resolve(true)
49
+ func startServices(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
50
+ // Ensure instance is set for callbacks
51
+ if OmikitPlugin.instance == nil {
52
+ OmikitPlugin.instance = self
53
+ }
54
+
55
+ DispatchQueue.main.async {
56
+ CallManager.shareInstance().registerNotificationCenter(showMissedCall: true)
57
+ resolve(true)
58
+ }
21
59
  }
22
60
 
23
61
  @objc(configPushNotification:resolver:rejecter:)
@@ -31,8 +69,8 @@ public class OmikitPlugin: RCTEventEmitter {
31
69
  }
32
70
 
33
71
 
34
- @objc(getInitialCall:rejecter:)
35
- func getInitialCall(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
72
+ @objc(getInitialCall:resolver:rejecter:)
73
+ func getInitialCall(_ data: Any, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
36
74
  if let call = CallManager.shareInstance().getAvailableCall() {
37
75
  let data: [String: Any] = [
38
76
  "callerNumber": call.callerNumber,
@@ -211,13 +249,19 @@ public class OmikitPlugin: RCTEventEmitter {
211
249
  }
212
250
  }
213
251
 
214
- @objc(getUserInfor:resolver:rejecter:)
215
- func getUserInfor(data: Any, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
216
- guard let phone = data as? String else {
217
- reject("INVALID_DATA", "Expected a phone number as a string.", nil)
252
+ @objc(getUserInfo:resolver:rejecter:)
253
+ func getUserInfo(data: Any, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
254
+ // Support both {phone: "xxx"} object and direct string
255
+ let phone: String
256
+ if let dict = data as? [String: Any], let p = dict["phone"] as? String {
257
+ phone = p
258
+ } else if let p = data as? String {
259
+ phone = p
260
+ } else {
261
+ reject("INVALID_DATA", "Expected a dictionary with phone key or a phone number string.", nil)
218
262
  return
219
263
  }
220
-
264
+
221
265
  CallManager.shareInstance().getUserInfo(phone: phone) { userInfo in
222
266
  if userInfo.isEmpty {
223
267
  reject("USER_NOT_FOUND", "User not found for phone number: \(phone)", nil)
@@ -226,7 +270,38 @@ public class OmikitPlugin: RCTEventEmitter {
226
270
  }
227
271
  }
228
272
  }
229
-
273
+
274
+ // MARK: - Getter Functions
275
+ @objc(getProjectId:rejecter:)
276
+ func getProjectId(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
277
+ resolve(OmiClient.getProjectId())
278
+ }
279
+
280
+ @objc(getSipInfo:rejecter:)
281
+ func getSipInfo(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
282
+ resolve(OmiClient.getSipInfo())
283
+ }
284
+
285
+ @objc(getDeviceId:rejecter:)
286
+ func getDeviceId(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
287
+ resolve(OmiClient.getDeviceId())
288
+ }
289
+
290
+ @objc(getFcmToken:rejecter:)
291
+ func getFcmToken(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
292
+ resolve(OmiClient.getFcmToken())
293
+ }
294
+
295
+ @objc(getAppId:rejecter:)
296
+ func getAppId(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
297
+ resolve(OmiClient.getAppId())
298
+ }
299
+
300
+ @objc(getVoipToken:rejecter:)
301
+ func getVoipToken(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
302
+ resolve(OmiClient.getVoipToken())
303
+ }
304
+
230
305
  // MARK: - Audio Methods
231
306
  @objc(getAudio:rejecter:)
232
307
  func getAudio(resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
@@ -236,12 +311,21 @@ public class OmikitPlugin: RCTEventEmitter {
236
311
 
237
312
  @objc(setAudio:resolver:rejecter:)
238
313
  func setAudio(data: Any, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
239
- guard let dataOmi = data as? [String: Any],
240
- let portType = dataOmi["portType"] as? String else {
314
+ guard let dataOmi = data as? [String: Any] else {
241
315
  reject("INVALID_DATA", "Expected a dictionary with port type.", nil)
242
316
  return
243
317
  }
244
-
318
+ // Support both number and string for portType
319
+ let portType: String
320
+ if let pt = dataOmi["portType"] as? String {
321
+ portType = pt
322
+ } else if let pt = dataOmi["portType"] as? NSNumber {
323
+ portType = pt.stringValue
324
+ } else {
325
+ reject("INVALID_DATA", "portType must be a number or string.", nil)
326
+ return
327
+ }
328
+
245
329
  CallManager.shareInstance().setAudioOutputs(portType: portType)
246
330
  resolve(true)
247
331
  }
@@ -315,6 +399,105 @@ public class OmikitPlugin: RCTEventEmitter {
315
399
  SWITCHBOARD_ANSWER,
316
400
  CALL_QUALITY,
317
401
  AUDIO_CHANGE,
402
+ REQUEST_PERMISSION
318
403
  ]
319
404
  }
405
+
406
+ // MARK: - Stub Methods for TurboModule Compatibility
407
+ // These methods are Android-only but required by Codegen spec
408
+
409
+ @objc(onHold:resolver:rejecter:)
410
+ func onHold(data: Any, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
411
+ // iOS uses toggleHold instead
412
+ resolve(true)
413
+ }
414
+
415
+ @objc(hideSystemNotificationSafely:rejecter:)
416
+ func hideSystemNotificationSafely(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
417
+ // Android-only feature
418
+ resolve(true)
419
+ }
420
+
421
+ @objc(hideSystemNotificationOnly:rejecter:)
422
+ func hideSystemNotificationOnly(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
423
+ // Android-only feature
424
+ resolve(true)
425
+ }
426
+
427
+ @objc(hideSystemNotificationAndUnregister:resolver:rejecter:)
428
+ func hideSystemNotificationAndUnregister(data: Any, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
429
+ // Android-only feature
430
+ resolve(true)
431
+ }
432
+
433
+ @objc(checkAndRequestPermissions:resolver:rejecter:)
434
+ func checkAndRequestPermissions(data: Any, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
435
+ // Android-only feature, iOS handles permissions differently
436
+ resolve(true)
437
+ }
438
+
439
+ @objc(checkPermissionStatus:rejecter:)
440
+ func checkPermissionStatus(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
441
+ // Android-only feature
442
+ resolve(nil)
443
+ }
444
+
445
+ @objc(requestPermissionsByCodes:resolver:rejecter:)
446
+ func requestPermissionsByCodes(data: Any, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
447
+ // Android-only feature
448
+ resolve(true)
449
+ }
450
+
451
+ @objc(systemAlertWindow:rejecter:)
452
+ func systemAlertWindow(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
453
+ // Android-only feature
454
+ resolve(true)
455
+ }
456
+
457
+ @objc(requestSystemAlertWindowPermission:rejecter:)
458
+ func requestSystemAlertWindowPermission(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
459
+ // Android-only feature
460
+ resolve(true)
461
+ }
462
+
463
+ @objc(openSystemAlertSetting:rejecter:)
464
+ func openSystemAlertSetting(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
465
+ // Android-only feature
466
+ resolve(nil)
467
+ }
468
+
469
+ @objc(checkCredentials:resolver:rejecter:)
470
+ func checkCredentials(data: Any, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
471
+ // Stub for iOS - not implemented yet
472
+ resolve([
473
+ "success": true,
474
+ "statusCode": 200,
475
+ "message": "iOS stub"
476
+ ])
477
+ }
478
+
479
+ @objc(registerWithOptions:resolver:rejecter:)
480
+ func registerWithOptions(data: Any, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
481
+ // Stub for iOS - not implemented yet
482
+ resolve([
483
+ "success": true,
484
+ "statusCode": 200,
485
+ "message": "iOS stub"
486
+ ])
487
+ }
488
+
489
+ @objc(getKeepAliveStatus:rejecter:)
490
+ func getKeepAliveStatus(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
491
+ // Android-only feature
492
+ resolve([
493
+ "isActive": false,
494
+ "platform": "ios"
495
+ ])
496
+ }
497
+
498
+ @objc(triggerKeepAlivePing:rejecter:)
499
+ func triggerKeepAlivePing(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
500
+ // Android-only feature
501
+ resolve(true)
502
+ }
320
503
  }