@qusaieilouti99/call-manager 0.1.79 → 0.1.80

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.
@@ -20,6 +20,11 @@ Pod::Spec.new do |s|
20
20
  "cpp/**/*.{hpp,cpp}",
21
21
  ]
22
22
 
23
+ s.pod_target_xcconfig = {
24
+ # C++ compiler flags, mainly for folly.
25
+ "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES"
26
+ }
27
+
23
28
  s.dependency 'React-jsi'
24
29
  s.dependency 'React-callinvoker'
25
30
 
@@ -7,16 +7,16 @@ protocol AudioManagerDelegate: AnyObject {
7
7
  func audioManager(_ manager: AudioManager, didChangeDevices routeInfo: AudioRoutesInfo)
8
8
  }
9
9
 
10
- class AudioManager: NSObject {
10
+ class AudioManager {
11
11
  private let logger = Logger(subsystem: "com.qusaieilouti99.callmanager", category: "AudioManager")
12
12
  private weak var delegate: AudioManagerDelegate?
13
13
  private var audioSession: AVAudioSession
14
14
  private var lastRouteInfo: AudioRoutesInfo?
15
+ private var notificationObservers: [NSObjectProtocol] = []
15
16
 
16
17
  init(delegate: AudioManagerDelegate) {
17
18
  self.delegate = delegate
18
19
  self.audioSession = AVAudioSession.sharedInstance()
19
- super.init()
20
20
 
21
21
  logger.info("🔊 AudioManager initializing...")
22
22
  setupNotifications()
@@ -25,31 +25,36 @@ class AudioManager: NSObject {
25
25
 
26
26
  deinit {
27
27
  logger.info("🔊 AudioManager deinitializing...")
28
- NotificationCenter.default.removeObserver(self)
28
+ for observer in notificationObservers {
29
+ NotificationCenter.default.removeObserver(observer)
30
+ }
31
+ notificationObservers.removeAll()
29
32
  }
30
33
 
31
- // MARK: - Setup
32
34
  private func setupNotifications() {
33
35
  logger.info("🔊 Setting up audio session notifications...")
34
36
 
35
- NotificationCenter.default.addObserver(
36
- self,
37
- selector: #selector(audioRouteChanged),
38
- name: AVAudioSession.routeChangeNotification,
39
- object: nil
40
- )
41
-
42
- NotificationCenter.default.addObserver(
43
- self,
44
- selector: #selector(audioSessionInterrupted),
45
- name: AVAudioSession.interruptionNotification,
46
- object: nil
47
- )
37
+ let routeChangeObserver = NotificationCenter.default.addObserver(
38
+ forName: AVAudioSession.routeChangeNotification,
39
+ object: nil,
40
+ queue: nil
41
+ ) { [weak self] notification in
42
+ self?.handleAudioRouteChanged(notification: notification)
43
+ }
44
+ notificationObservers.append(routeChangeObserver)
45
+
46
+ let interruptionObserver = NotificationCenter.default.addObserver(
47
+ forName: AVAudioSession.interruptionNotification,
48
+ object: nil,
49
+ queue: nil
50
+ ) { [weak self] notification in
51
+ self?.handleAudioSessionInterrupted(notification: notification)
52
+ }
53
+ notificationObservers.append(interruptionObserver)
48
54
 
49
55
  logger.info("🔊 ✅ Audio session notifications setup completed")
50
56
  }
51
57
 
52
- // MARK: - Public Methods
53
58
  func configureForIncomingCall() {
54
59
  logger.info("🔊 Configuring audio session for incoming call...")
55
60
 
@@ -103,7 +108,6 @@ class AudioManager: NSObject {
103
108
  logger.debug("🔊 Current route inputs: \(currentRoute.inputs.map { $0.portType.rawValue })")
104
109
  logger.debug("🔊 Current route outputs: \(currentRoute.outputs.map { $0.portType.rawValue })")
105
110
 
106
- // Check for available inputs/outputs
107
111
  if let availableInputs = audioSession.availableInputs {
108
112
  logger.debug("🔊 Available inputs: \(availableInputs.map { $0.portType.rawValue })")
109
113
 
@@ -126,7 +130,6 @@ class AudioManager: NSObject {
126
130
  }
127
131
  }
128
132
 
129
- // Determine current route
130
133
  let currentRouteString = getCurrentAudioRoute()
131
134
 
132
135
  let routeInfo = AudioRoutesInfo(devices: devices, currentRoute: currentRouteString)
@@ -153,11 +156,9 @@ class AudioManager: NSObject {
153
156
  case "Bluetooth":
154
157
  logger.debug("🔊 Setting to Bluetooth (system managed)...")
155
158
  try audioSession.overrideOutputAudioPort(.none)
156
- // Bluetooth routing is handled automatically by the system
157
159
  case "Headset":
158
160
  logger.debug("🔊 Setting to Headset (system managed)...")
159
161
  try audioSession.overrideOutputAudioPort(.none)
160
- // Headset routing is handled automatically by the system
161
162
  default:
162
163
  logger.warning("🔊 ⚠️ Unknown audio route: \(route)")
163
164
  return
@@ -176,8 +177,6 @@ class AudioManager: NSObject {
176
177
 
177
178
  func setMuted(_ muted: Bool) {
178
179
  logger.info("🔊 Mute state changed to: \(muted)")
179
- // Note: Muting is typically handled by CallKit automatically
180
- // This is here for consistency with Android implementation
181
180
  }
182
181
 
183
182
  func cleanup() {
@@ -191,7 +190,6 @@ class AudioManager: NSObject {
191
190
  }
192
191
  }
193
192
 
194
- // MARK: - Private Methods
195
193
  private func getCurrentAudioRoute() -> String {
196
194
  let currentRoute = audioSession.currentRoute
197
195
 
@@ -214,23 +212,22 @@ class AudioManager: NSObject {
214
212
  }
215
213
 
216
214
  logger.debug("🔊 No specific route found, defaulting to Earpiece")
217
- return "Earpiece" // Default fallback
215
+ return "Earpiece"
218
216
  }
219
217
 
220
218
  private func notifyRouteChange() {
221
219
  logger.debug("🔊 Notifying delegate about route change...")
222
220
  let routeInfo = getAudioDevices()
223
- delegate?.audioManager(self, didChangeRoute: routeInfo)
221
+ self.delegate?.audioManager(self, didChangeRoute: routeInfo)
224
222
  }
225
223
 
226
224
  private func notifyDeviceChange() {
227
225
  logger.debug("🔊 Notifying delegate about device change...")
228
226
  let routeInfo = getAudioDevices()
229
- delegate?.audioManager(self, didChangeDevices: routeInfo)
227
+ self.delegate?.audioManager(self, didChangeDevices: routeInfo)
230
228
  }
231
229
 
232
- // MARK: - Notification Handlers
233
- @objc private func audioRouteChanged(notification: Notification) {
230
+ private func handleAudioRouteChanged(notification: Notification) {
234
231
  logger.info("🔊 Audio route changed notification received")
235
232
 
236
233
  guard let userInfo = notification.userInfo,
@@ -255,7 +252,7 @@ class AudioManager: NSObject {
255
252
  }
256
253
  }
257
254
 
258
- @objc private func audioSessionInterrupted(notification: Notification) {
255
+ private func handleAudioSessionInterrupted(notification: Notification) {
259
256
  logger.info("🔊 Audio session interrupted notification received")
260
257
 
261
258
  guard let userInfo = notification.userInfo,
@@ -274,7 +271,6 @@ class AudioManager: NSObject {
274
271
  let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
275
272
  if options.contains(.shouldResume) {
276
273
  logger.info("🔊 Should resume audio session")
277
- // Audio session will be reactivated by CallKit
278
274
  }
279
275
  }
280
276
  @unknown default:
@@ -5,35 +5,28 @@ import UserNotifications
5
5
  import UIKit
6
6
  import OSLog
7
7
 
8
- @objc public class CallEngine: NSObject {
8
+ public class CallEngine {
9
9
  static let shared = CallEngine()
10
10
 
11
- // MARK: - Constants
12
11
  private let logger = Logger(subsystem: "com.qusaieilouti99.callmanager", category: "CallEngine")
13
12
  private let defaultTimeout: TimeInterval = 60.0
14
13
 
15
- // MARK: - Core Components
16
14
  private var callKitManager: CallKitManager?
17
15
  private var audioManager: AudioManager?
18
16
 
19
- // MARK: - State Management
20
17
  private var activeCalls: [String: CallInfo] = [:]
21
18
  private var callMetadata: [String: String] = [:]
22
19
  private var currentCallId: String?
23
20
  private var canMakeMultipleCalls: Bool = false
24
21
 
25
- // MARK: - Event System
26
22
  private var eventHandler: ((CallEventType, String) -> Void)?
27
23
  private var cachedEvents: [(CallEventType, String)] = []
28
24
 
29
- // MARK: - Internal Call End Listeners (for UI components)
30
25
  private var callEndListeners: [(String) -> Void] = []
31
26
 
32
- // MARK: - Initialization
33
27
  private var isInitialized: Bool = false
34
28
 
35
- private override init() {
36
- super.init()
29
+ private init() {
37
30
  logger.info("🚀 CallEngine singleton created")
38
31
  }
39
32
 
@@ -52,7 +45,6 @@ import OSLog
52
45
  logger.info("🚀 ✅ CallEngine initialized successfully")
53
46
  }
54
47
 
55
- // MARK: - Public API
56
48
  public func setCanMakeMultipleCalls(_ allow: Bool) {
57
49
  canMakeMultipleCalls = allow
58
50
  logger.info("🚀 canMakeMultipleCalls set to: \(allow)")
@@ -60,7 +52,7 @@ import OSLog
60
52
 
61
53
  public func getCurrentCallState() -> String {
62
54
  logger.debug("🚀 Getting current call state...")
63
- let callsArray = activeCalls.values.map { $0.toJSONObject() }
55
+ let callsArray = self.activeCalls.values.map { $0.toJSONObject() }
64
56
  do {
65
57
  let jsonData = try JSONSerialization.data(withJSONObject: callsArray, options: [])
66
58
  let result = String(data: jsonData, encoding: .utf8) ?? "[]"
@@ -72,7 +64,6 @@ import OSLog
72
64
  }
73
65
  }
74
66
 
75
- // MARK: - Incoming Call Management
76
67
  public func reportIncomingCall(
77
68
  callId: String,
78
69
  callType: String,
@@ -83,29 +74,26 @@ import OSLog
83
74
  logger.info("📞 Reporting incoming call: callId=\(callId), type=\(callType), name=\(displayName)")
84
75
 
85
76
  if let metadata = metadata {
86
- callMetadata[callId] = metadata
77
+ self.callMetadata[callId] = metadata
87
78
  logger.info("📞 Metadata stored for call \(callId)")
88
79
  }
89
80
 
90
- // Check for call collision
91
- if let incomingCall = activeCalls.values.first(where: { $0.state == .incoming }),
81
+ if let incomingCall = self.activeCalls.values.first(where: { $0.state == .incoming }),
92
82
  incomingCall.callId != callId {
93
83
  logger.warning("📞 ⚠️ Incoming call collision detected. Auto-rejecting new call: \(callId)")
94
84
  rejectIncomingCallCollision(callId: callId, reason: "Another call is already incoming")
95
85
  return
96
86
  }
97
87
 
98
- // Check for active call when multiple calls not allowed
99
- if let activeCall = activeCalls.values.first(where: { $0.state == .active || $0.state == .held }),
88
+ if let activeCall = self.activeCalls.values.first(where: { $0.state == .active || $0.state == .held }),
100
89
  !canMakeMultipleCalls {
101
90
  logger.warning("📞 ⚠️ Active call exists when receiving incoming call. Auto-rejecting: \(callId)")
102
91
  rejectIncomingCallCollision(callId: callId, reason: "Another call is already active")
103
92
  return
104
93
  }
105
94
 
106
- // Hold existing active calls if multiple calls not allowed
107
95
  if !canMakeMultipleCalls {
108
- for call in activeCalls.values where call.state == .active {
96
+ for call in self.activeCalls.values where call.state == .active {
109
97
  logger.info("📞 Holding existing active call: \(call.callId)")
110
98
  holdCallInternal(callId: call.callId, heldBySystem: false)
111
99
  }
@@ -119,22 +107,22 @@ import OSLog
119
107
  state: .incoming
120
108
  )
121
109
 
122
- activeCalls[callId] = callInfo
123
- currentCallId = callId
124
- logger.info("📞 Call added to active calls. Total: \(activeCalls.count)")
110
+ self.activeCalls[callId] = callInfo
111
+ self.currentCallId = callId
112
+ logger.info("📞 Call added to active calls. Total: \(self.activeCalls.count)")
125
113
 
126
- callKitManager?.reportIncomingCall(callInfo: callInfo) { [weak self] error in
114
+ self.callKitManager?.reportIncomingCall(callInfo: callInfo) { [weak self] error in
115
+ guard let self = self else { return }
127
116
  if let error = error {
128
- self?.logger.error("📞 ❌ Failed to report incoming call: \(error.localizedDescription)")
129
- self?.endCallInternal(callId: callId)
117
+ self.logger.error("📞 ❌ Failed to report incoming call: \(error.localizedDescription)")
118
+ self.endCallInternal(callId: callId)
130
119
  } else {
131
- self?.logger.info("📞 ✅ Successfully reported incoming call for \(callId)")
132
- self?.audioManager?.configureForIncomingCall()
120
+ self.logger.info("📞 ✅ Successfully reported incoming call for \(callId)")
121
+ self.audioManager?.configureForIncomingCall()
133
122
  }
134
123
  }
135
124
  }
136
125
 
137
- // MARK: - Outgoing Call Management
138
126
  public func startOutgoingCall(
139
127
  callId: String,
140
128
  callType: String,
@@ -144,7 +132,7 @@ import OSLog
144
132
  logger.info("📞 Starting outgoing call: callId=\(callId), type=\(callType), target=\(targetName)")
145
133
 
146
134
  if let metadata = metadata {
147
- callMetadata[callId] = metadata
135
+ self.callMetadata[callId] = metadata
148
136
  logger.info("📞 Metadata stored for call \(callId)")
149
137
  }
150
138
 
@@ -157,9 +145,8 @@ import OSLog
157
145
  return
158
146
  }
159
147
 
160
- // Hold existing active calls if multiple calls not allowed
161
148
  if !canMakeMultipleCalls {
162
- for call in activeCalls.values where call.state == .active {
149
+ for call in self.activeCalls.values where call.state == .active {
163
150
  logger.info("📞 Holding existing active call: \(call.callId)")
164
151
  holdCallInternal(callId: call.callId, heldBySystem: false)
165
152
  }
@@ -173,17 +160,18 @@ import OSLog
173
160
  state: .dialing
174
161
  )
175
162
 
176
- activeCalls[callId] = callInfo
177
- currentCallId = callId
178
- logger.info("📞 Call added to active calls. Total: \(activeCalls.count)")
163
+ self.activeCalls[callId] = callInfo
164
+ self.currentCallId = callId
165
+ logger.info("📞 Call added to active calls. Total: \(self.activeCalls.count)")
179
166
 
180
- callKitManager?.startOutgoingCall(callInfo: callInfo) { [weak self] error in
167
+ self.callKitManager?.startOutgoingCall(callInfo: callInfo) { [weak self] error in
168
+ guard let self = self else { return }
181
169
  if let error = error {
182
- self?.logger.error("📞 ❌ Failed to start outgoing call: \(error.localizedDescription)")
183
- self?.endCallInternal(callId: callId)
170
+ self.logger.error("📞 ❌ Failed to start outgoing call: \(error.localizedDescription)")
171
+ self.endCallInternal(callId: callId)
184
172
  } else {
185
- self?.logger.info("📞 ✅ Successfully started outgoing call for \(callId)")
186
- self?.audioManager?.configureForOutgoingCall(isVideo: callType == "Video")
173
+ self.logger.info("📞 ✅ Successfully started outgoing call for \(callId)")
174
+ self.audioManager?.configureForOutgoingCall(isVideo: callType == "Video")
187
175
  }
188
176
  }
189
177
  }
@@ -197,18 +185,17 @@ import OSLog
197
185
  logger.info("📞 Starting call (direct active): callId=\(callId), type=\(callType), target=\(targetName)")
198
186
 
199
187
  if let metadata = metadata {
200
- callMetadata[callId] = metadata
188
+ self.callMetadata[callId] = metadata
201
189
  logger.info("📞 Metadata stored for call \(callId)")
202
190
  }
203
191
 
204
- if activeCalls.keys.contains(callId) {
192
+ if self.activeCalls.keys.contains(callId) {
205
193
  logger.warning("📞 ⚠️ Call \(callId) already exists, cannot start again")
206
194
  return
207
195
  }
208
196
 
209
- // Hold existing active calls if multiple calls not allowed
210
197
  if !canMakeMultipleCalls {
211
- for call in activeCalls.values where call.state == .active {
198
+ for call in self.activeCalls.values where call.state == .active {
212
199
  logger.info("📞 Holding existing active call: \(call.callId)")
213
200
  holdCallInternal(callId: call.callId, heldBySystem: false)
214
201
  }
@@ -222,17 +209,16 @@ import OSLog
222
209
  state: .active
223
210
  )
224
211
 
225
- activeCalls[callId] = callInfo
226
- currentCallId = callId
227
- logger.info("📞 Call added as ACTIVE. Total: \(activeCalls.count)")
212
+ self.activeCalls[callId] = callInfo
213
+ self.currentCallId = callId
214
+ logger.info("📞 Call added as ACTIVE. Total: \(self.activeCalls.count)")
228
215
 
229
- audioManager?.configureForActiveCall(isVideo: callType == "Video")
216
+ self.audioManager?.configureForActiveCall(isVideo: callType == "Video")
230
217
  emitOutgoingCallAnsweredWithMetadata(callId: callId)
231
218
 
232
219
  logger.info("📞 ✅ Call \(callId) started as ACTIVE")
233
220
  }
234
221
 
235
- // MARK: - Call Answer Management
236
222
  public func callAnsweredFromJS(callId: String) {
237
223
  logger.info("📞 Remote party answered: \(callId)")
238
224
  coreCallAnswered(callId: callId, isLocalAnswer: false)
@@ -246,23 +232,22 @@ import OSLog
246
232
  private func coreCallAnswered(callId: String, isLocalAnswer: Bool) {
247
233
  logger.info("📞 Core call answered: callId=\(callId), isLocalAnswer=\(isLocalAnswer)")
248
234
 
249
- guard var callInfo = activeCalls[callId] else {
235
+ guard var callInfo = self.activeCalls[callId] else {
250
236
  logger.warning("📞 ⚠️ Cannot answer call \(callId) - not found in active calls")
251
237
  return
252
238
  }
253
239
 
254
240
  let previousState = callInfo.state
255
241
  callInfo.updateState(.active)
256
- activeCalls[callId] = callInfo
257
- currentCallId = callId
242
+ self.activeCalls[callId] = callInfo
243
+ self.currentCallId = callId
258
244
 
259
- logger.info("📞 Call state updated: \(previousState.rawValue) → \(CallState.active.rawValue)")
245
+ logger.info("📞 Call state updated: \(previousState.stringValue) → \(CallState.active.stringValue)")
260
246
 
261
- audioManager?.configureForActiveCall(isVideo: callInfo.callType == "Video")
247
+ self.audioManager?.configureForActiveCall(isVideo: callInfo.callType == "Video")
262
248
 
263
- // Hold other active calls if multiple calls not allowed
264
249
  if !canMakeMultipleCalls {
265
- for call in activeCalls.values where call.callId != callId && call.state == .active {
250
+ for call in self.activeCalls.values where call.callId != callId && call.state == .active {
266
251
  logger.info("📞 Holding other active call: \(call.callId)")
267
252
  holdCallInternal(callId: call.callId, heldBySystem: false)
268
253
  }
@@ -277,11 +262,10 @@ import OSLog
277
262
  logger.info("📞 ✅ Call \(callId) successfully answered")
278
263
  }
279
264
 
280
- // MARK: - Call Control Methods
281
265
  public func setOnHold(callId: String, onHold: Bool) {
282
266
  logger.info("📞 Setting hold state: callId=\(callId), onHold=\(onHold)")
283
267
 
284
- guard let callInfo = activeCalls[callId] else {
268
+ guard let callInfo = self.activeCalls[callId] else {
285
269
  logger.warning("📞 ⚠️ Cannot set hold state for call \(callId) - not found")
286
270
  return
287
271
  }
@@ -291,23 +275,23 @@ import OSLog
291
275
  } else if !onHold && callInfo.state == .held {
292
276
  unholdCallInternal(callId: callId, resumedBySystem: false)
293
277
  } else {
294
- logger.warning("📞 ⚠️ Invalid hold operation: call \(callId) is in state \(callInfo.state.rawValue)")
278
+ logger.warning("📞 ⚠️ Invalid hold operation: call \(callId) is in state \(callInfo.state.stringValue)")
295
279
  }
296
280
  }
297
281
 
298
282
  private func holdCallInternal(callId: String, heldBySystem: Bool) {
299
283
  logger.info("📞 Holding call internally: callId=\(callId), heldBySystem=\(heldBySystem)")
300
284
 
301
- guard var callInfo = activeCalls[callId], callInfo.state == .active else {
285
+ guard var callInfo = self.activeCalls[callId], callInfo.state == .active else {
302
286
  logger.warning("📞 ⚠️ Cannot hold call \(callId) - not in active state")
303
287
  return
304
288
  }
305
289
 
306
290
  callInfo.updateState(.held)
307
291
  callInfo.wasHeldBySystem = heldBySystem
308
- activeCalls[callId] = callInfo
292
+ self.activeCalls[callId] = callInfo
309
293
 
310
- callKitManager?.setCallOnHold(callId: callId, onHold: true)
294
+ self.callKitManager?.setCallOnHold(callId: callId, onHold: true)
311
295
  emitEvent(.callHeld, data: ["callId": callId])
312
296
 
313
297
  logger.info("📞 ✅ Call \(callId) held successfully")
@@ -320,16 +304,16 @@ import OSLog
320
304
  private func unholdCallInternal(callId: String, resumedBySystem: Bool) {
321
305
  logger.info("📞 Unholding call internally: callId=\(callId), resumedBySystem=\(resumedBySystem)")
322
306
 
323
- guard var callInfo = activeCalls[callId], callInfo.state == .held else {
307
+ guard var callInfo = self.activeCalls[callId], callInfo.state == .held else {
324
308
  logger.warning("📞 ⚠️ Cannot unhold call \(callId) - not in held state")
325
309
  return
326
310
  }
327
311
 
328
312
  callInfo.updateState(.active)
329
313
  callInfo.wasHeldBySystem = false
330
- activeCalls[callId] = callInfo
314
+ self.activeCalls[callId] = callInfo
331
315
 
332
- callKitManager?.setCallOnHold(callId: callId, onHold: false)
316
+ self.callKitManager?.setCallOnHold(callId: callId, onHold: false)
333
317
  emitEvent(.callUnheld, data: ["callId": callId])
334
318
 
335
319
  logger.info("📞 ✅ Call \(callId) unheld successfully")
@@ -338,73 +322,70 @@ import OSLog
338
322
  public func setMuted(callId: String, muted: Bool) {
339
323
  logger.info("📞 Setting mute state: callId=\(callId), muted=\(muted)")
340
324
 
341
- guard activeCalls[callId] != nil else {
325
+ guard self.activeCalls[callId] != nil else {
342
326
  logger.warning("📞 ⚠️ Cannot set mute state for call \(callId) - not found")
343
327
  return
344
328
  }
345
329
 
346
- audioManager?.setMuted(muted)
330
+ self.audioManager?.setMuted(muted)
347
331
  let eventType: CallEventType = muted ? .callMuted : .callUnmuted
348
332
  emitEvent(eventType, data: ["callId": callId])
349
333
  logger.info("📞 ✅ Call \(callId) mute state changed to: \(muted)")
350
334
  }
351
335
 
352
- // MARK: - Call End Management
353
336
  public func endCall(callId: String) {
354
337
  logger.info("📞 Ending call: \(callId)")
355
338
  endCallInternal(callId: callId)
356
339
  }
357
340
 
358
341
  public func endAllCalls() {
359
- logger.info("📞 Ending all calls. Current active calls: \(activeCalls.count)")
342
+ logger.info("📞 Ending all calls. Current active calls: \(self.activeCalls.count)")
360
343
 
361
- let callIds = Array(activeCalls.keys)
344
+ let callIds = Array(self.activeCalls.keys)
362
345
  for callId in callIds {
363
346
  logger.info("📞 Ending call: \(callId)")
364
347
  endCallInternal(callId: callId)
365
348
  }
366
349
 
367
- activeCalls.removeAll()
368
- callMetadata.removeAll()
369
- currentCallId = nil
350
+ self.activeCalls.removeAll()
351
+ self.callMetadata.removeAll()
352
+ self.currentCallId = nil
370
353
 
371
- audioManager?.cleanup()
354
+ self.audioManager?.cleanup()
372
355
  logger.info("📞 ✅ All calls ended and cleanup completed")
373
356
  }
374
357
 
375
358
  private func endCallInternal(callId: String) {
376
359
  logger.info("📞 Ending call internally: \(callId)")
377
360
 
378
- guard var callInfo = activeCalls[callId] else {
361
+ guard var callInfo = self.activeCalls[callId] else {
379
362
  logger.warning("📞 ⚠️ Call \(callId) not found in active calls")
380
363
  return
381
364
  }
382
365
 
383
- let metadata = callMetadata.removeValue(forKey: callId)
366
+ let metadata = self.callMetadata.removeValue(forKey: callId)
384
367
  callInfo.updateState(.ended)
385
- activeCalls.removeValue(forKey: callId)
368
+ self.activeCalls.removeValue(forKey: callId)
386
369
 
387
- if currentCallId == callId {
388
- currentCallId = activeCalls.values.first { $0.state != .ended }?.callId
389
- logger.info("📞 Current call ID updated to: \(currentCallId ?? "nil")")
370
+ if self.currentCallId == callId {
371
+ self.currentCallId = self.activeCalls.values.first { $0.state != .ended }?.callId
372
+ logger.info("📞 Current call ID updated to: \(self.currentCallId ?? "nil")")
390
373
  }
391
374
 
392
- callKitManager?.endCall(callId: callId)
375
+ self.callKitManager?.endCall(callId: callId)
393
376
 
394
- if activeCalls.isEmpty {
377
+ if self.activeCalls.isEmpty {
395
378
  logger.info("📞 No more active calls, cleaning up audio")
396
- audioManager?.cleanup()
379
+ self.audioManager?.cleanup()
397
380
  }
398
381
 
399
- // Notify internal call end listeners (like CallActivity equivalent)
400
- logger.info("📞 Notifying \(callEndListeners.count) internal call end listeners")
401
- for listener in callEndListeners {
382
+ logger.info("📞 Notifying \(self.callEndListeners.count) internal call end listeners")
383
+ for listener in self.callEndListeners {
402
384
  DispatchQueue.main.async {
403
385
  listener(callId)
404
386
  }
405
387
  }
406
388
 
407
- // Emit end event with metadata
408
389
  var eventData: [String: Any] = ["callId": callId]
409
390
  if let metadata = metadata {
410
391
  eventData["metadata"] = metadata
@@ -412,50 +393,47 @@ import OSLog
412
393
  }
413
394
  emitEvent(.callEnded, data: eventData)
414
395
 
415
- logger.info("📞 ✅ Call \(callId) ended successfully. Remaining calls: \(activeCalls.count)")
396
+ logger.info("📞 ✅ Call \(callId) ended successfully. Remaining calls: \(self.activeCalls.count)")
416
397
  }
417
398
 
418
- // MARK: - Display Information Update
419
399
  public func updateDisplayCallInformation(callId: String, callerName: String) {
420
400
  logger.info("📲 Updating display info: callId=\(callId), callerName=\(callerName)")
421
401
 
422
- guard var callInfo = activeCalls[callId] else {
402
+ guard var callInfo = self.activeCalls[callId] else {
423
403
  logger.warning("📲 ⚠️ Cannot update display info for call \(callId) - not found")
424
404
  return
425
405
  }
426
406
 
427
407
  callInfo.updateDisplayName(callerName)
428
- activeCalls[callId] = callInfo
408
+ self.activeCalls[callId] = callInfo
429
409
 
430
- callKitManager?.updateCall(callId: callId, displayName: callerName)
410
+ self.callKitManager?.updateCall(callId: callId, displayName: callerName)
431
411
  logger.info("📲 ✅ Display info updated successfully")
432
412
  }
433
413
 
434
- // MARK: - Audio Management
435
414
  public func getAudioDevices() -> AudioRoutesInfo {
436
415
  logger.debug("🔊 Getting audio devices...")
437
- let result = audioManager?.getAudioDevices() ?? AudioRoutesInfo(devices: [], currentRoute: "Unknown")
416
+ let result = self.audioManager?.getAudioDevices() ?? AudioRoutesInfo(devices: [], currentRoute: "Unknown")
438
417
  logger.debug("🔊 Audio devices result: \(result.devices), current: \(result.currentRoute)")
439
418
  return result
440
419
  }
441
420
 
442
421
  public func setAudioRoute(_ route: String) {
443
422
  logger.info("🔊 Setting audio route: \(route)")
444
- audioManager?.setAudioRoute(route)
423
+ self.audioManager?.setAudioRoute(route)
445
424
  }
446
425
 
447
- // MARK: - Event System
448
426
  public func setEventHandler(_ handler: ((CallEventType, String) -> Void)?) {
449
427
  logger.info("📡 Setting event handler. Handler present: \(handler != nil)")
450
- eventHandler = handler
428
+ self.eventHandler = handler
451
429
 
452
- if let handler = handler, !cachedEvents.isEmpty {
453
- logger.info("📡 Emitting \(cachedEvents.count) cached events")
454
- for (type, data) in cachedEvents {
430
+ if let handler = handler, !self.cachedEvents.isEmpty {
431
+ logger.info("📡 Emitting \(self.cachedEvents.count) cached events")
432
+ for (type, data) in self.cachedEvents {
455
433
  logger.debug("📡 Emitting cached event: \(type)")
456
434
  handler(type, data)
457
435
  }
458
- cachedEvents.removeAll()
436
+ self.cachedEvents.removeAll()
459
437
  logger.info("📡 ✅ All cached events emitted and cleared")
460
438
  }
461
439
  }
@@ -468,43 +446,41 @@ import OSLog
468
446
  let jsonData = try JSONSerialization.data(withJSONObject: data, options: [])
469
447
  let dataString = String(data: jsonData, encoding: .utf8) ?? "{}"
470
448
 
471
- if let eventHandler = eventHandler {
449
+ if let eventHandler = self.eventHandler {
472
450
  logger.debug("📡 Calling event handler with data: \(dataString)")
473
451
  eventHandler(type, dataString)
474
452
  } else {
475
453
  logger.info("📡 No event handler, caching event: \(type)")
476
- cachedEvents.append((type, dataString))
454
+ self.cachedEvents.append((type, dataString))
477
455
  }
478
456
  } catch {
479
457
  logger.error("📡 ❌ Failed to serialize event data: \(error.localizedDescription)")
480
458
  }
481
459
  }
482
460
 
483
- // MARK: - Internal Call End Listeners (for UI cleanup)
484
461
  internal func registerCallEndListener(_ listener: @escaping (String) -> Void) {
485
- callEndListeners.append(listener)
486
- logger.info("🔗 Internal call end listener registered. Total: \(callEndListeners.count)")
462
+ self.callEndListeners.append(listener)
463
+ logger.info("🔗 Internal call end listener registered. Total: \(self.callEndListeners.count)")
487
464
  }
488
465
 
489
466
  internal func unregisterCallEndListener() {
490
- callEndListeners.removeAll()
467
+ self.callEndListeners.removeAll()
491
468
  logger.info("🔗 All internal call end listeners unregistered")
492
469
  }
493
470
 
494
- // MARK: - Utility Methods
495
471
  public func getActiveCalls() -> [CallInfo] {
496
- let calls = Array(activeCalls.values)
472
+ let calls = Array(self.activeCalls.values)
497
473
  logger.debug("📊 Getting active calls. Count: \(calls.count)")
498
474
  return calls
499
475
  }
500
476
 
501
477
  public func getCurrentCallId() -> String? {
502
- logger.debug("📊 Current call ID: \(currentCallId ?? "nil")")
503
- return currentCallId
478
+ logger.debug("📊 Current call ID: \(self.currentCallId ?? "nil")")
479
+ return self.currentCallId
504
480
  }
505
481
 
506
482
  public func isCallActive() -> Bool {
507
- let hasActiveCalls = activeCalls.values.contains { call in
483
+ let hasActiveCalls = self.activeCalls.values.contains { call in
508
484
  call.state == .active || call.state == .incoming || call.state == .dialing || call.state == .held
509
485
  }
510
486
  logger.debug("📊 Is call active: \(hasActiveCalls)")
@@ -512,7 +488,7 @@ import OSLog
512
488
  }
513
489
 
514
490
  private func validateOutgoingCallRequest() -> Bool {
515
- let hasConflictingCalls = activeCalls.values.contains { call in
491
+ let hasConflictingCalls = self.activeCalls.values.contains { call in
516
492
  call.state == .incoming || call.state == .active
517
493
  }
518
494
  let isValid = !hasConflictingCalls
@@ -522,7 +498,7 @@ import OSLog
522
498
 
523
499
  private func rejectIncomingCallCollision(callId: String, reason: String) {
524
500
  logger.warning("📞 ⚠️ Rejecting call collision: \(callId), reason: \(reason)")
525
- callMetadata.removeValue(forKey: callId)
501
+ self.callMetadata.removeValue(forKey: callId)
526
502
  emitEvent(.callRejected, data: [
527
503
  "callId": callId,
528
504
  "reason": reason
@@ -532,12 +508,12 @@ import OSLog
532
508
  private func emitCallAnsweredWithMetadata(callId: String) {
533
509
  logger.info("📡 Emitting call answered event with metadata: \(callId)")
534
510
 
535
- guard let callInfo = activeCalls[callId] else {
511
+ guard let callInfo = self.activeCalls[callId] else {
536
512
  logger.warning("📡 ⚠️ Cannot emit call answered - call not found: \(callId)")
537
513
  return
538
514
  }
539
515
 
540
- let metadata = callMetadata[callId]
516
+ let metadata = self.callMetadata[callId]
541
517
 
542
518
  var eventData: [String: Any] = [
543
519
  "callId": callId,
@@ -560,12 +536,12 @@ import OSLog
560
536
  private func emitOutgoingCallAnsweredWithMetadata(callId: String) {
561
537
  logger.info("📡 Emitting outgoing call answered event with metadata: \(callId)")
562
538
 
563
- guard let callInfo = activeCalls[callId] else {
539
+ guard let callInfo = self.activeCalls[callId] else {
564
540
  logger.warning("📡 ⚠️ Cannot emit outgoing call answered - call not found: \(callId)")
565
541
  return
566
542
  }
567
543
 
568
- let metadata = callMetadata[callId]
544
+ let metadata = self.callMetadata[callId]
569
545
 
570
546
  var eventData: [String: Any] = [
571
547
  "callId": callId,
@@ -586,7 +562,6 @@ import OSLog
586
562
  }
587
563
  }
588
564
 
589
- // MARK: - CallKitManagerDelegate
590
565
  extension CallEngine: CallKitManagerDelegate {
591
566
  func callKitManager(_ manager: CallKitManager, didAnswerCall callId: String) {
592
567
  logger.info("📲 CallKit delegate: answer call \(callId)")
@@ -613,7 +588,6 @@ extension CallEngine: CallKitManagerDelegate {
613
588
  }
614
589
  }
615
590
 
616
- // MARK: - AudioManagerDelegate
617
591
  extension CallEngine: AudioManagerDelegate {
618
592
  func audioManager(_ manager: AudioManager, didChangeRoute routeInfo: AudioRoutesInfo) {
619
593
  logger.info("🔊 Audio manager delegate: route changed to \(routeInfo.currentRoute)")
@@ -23,13 +23,13 @@ public struct CallInfo {
23
23
  self.wasHeldBySystem = false
24
24
  self.isManuallySilenced = false
25
25
 
26
- Self.logger.info("📱 CallInfo created: callId=\(callId), type=\(callType), name=\(displayName), state=\(state.rawValue)")
26
+ Self.logger.info("📱 CallInfo created: callId=\(callId), type=\(callType), name=\(displayName), state=\(state.stringValue)")
27
27
  }
28
28
 
29
29
  mutating func updateState(_ newState: CallState) {
30
30
  let oldState = self.state
31
31
  self.state = newState
32
- Self.logger.info("📱 CallInfo state changed: callId=\(callId), \(oldState.rawValue) → \(newState.rawValue)")
32
+ Self.logger.info("📱 CallInfo state changed: callId=\(callId), \(oldState.stringValue) → \(newState.stringValue)")
33
33
  }
34
34
 
35
35
  mutating func updateDisplayName(_ newName: String) {
@@ -43,7 +43,7 @@ public struct CallInfo {
43
43
  "callId": callId,
44
44
  "callType": callType,
45
45
  "displayName": displayName,
46
- "state": state.rawValue,
46
+ "state": state.stringValue,
47
47
  "timestamp": timestamp,
48
48
  "wasHeldBySystem": wasHeldBySystem
49
49
  ]
@@ -63,4 +63,8 @@ public enum CallState: String, CaseIterable {
63
63
  case active = "ACTIVE"
64
64
  case held = "HELD"
65
65
  case ended = "ENDED"
66
+
67
+ var stringValue: String {
68
+ return self.rawValue
69
+ }
66
70
  }
@@ -9,7 +9,7 @@ protocol CallKitManagerDelegate: AnyObject {
9
9
  func callKitManager(_ manager: CallKitManager, didSetMuted callId: String, muted: Bool)
10
10
  }
11
11
 
12
- class CallKitManager: NSObject {
12
+ class CallKitManager {
13
13
  private let logger = Logger(subsystem: "com.qusaieilouti99.callmanager", category: "CallKitManager")
14
14
  private let provider: CXProvider
15
15
  private let callController = CXCallController()
@@ -30,7 +30,6 @@ class CallKitManager: NSObject {
30
30
  configuration.supportedHandleTypes = [.phoneNumber, .generic]
31
31
  configuration.includesCallsInRecents = true
32
32
 
33
- // Audio configuration
34
33
  if let ringtonePath = Bundle.main.path(forResource: "ringtone", ofType: "caf") {
35
34
  configuration.ringtoneSoundURL = URL(fileURLWithPath: ringtonePath)
36
35
  logger.info("📲 Custom ringtone configured")
@@ -39,14 +38,10 @@ class CallKitManager: NSObject {
39
38
  }
40
39
 
41
40
  provider = CXProvider(configuration: configuration)
42
-
43
- super.init()
44
-
45
- provider.setDelegate(self, queue: nil)
41
+ provider.setDelegate(CallKitProviderDelegate(manager: self), queue: nil)
46
42
  logger.info("📲 ✅ CallKitManager initialized successfully")
47
43
  }
48
44
 
49
- // MARK: - Public Methods
50
45
  func reportIncomingCall(callInfo: CallInfo, completion: @escaping (Error?) -> Void) {
51
46
  logger.info("📲 Reporting incoming call: \(callInfo.callId)")
52
47
 
@@ -68,15 +63,16 @@ class CallKitManager: NSObject {
68
63
 
69
64
  logger.info("📲 Call update configured: name=\(callInfo.displayName), hasVideo=\(update.hasVideo)")
70
65
 
71
- activeCallIds.insert(callInfo.callId)
72
- logger.info("📲 Added to active calls. Total active: \(activeCallIds.count)")
66
+ self.activeCallIds.insert(callInfo.callId)
67
+ logger.info("📲 Added to active calls. Total active: \(self.activeCallIds.count)")
73
68
 
74
69
  provider.reportNewIncomingCall(with: callUUID, update: update) { [weak self] error in
70
+ guard let self = self else { return }
75
71
  if let error = error {
76
- self?.logger.error("📲 ❌ Failed to report incoming call: \(error.localizedDescription)")
77
- self?.activeCallIds.remove(callInfo.callId)
72
+ self.logger.error("📲 ❌ Failed to report incoming call: \(error.localizedDescription)")
73
+ self.activeCallIds.remove(callInfo.callId)
78
74
  } else {
79
- self?.logger.info("📲 ✅ Successfully reported incoming call: \(callInfo.callId)")
75
+ self.logger.info("📲 ✅ Successfully reported incoming call: \(callInfo.callId)")
80
76
  }
81
77
  completion(error)
82
78
  }
@@ -100,15 +96,16 @@ class CallKitManager: NSObject {
100
96
 
101
97
  let transaction = CXTransaction(action: startCallAction)
102
98
 
103
- activeCallIds.insert(callInfo.callId)
104
- logger.info("📲 Added to active calls. Total active: \(activeCallIds.count)")
99
+ self.activeCallIds.insert(callInfo.callId)
100
+ logger.info("📲 Added to active calls. Total active: \(self.activeCallIds.count)")
105
101
 
106
102
  callController.request(transaction) { [weak self] error in
103
+ guard let self = self else { return }
107
104
  if let error = error {
108
- self?.logger.error("📲 ❌ Failed to start outgoing call: \(error.localizedDescription)")
109
- self?.activeCallIds.remove(callInfo.callId)
105
+ self.logger.error("📲 ❌ Failed to start outgoing call: \(error.localizedDescription)")
106
+ self.activeCallIds.remove(callInfo.callId)
110
107
  } else {
111
- self?.logger.info("📲 ✅ Successfully started outgoing call: \(callInfo.callId)")
108
+ self.logger.info("📲 ✅ Successfully started outgoing call: \(callInfo.callId)")
112
109
  }
113
110
  completion(error)
114
111
  }
@@ -118,7 +115,7 @@ class CallKitManager: NSObject {
118
115
  logger.info("📲 Ending call: \(callId)")
119
116
 
120
117
  guard let callUUID = UUID(uuidString: callId),
121
- activeCallIds.contains(callId) else {
118
+ self.activeCallIds.contains(callId) else {
122
119
  logger.warning("📲 ⚠️ Cannot end call \(callId) - not found or invalid UUID")
123
120
  return
124
121
  }
@@ -127,11 +124,12 @@ class CallKitManager: NSObject {
127
124
  let transaction = CXTransaction(action: endCallAction)
128
125
 
129
126
  callController.request(transaction) { [weak self] error in
127
+ guard let self = self else { return }
130
128
  if let error = error {
131
- self?.logger.error("📲 ❌ Failed to end call: \(error.localizedDescription)")
129
+ self.logger.error("📲 ❌ Failed to end call: \(error.localizedDescription)")
132
130
  } else {
133
- self?.logger.info("📲 ✅ Successfully ended call: \(callId)")
134
- self?.activeCallIds.remove(callId)
131
+ self.logger.info("📲 ✅ Successfully ended call: \(callId)")
132
+ self.activeCallIds.remove(callId)
135
133
  }
136
134
  }
137
135
  }
@@ -140,7 +138,7 @@ class CallKitManager: NSObject {
140
138
  logger.info("📲 Setting call \(callId) hold state to: \(onHold)")
141
139
 
142
140
  guard let callUUID = UUID(uuidString: callId),
143
- activeCallIds.contains(callId) else {
141
+ self.activeCallIds.contains(callId) else {
144
142
  logger.warning("📲 ⚠️ Cannot set hold for call \(callId) - not found or invalid UUID")
145
143
  return
146
144
  }
@@ -149,10 +147,11 @@ class CallKitManager: NSObject {
149
147
  let transaction = CXTransaction(action: holdAction)
150
148
 
151
149
  callController.request(transaction) { [weak self] error in
150
+ guard let self = self else { return }
152
151
  if let error = error {
153
- self?.logger.error("📲 ❌ Failed to set call on hold: \(error.localizedDescription)")
152
+ self.logger.error("📲 ❌ Failed to set call on hold: \(error.localizedDescription)")
154
153
  } else {
155
- self?.logger.info("📲 ✅ Successfully set call \(callId) hold state to: \(onHold)")
154
+ self.logger.info("📲 ✅ Successfully set call \(callId) hold state to: \(onHold)")
156
155
  }
157
156
  }
158
157
  }
@@ -161,7 +160,7 @@ class CallKitManager: NSObject {
161
160
  logger.info("📲 Updating call \(callId) display name to: \(displayName)")
162
161
 
163
162
  guard let callUUID = UUID(uuidString: callId),
164
- activeCallIds.contains(callId) else {
163
+ self.activeCallIds.contains(callId) else {
165
164
  logger.warning("📲 ⚠️ Cannot update call \(callId) - not found or invalid UUID")
166
165
  return
167
166
  }
@@ -173,66 +172,97 @@ class CallKitManager: NSObject {
173
172
  provider.reportCall(with: callUUID, updated: update)
174
173
  logger.info("📲 ✅ Updated call \(callId) display name to: \(displayName)")
175
174
  }
175
+
176
+ fileprivate func handleProviderReset() {
177
+ logger.info("📲 🔄 Provider did reset - clearing all active calls")
178
+ self.activeCallIds.removeAll()
179
+ }
180
+
181
+ fileprivate func handleAnswerCall(callId: String) {
182
+ logger.info("📲 📞 Provider perform answer call action")
183
+ logger.info("📲 Answering call: \(callId)")
184
+ self.delegate?.callKitManager(self, didAnswerCall: callId)
185
+ }
186
+
187
+ fileprivate func handleEndCall(callId: String) {
188
+ logger.info("📲 📞 Provider perform end call action")
189
+ logger.info("📲 Ending call: \(callId)")
190
+ self.activeCallIds.remove(callId)
191
+ self.delegate?.callKitManager(self, didEndCall: callId)
192
+ }
193
+
194
+ fileprivate func handleSetHeld(callId: String, onHold: Bool) {
195
+ logger.info("📲 📞 Provider perform set held call action: \(onHold)")
196
+ logger.info("📲 Setting call \(callId) hold state to: \(onHold)")
197
+ self.delegate?.callKitManager(self, didSetHeld: callId, onHold: onHold)
198
+ }
199
+
200
+ fileprivate func handleSetMuted(callId: String, muted: Bool) {
201
+ logger.info("📲 📞 Provider perform set muted call action: \(muted)")
202
+ logger.info("📲 Setting call \(callId) mute state to: \(muted)")
203
+ self.delegate?.callKitManager(self, didSetMuted: callId, muted: muted)
204
+ }
205
+
206
+ fileprivate func handleStartCall() {
207
+ logger.info("📲 📞 Provider perform start call action")
208
+ logger.info("📲 ✅ Start call action fulfilled")
209
+ }
210
+
211
+ fileprivate func handleAudioSessionActivated() {
212
+ logger.info("📲 🔊 Provider did activate audio session")
213
+ }
214
+
215
+ fileprivate func handleAudioSessionDeactivated() {
216
+ logger.info("📲 🔊 Provider did deactivate audio session")
217
+ }
176
218
  }
177
219
 
178
- // MARK: - CXProviderDelegate
179
- extension CallKitManager: CXProviderDelegate {
220
+ private class CallKitProviderDelegate: NSObject, CXProviderDelegate {
221
+ private weak var manager: CallKitManager?
222
+
223
+ init(manager: CallKitManager) {
224
+ self.manager = manager
225
+ super.init()
226
+ }
227
+
180
228
  func providerDidReset(_ provider: CXProvider) {
181
- logger.info("📲 🔄 Provider did reset - clearing all active calls")
182
- activeCallIds.removeAll()
229
+ manager?.handleProviderReset()
183
230
  }
184
231
 
185
232
  func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
186
- logger.info("📲 📞 Provider perform answer call action")
187
233
  let callId = action.callUUID.uuidString
188
- logger.info("📲 Answering call: \(callId)")
189
- delegate?.callKitManager(self, didAnswerCall: callId)
234
+ manager?.handleAnswerCall(callId: callId)
190
235
  action.fulfill()
191
- logger.info("📲 ✅ Answer action fulfilled")
192
236
  }
193
237
 
194
238
  func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
195
- logger.info("📲 📞 Provider perform end call action")
196
239
  let callId = action.callUUID.uuidString
197
- logger.info("📲 Ending call: \(callId)")
198
- activeCallIds.remove(callId)
199
- delegate?.callKitManager(self, didEndCall: callId)
240
+ manager?.handleEndCall(callId: callId)
200
241
  action.fulfill()
201
- logger.info("📲 ✅ End call action fulfilled")
202
242
  }
203
243
 
204
244
  func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
205
- logger.info("📲 📞 Provider perform set held call action: \(action.isOnHold)")
206
245
  let callId = action.callUUID.uuidString
207
- logger.info("📲 Setting call \(callId) hold state to: \(action.isOnHold)")
208
- delegate?.callKitManager(self, didSetHeld: callId, onHold: action.isOnHold)
246
+ manager?.handleSetHeld(callId: callId, onHold: action.isOnHold)
209
247
  action.fulfill()
210
- logger.info("📲 ✅ Set held action fulfilled")
211
248
  }
212
249
 
213
250
  func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
214
- logger.info("📲 📞 Provider perform set muted call action: \(action.isMuted)")
215
251
  let callId = action.callUUID.uuidString
216
- logger.info("📲 Setting call \(callId) mute state to: \(action.isMuted)")
217
- delegate?.callKitManager(self, didSetMuted: callId, muted: action.isMuted)
252
+ manager?.handleSetMuted(callId: callId, muted: action.isMuted)
218
253
  action.fulfill()
219
- logger.info("📲 ✅ Set muted action fulfilled")
220
254
  }
221
255
 
222
256
  func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
223
- logger.info("📲 📞 Provider perform start call action")
224
- logger.info("📲 Starting outgoing call with handle: \(action.handle.value)")
257
+ manager?.handleStartCall()
225
258
  action.fulfill()
226
- logger.info("📲 ✅ Start call action fulfilled")
227
259
  }
228
260
 
229
261
  func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
230
- logger.info("📲 🔊 Provider did activate audio session")
231
- logger.info("📲 Audio session category: \(audioSession.category.rawValue)")
232
- logger.info("📲 Audio session mode: \(audioSession.mode.rawValue)")
262
+ manager?.handleAudioSessionActivated()
233
263
  }
234
264
 
235
265
  func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
236
- logger.info("📲 🔊 Provider did deactivate audio session")
266
+ manager?.handleAudioSessionDeactivated()
237
267
  }
238
268
  }
@@ -17,7 +17,6 @@ public class CallManager: HybridCallManagerSpec {
17
17
  CallEngine.shared.initialize()
18
18
  }
19
19
 
20
- // MARK: - HybridCallManagerSpec Implementation
21
20
  public func endCall(callId: String) throws {
22
21
  logger.info("🎯📞 endCall requested for callId: \(callId)")
23
22
  ensureInitialized()
@@ -35,7 +34,6 @@ public class CallManager: HybridCallManagerSpec {
35
34
  public func silenceRingtone() throws {
36
35
  logger.info("🎯🔇 silenceRingtone requested")
37
36
  ensureInitialized()
38
- // Ringtone silencing is handled by CallKit automatically in iOS
39
37
  logger.info("🎯🔇 ✅ silenceRingtone completed (handled by CallKit)")
40
38
  }
41
39
 
@@ -57,7 +55,6 @@ public class CallManager: HybridCallManagerSpec {
57
55
  public func keepScreenAwake(keepAwake: Bool) throws {
58
56
  logger.info("🎯💡 keepScreenAwake requested: \(keepAwake)")
59
57
  ensureInitialized()
60
- // Screen wake management is handled by CallKit automatically
61
58
  logger.info("🎯💡 ✅ keepScreenAwake completed (handled by CallKit)")
62
59
  }
63
60
 
@@ -65,17 +62,17 @@ public class CallManager: HybridCallManagerSpec {
65
62
  logger.info("🎯📡 addListener called")
66
63
  ensureInitialized()
67
64
 
68
- CallEngine.shared.setEventHandler { eventType, payload in
69
- self.logger.debug("🎯📡 Event emitted: \(eventType), payload length: \(payload.count)")
65
+ CallEngine.shared.setEventHandler { [weak self] eventType, payload in
66
+ self?.logger.debug("🎯📡 Event emitted: \(eventType), payload length: \(payload.count)")
70
67
  listener(eventType, payload)
71
68
  }
72
69
 
73
70
  logger.info("🎯📡 ✅ Event handler registered")
74
71
 
75
- return {
76
- self.logger.info("🎯📡 Removing event handler...")
72
+ return { [weak self] in
73
+ self?.logger.info("🎯📡 Removing event handler...")
77
74
  CallEngine.shared.setEventHandler(nil)
78
- self.logger.info("🎯📡 ✅ Event handler removed")
75
+ self?.logger.info("🎯📡 ✅ Event handler removed")
79
76
  }
80
77
  }
81
78
 
@@ -131,17 +128,17 @@ public class CallManager: HybridCallManagerSpec {
131
128
  logger.info("🎯🔑 registerVoIPTokenListener called")
132
129
  ensureInitialized()
133
130
 
134
- VoIPTokenManager.shared.registerTokenListener { token in
135
- self.logger.info("🎯🔑 VoIP token received, length: \(token.count)")
131
+ VoIPTokenManager.shared.registerTokenListener { [weak self] token in
132
+ self?.logger.info("🎯🔑 VoIP token received, length: \(token.count)")
136
133
  listener(token)
137
134
  }
138
135
 
139
136
  logger.info("🎯🔑 ✅ VoIP token listener registered")
140
137
 
141
- return {
142
- self.logger.info("🎯🔑 Removing VoIP token listener...")
138
+ return { [weak self] in
139
+ self?.logger.info("🎯🔑 Removing VoIP token listener...")
143
140
  VoIPTokenManager.shared.unregisterTokenListener()
144
- self.logger.info("🎯🔑 ✅ VoIP token listener removed")
141
+ self?.logger.info("🎯🔑 ✅ VoIP token listener removed")
145
142
  }
146
143
  }
147
144
  }
@@ -2,7 +2,7 @@ import Foundation
2
2
  import PushKit
3
3
  import OSLog
4
4
 
5
- class VoIPTokenManager: NSObject {
5
+ class VoIPTokenManager {
6
6
  static let shared = VoIPTokenManager()
7
7
 
8
8
  private let logger = Logger(subsystem: "com.qusaieilouti99.callmanager", category: "VoIPTokenManager")
@@ -10,8 +10,7 @@ class VoIPTokenManager: NSObject {
10
10
  private var tokenListener: ((String) -> Void)?
11
11
  private var cachedToken: String?
12
12
 
13
- private override init() {
14
- super.init()
13
+ private init() {
15
14
  logger.info("🔑 VoIPTokenManager initializing...")
16
15
  setupPushKit()
17
16
  }
@@ -19,17 +18,16 @@ class VoIPTokenManager: NSObject {
19
18
  private func setupPushKit() {
20
19
  logger.info("🔑 Setting up PushKit registry...")
21
20
  pushRegistry = PKPushRegistry(queue: DispatchQueue.main)
22
- pushRegistry?.delegate = self
21
+ pushRegistry?.delegate = VoIPPushDelegate(manager: self)
23
22
  pushRegistry?.desiredPushTypes = [.voIP]
24
23
  logger.info("🔑 PushKit registry setup completed successfully")
25
24
  }
26
25
 
27
26
  func registerTokenListener(_ listener: @escaping (String) -> Void) {
28
27
  logger.info("🔑 Registering VoIP token listener...")
29
- tokenListener = listener
28
+ self.tokenListener = listener
30
29
 
31
- // If we have a cached token, immediately return it
32
- if let cachedToken = cachedToken {
30
+ if let cachedToken = self.cachedToken {
33
31
  logger.info("🔑 Returning cached VoIP token: \(cachedToken.prefix(10))...")
34
32
  listener(cachedToken)
35
33
  } else {
@@ -39,7 +37,54 @@ class VoIPTokenManager: NSObject {
39
37
 
40
38
  func unregisterTokenListener() {
41
39
  logger.info("🔑 Unregistering VoIP token listener")
42
- tokenListener = nil
40
+ self.tokenListener = nil
41
+ }
42
+
43
+ fileprivate func handleTokenUpdate(deviceToken: String) {
44
+ logger.info("🔑 ✅ VoIP Device Token received: \(deviceToken.prefix(10))...\(deviceToken.suffix(10))")
45
+
46
+ self.cachedToken = deviceToken
47
+ logger.info("🔑 Token cached successfully")
48
+
49
+ if let tokenListener = self.tokenListener {
50
+ logger.info("🔑 Calling token listener with new token")
51
+ tokenListener(deviceToken)
52
+ } else {
53
+ logger.info("🔑 No token listener registered, token will be returned when listener is added")
54
+ }
55
+ }
56
+
57
+ fileprivate func handleIncomingPush(payload: [String: Any]) {
58
+ logger.info("🔔 Received VoIP push notification")
59
+ logger.debug("🔔 Full payload: \(payload)")
60
+
61
+ if let notificationType = payload["type"] as? String {
62
+ logger.info("🔔 Notification type: \(notificationType)")
63
+ switch notificationType {
64
+ case "Call":
65
+ logger.info("🔔 Processing incoming call notification...")
66
+ handleIncomingCall(payload: payload)
67
+ case "EndCall":
68
+ logger.info("🔔 Processing end call notification...")
69
+ handleEndCall(payload: payload)
70
+ default:
71
+ logger.warning("🔔 ⚠️ Unknown VoIP notification type: \(notificationType)")
72
+ }
73
+ } else {
74
+ logger.info("🔔 No type specified, assuming incoming call...")
75
+ handleIncomingCall(payload: payload)
76
+ }
77
+
78
+ logger.info("🔔 ✅ VoIP push notification processing completed")
79
+ }
80
+
81
+ fileprivate func handleTokenInvalidation() {
82
+ logger.warning("🔑 ⚠️ VoIP push token invalidated")
83
+ self.cachedToken = nil
84
+ if let tokenListener = self.tokenListener {
85
+ logger.info("🔑 Notifying listener about token invalidation")
86
+ tokenListener("")
87
+ }
43
88
  }
44
89
 
45
90
  private func handleIncomingCall(payload: [String: Any]) {
@@ -98,61 +143,25 @@ class VoIPTokenManager: NSObject {
98
143
  }
99
144
  }
100
145
 
101
- // MARK: - PKPushRegistryDelegate
102
- extension VoIPTokenManager: PKPushRegistryDelegate {
103
- func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
104
- logger.info("🔑 VoIP push credentials updated for type: \(type)")
146
+ private class VoIPPushDelegate: NSObject, PKPushRegistryDelegate {
147
+ private weak var manager: VoIPTokenManager?
105
148
 
106
- let deviceToken = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()
107
- logger.info("🔑 VoIP Device Token received: \(deviceToken.prefix(10))...\(deviceToken.suffix(10))")
108
-
109
- // Cache the token
110
- cachedToken = deviceToken
111
- logger.info("🔑 Token cached successfully")
149
+ init(manager: VoIPTokenManager) {
150
+ self.manager = manager
151
+ super.init()
152
+ }
112
153
 
113
- // Notify the listener about the new token
114
- if let tokenListener = tokenListener {
115
- logger.info("🔑 Calling token listener with new token")
116
- tokenListener(deviceToken)
117
- } else {
118
- logger.info("🔑 No token listener registered, token will be returned when listener is added")
119
- }
154
+ func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
155
+ let deviceToken = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()
156
+ manager?.handleTokenUpdate(deviceToken: deviceToken)
120
157
  }
121
158
 
122
159
  func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
123
- logger.info("🔔 Received VoIP push notification")
124
- logger.debug("🔔 Full payload: \(payload.dictionaryPayload)")
125
-
126
- let payloadDict = payload.dictionaryPayload
127
-
128
- // Handle different notification types
129
- if let notificationType = payloadDict["type"] as? String {
130
- logger.info("🔔 Notification type: \(notificationType)")
131
- switch notificationType {
132
- case "Call":
133
- logger.info("🔔 Processing incoming call notification...")
134
- handleIncomingCall(payload: payloadDict)
135
- case "EndCall":
136
- logger.info("🔔 Processing end call notification...")
137
- handleEndCall(payload: payloadDict)
138
- default:
139
- logger.warning("🔔 ⚠️ Unknown VoIP notification type: \(notificationType)")
140
- }
141
- } else {
142
- logger.info("🔔 No type specified, assuming incoming call...")
143
- handleIncomingCall(payload: payloadDict)
144
- }
145
-
146
- logger.info("🔔 ✅ VoIP push notification processing completed")
160
+ manager?.handleIncomingPush(payload: payload.dictionaryPayload)
147
161
  completion()
148
162
  }
149
163
 
150
164
  func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
151
- logger.warning("🔑 ⚠️ VoIP push token invalidated for type: \(type)")
152
- cachedToken = nil
153
- if let tokenListener = tokenListener {
154
- logger.info("🔑 Notifying listener about token invalidation")
155
- tokenListener("")
156
- }
165
+ manager?.handleTokenInvalidation()
157
166
  }
158
167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusaieilouti99/call-manager",
3
- "version": "0.1.79",
3
+ "version": "0.1.80",
4
4
  "description": "Call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",