@stream-io/react-native-callingx 0.1.0-beta.4 → 0.1.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/android/build.gradle +9 -0
- package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +69 -45
- package/android/src/main/java/io/getstream/rn/callingx/CallingxEventEmitterAdapter.kt +7 -0
- package/android/src/main/java/io/getstream/rn/callingx/{CallingxModule.kt → CallingxModuleImpl.kt} +194 -103
- package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +9 -9
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt +8 -8
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverActivity.kt +7 -7
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverService.kt +7 -7
- package/android/src/main/java/io/getstream/rn/callingx/repo/TelecomCallRepository.kt +3 -0
- package/android/src/newarch/java/io/getstream/rn/callingx/CallingxModule.kt +148 -0
- package/android/src/oldarch/java/io/getstream/rn/callingx/CallingxModule.kt +177 -0
- package/android/src/oldarch/java/io/getstream/rn/callingx/CallingxPackage.kt +16 -0
- package/dist/module/CallingxModule.js +2 -2
- package/dist/module/CallingxModule.js.map +1 -1
- package/dist/module/EventManager.js +11 -4
- package/dist/module/EventManager.js.map +1 -1
- package/dist/module/spec/NativeCallingx.js +4 -2
- package/dist/module/spec/NativeCallingx.js.map +1 -1
- package/dist/module/utils/utils.js +3 -0
- package/dist/module/utils/utils.js.map +1 -1
- package/dist/typescript/src/CallingxModule.d.ts +1 -1
- package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
- package/dist/typescript/src/EventManager.d.ts.map +1 -1
- package/dist/typescript/src/spec/NativeCallingx.d.ts +3 -3
- package/dist/typescript/src/spec/NativeCallingx.d.ts.map +1 -1
- package/dist/typescript/src/types.d.ts +2 -2
- package/dist/typescript/src/types.d.ts.map +1 -1
- package/dist/typescript/src/utils/utils.d.ts +1 -0
- package/dist/typescript/src/utils/utils.d.ts.map +1 -1
- package/ios/Callingx.mm +326 -22
- package/ios/CallingxCall.swift +105 -0
- package/ios/CallingxImpl.swift +136 -70
- package/ios/CallingxPublic.h +2 -5
- package/ios/UUIDStorage.swift +71 -26
- package/package.json +3 -3
- package/src/CallingxModule.ts +2 -2
- package/src/EventManager.ts +18 -5
- package/src/spec/NativeCallingx.ts +12 -3
- package/src/types.ts +2 -2
- package/src/utils/utils.ts +3 -0
- /package/android/src/{main → newarch}/java/io/getstream/rn/callingx/CallingxPackage.kt +0 -0
package/ios/CallingxImpl.swift
CHANGED
|
@@ -31,25 +31,23 @@ import stream_react_native_webrtc
|
|
|
31
31
|
@objc public static var sharedProvider: CXProvider?
|
|
32
32
|
@objc public static var uuidStorage: UUIDStorage?
|
|
33
33
|
@objc public static weak var sharedInstance: CallingxImpl?
|
|
34
|
+
/// Events stored before the module instance exists (e.g. VoIP from killed state). Drained in getInitialEvents().
|
|
35
|
+
private static var delayedEvents: [[String: Any]] = []
|
|
34
36
|
|
|
35
37
|
// MARK: - Instance Properties
|
|
36
38
|
@objc public var callKeepCallController: CXCallController?
|
|
37
39
|
@objc public var callKeepProvider: CXProvider?
|
|
38
40
|
@objc public weak var eventEmitter: CallingxEventEmitter?
|
|
41
|
+
@objc public weak var webRTCModule: WebRTCModule?
|
|
39
42
|
|
|
40
43
|
private var canSendEvents: Bool = false
|
|
41
44
|
private var isSetup: Bool = false
|
|
42
|
-
private var isSelfAnswered: Bool = false
|
|
43
|
-
private var isSelfEnded: Bool = false
|
|
44
|
-
private var isSelfMuted: Bool = false
|
|
45
|
-
private var delayedEvents: [[String: Any]] = []
|
|
46
45
|
|
|
47
46
|
// MARK: - Initialization
|
|
48
47
|
@objc public override init() {
|
|
49
48
|
super.init()
|
|
50
49
|
|
|
51
50
|
isSetup = false
|
|
52
|
-
delayedEvents = []
|
|
53
51
|
canSendEvents = false
|
|
54
52
|
|
|
55
53
|
NotificationCenter.default.addObserver(
|
|
@@ -74,7 +72,6 @@ import stream_react_native_webrtc
|
|
|
74
72
|
callKeepProvider?.invalidate()
|
|
75
73
|
CallingxImpl.sharedProvider = nil
|
|
76
74
|
canSendEvents = false
|
|
77
|
-
delayedEvents = []
|
|
78
75
|
isSetup = false
|
|
79
76
|
}
|
|
80
77
|
|
|
@@ -99,9 +96,10 @@ import stream_react_native_webrtc
|
|
|
99
96
|
supportsDTMF: Bool,
|
|
100
97
|
supportsGrouping: Bool,
|
|
101
98
|
supportsUngrouping: Bool,
|
|
102
|
-
fromPushKit: Bool,
|
|
103
99
|
payload: [String: Any]?,
|
|
104
|
-
completion: (() -> Void)
|
|
100
|
+
completion: (() -> Void)?,
|
|
101
|
+
resolve: RCTPromiseResolveBlock?,
|
|
102
|
+
reject: RCTPromiseRejectBlock?
|
|
105
103
|
) {
|
|
106
104
|
initializeIfNeeded()
|
|
107
105
|
|
|
@@ -112,6 +110,7 @@ import stream_react_native_webrtc
|
|
|
112
110
|
print("[Callingx][reportNewIncomingCall] callId already exists")
|
|
113
111
|
#endif
|
|
114
112
|
completion?()
|
|
113
|
+
resolve?(true)
|
|
115
114
|
return
|
|
116
115
|
}
|
|
117
116
|
|
|
@@ -144,16 +143,28 @@ import stream_react_native_webrtc
|
|
|
144
143
|
"supportsDTMF": supportsDTMF ? "1" : "0",
|
|
145
144
|
"supportsGrouping": supportsGrouping ? "1" : "0",
|
|
146
145
|
"supportsUngrouping": supportsUngrouping ? "1" : "0",
|
|
147
|
-
"fromPushKit": fromPushKit ? "1" : "0",
|
|
148
146
|
"payload": payload ?? ""
|
|
149
147
|
]
|
|
150
148
|
|
|
151
|
-
sharedInstance
|
|
149
|
+
if let instance = sharedInstance {
|
|
150
|
+
instance.sendEvent(CallingxEvents.didDisplayIncomingCall, body: body)
|
|
151
|
+
} else {
|
|
152
|
+
let dictionary: [String: Any] = [
|
|
153
|
+
"eventName": CallingxEvents.didDisplayIncomingCall,
|
|
154
|
+
"params": body
|
|
155
|
+
]
|
|
156
|
+
DispatchQueue.main.async {
|
|
157
|
+
CallingxImpl.delayedEvents.append(dictionary)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
152
160
|
|
|
153
161
|
if error == nil {
|
|
154
162
|
#if DEBUG
|
|
155
163
|
print("[Callingx][reportNewIncomingCall] success callId = \(callId)")
|
|
156
164
|
#endif
|
|
165
|
+
resolve?(true)
|
|
166
|
+
} else {
|
|
167
|
+
reject?("DISPLAY_INCOMING_CALL_ERROR", error?.localizedDescription, error)
|
|
157
168
|
}
|
|
158
169
|
|
|
159
170
|
completion?()
|
|
@@ -196,17 +207,19 @@ import stream_react_native_webrtc
|
|
|
196
207
|
print("[Callingx][endCall] callId = \(callId) reason = \(reason)")
|
|
197
208
|
#endif
|
|
198
209
|
|
|
199
|
-
guard let
|
|
210
|
+
guard let call = uuidStorage?.getCall(forCid: callId) else {
|
|
200
211
|
#if DEBUG
|
|
201
212
|
print("[Callingx][endCall] callId not found")
|
|
202
213
|
#endif
|
|
203
214
|
return
|
|
204
215
|
}
|
|
205
216
|
|
|
217
|
+
call.markEnded()
|
|
218
|
+
|
|
206
219
|
// CXCallEndedReason raw values: failed=1, remoteEnded=2, unanswered=3, answeredElsewhere=4, declinedElsewhere=5
|
|
207
220
|
let endedReason = CXCallEndedReason(rawValue: reason) ?? .failed
|
|
208
221
|
|
|
209
|
-
sharedProvider?.reportCall(with: uuid, endedAt: Date(), reason: endedReason)
|
|
222
|
+
sharedProvider?.reportCall(with: call.uuid, endedAt: call.endedAt ?? Date(), reason: endedReason)
|
|
210
223
|
uuidStorage?.removeCid(callId)
|
|
211
224
|
}
|
|
212
225
|
|
|
@@ -242,9 +255,13 @@ import stream_react_native_webrtc
|
|
|
242
255
|
print("[Callingx][requestTransaction] Error requesting transaction (\(transaction.actions)): (\(error))")
|
|
243
256
|
#endif
|
|
244
257
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
258
|
+
// Reset per-call action-source flags for all actions in the failed transaction
|
|
259
|
+
for action in transaction.actions {
|
|
260
|
+
if let callAction = action as? CXCallAction,
|
|
261
|
+
let call = CallingxImpl.uuidStorage?.getCallByUUID(callAction.callUUID) {
|
|
262
|
+
call.resetAllSelfFlags()
|
|
263
|
+
}
|
|
264
|
+
}
|
|
248
265
|
} else {
|
|
249
266
|
#if DEBUG
|
|
250
267
|
print("[Callingx][requestTransaction] Requested transaction successfully")
|
|
@@ -280,9 +297,9 @@ import stream_react_native_webrtc
|
|
|
280
297
|
if self.canSendEvents {
|
|
281
298
|
self.eventEmitter?.emitEvent(dictionary)
|
|
282
299
|
} else {
|
|
283
|
-
|
|
300
|
+
CallingxImpl.delayedEvents.append(dictionary)
|
|
284
301
|
#if DEBUG
|
|
285
|
-
print("[Callingx] delayedEvents: \(
|
|
302
|
+
print("[Callingx] delayedEvents: \(CallingxImpl.delayedEvents)")
|
|
286
303
|
#endif
|
|
287
304
|
}
|
|
288
305
|
}
|
|
@@ -329,11 +346,11 @@ import stream_react_native_webrtc
|
|
|
329
346
|
var events: [[String: Any]] = []
|
|
330
347
|
let action = {
|
|
331
348
|
#if DEBUG
|
|
332
|
-
print("[Callingx][getInitialEvents] delayedEvents = \(
|
|
349
|
+
print("[Callingx][getInitialEvents] delayedEvents = \(CallingxImpl.delayedEvents)")
|
|
333
350
|
#endif
|
|
334
351
|
|
|
335
|
-
events =
|
|
336
|
-
|
|
352
|
+
events = CallingxImpl.delayedEvents
|
|
353
|
+
CallingxImpl.delayedEvents = []
|
|
337
354
|
self.canSendEvents = true
|
|
338
355
|
}
|
|
339
356
|
|
|
@@ -354,15 +371,25 @@ import stream_react_native_webrtc
|
|
|
354
371
|
print("[Callingx][answerIncomingCall] callId = \(callId)")
|
|
355
372
|
#endif
|
|
356
373
|
|
|
357
|
-
guard let
|
|
374
|
+
guard let call = CallingxImpl.uuidStorage?.getCall(forCid: callId) else {
|
|
358
375
|
#if DEBUG
|
|
359
376
|
print("[Callingx][answerIncomingCall] callId not found")
|
|
360
377
|
#endif
|
|
361
378
|
return false
|
|
362
379
|
}
|
|
363
380
|
|
|
364
|
-
|
|
365
|
-
|
|
381
|
+
// Guard: already answered or ended — prevent duplicate CXAnswerCallAction transactions
|
|
382
|
+
if call.isAnswered || call.hasEnded {
|
|
383
|
+
#if DEBUG
|
|
384
|
+
print("[Callingx][answerIncomingCall] callId already answered/ended, skipping")
|
|
385
|
+
#endif
|
|
386
|
+
return true
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
call.markSelfAnswered()
|
|
390
|
+
call.markStartedConnecting() // internal state: incoming call is now connecting
|
|
391
|
+
|
|
392
|
+
let answerCallAction = CXAnswerCallAction(call: call.uuid)
|
|
366
393
|
let transaction = CXTransaction()
|
|
367
394
|
transaction.addAction(answerCallAction)
|
|
368
395
|
|
|
@@ -374,8 +401,10 @@ import stream_react_native_webrtc
|
|
|
374
401
|
callId: String,
|
|
375
402
|
phoneNumber: String,
|
|
376
403
|
callerName: String,
|
|
377
|
-
hasVideo: Bool
|
|
378
|
-
|
|
404
|
+
hasVideo: Bool,
|
|
405
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
406
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
407
|
+
) {
|
|
379
408
|
let uuid = CallingxImpl.uuidStorage?.getUUID(forCid: callId)
|
|
380
409
|
CallingxImpl.reportNewIncomingCall(
|
|
381
410
|
callId: callId,
|
|
@@ -387,9 +416,10 @@ import stream_react_native_webrtc
|
|
|
387
416
|
supportsDTMF: false,
|
|
388
417
|
supportsGrouping: false,
|
|
389
418
|
supportsUngrouping: false,
|
|
390
|
-
fromPushKit: false,
|
|
391
419
|
payload: nil,
|
|
392
|
-
completion: nil
|
|
420
|
+
completion: nil,
|
|
421
|
+
resolve: resolve,
|
|
422
|
+
reject: reject
|
|
393
423
|
)
|
|
394
424
|
|
|
395
425
|
let wasAlreadyAnswered = uuid != nil
|
|
@@ -406,7 +436,6 @@ import stream_react_native_webrtc
|
|
|
406
436
|
}
|
|
407
437
|
}
|
|
408
438
|
}
|
|
409
|
-
return true
|
|
410
439
|
}
|
|
411
440
|
|
|
412
441
|
@objc public func endCall(_ callId: String) -> Bool {
|
|
@@ -414,25 +443,35 @@ import stream_react_native_webrtc
|
|
|
414
443
|
print("[Callingx][endCall] callId = \(callId)")
|
|
415
444
|
#endif
|
|
416
445
|
|
|
417
|
-
guard let
|
|
446
|
+
guard let call = CallingxImpl.uuidStorage?.getCall(forCid: callId) else {
|
|
418
447
|
#if DEBUG
|
|
419
448
|
print("[Callingx][endCall] callId not found")
|
|
420
449
|
#endif
|
|
421
450
|
return false
|
|
422
451
|
}
|
|
423
452
|
|
|
424
|
-
|
|
425
|
-
|
|
453
|
+
// Guard: already ended — prevent duplicate CXEndCallAction transactions
|
|
454
|
+
if call.hasEnded {
|
|
455
|
+
#if DEBUG
|
|
456
|
+
print("[Callingx][endCall] callId already ended, skipping")
|
|
457
|
+
#endif
|
|
458
|
+
return true
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
call.markSelfEnded()
|
|
462
|
+
call.markEnded()
|
|
463
|
+
|
|
464
|
+
let endCallAction = CXEndCallAction(call: call.uuid)
|
|
426
465
|
let transaction = CXTransaction(action: endCallAction)
|
|
427
466
|
|
|
428
467
|
requestTransaction(transaction)
|
|
429
468
|
return true
|
|
430
469
|
}
|
|
431
470
|
|
|
432
|
-
@objc public func
|
|
471
|
+
@objc public func isCallTracked(_ callId: String) -> Bool {
|
|
433
472
|
guard let uuid = CallingxImpl.uuidStorage?.getUUID(forCid: callId) else {
|
|
434
473
|
#if DEBUG
|
|
435
|
-
print("[Callingx][
|
|
474
|
+
print("[Callingx][isCallTracked] callId not found")
|
|
436
475
|
#endif
|
|
437
476
|
return false
|
|
438
477
|
}
|
|
@@ -451,16 +490,18 @@ import stream_react_native_webrtc
|
|
|
451
490
|
print("[Callingx][setCurrentCallActive] callId = \(callId)")
|
|
452
491
|
#endif
|
|
453
492
|
|
|
454
|
-
guard let
|
|
493
|
+
guard let call = CallingxImpl.uuidStorage?.getCall(forCid: callId) else {
|
|
455
494
|
#if DEBUG
|
|
456
495
|
print("[Callingx][setCurrentCallActive] callId not found")
|
|
457
496
|
#endif
|
|
458
497
|
return false
|
|
459
498
|
}
|
|
460
499
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
500
|
+
call.markConnected()
|
|
501
|
+
|
|
502
|
+
// Report connected timestamp to CallKit.
|
|
503
|
+
// startedConnectingAt is reported separately in the CXStartCallAction delegate.
|
|
504
|
+
callKeepProvider?.reportOutgoingCall(with: call.uuid, connectedAt: call.connectedAt ?? Date())
|
|
464
505
|
return true
|
|
465
506
|
}
|
|
466
507
|
|
|
@@ -469,15 +510,15 @@ import stream_react_native_webrtc
|
|
|
469
510
|
print("[Callingx][setMutedCall] muted = \(isMuted)")
|
|
470
511
|
#endif
|
|
471
512
|
|
|
472
|
-
guard let
|
|
513
|
+
guard let call = CallingxImpl.uuidStorage?.getCall(forCid: callId) else {
|
|
473
514
|
#if DEBUG
|
|
474
515
|
print("[Callingx][setMutedCall] callId not found")
|
|
475
516
|
#endif
|
|
476
517
|
return false
|
|
477
518
|
}
|
|
478
519
|
|
|
479
|
-
|
|
480
|
-
let setMutedAction = CXSetMutedCallAction(call: uuid, muted: isMuted)
|
|
520
|
+
call.markSelfMuted()
|
|
521
|
+
let setMutedAction = CXSetMutedCallAction(call: call.uuid, muted: isMuted)
|
|
481
522
|
let transaction = CXTransaction()
|
|
482
523
|
transaction.addAction(setMutedAction)
|
|
483
524
|
|
|
@@ -524,10 +565,12 @@ import stream_react_native_webrtc
|
|
|
524
565
|
return
|
|
525
566
|
}
|
|
526
567
|
|
|
568
|
+
let call = storage.getOrCreateCall(forCid: callId, isOutgoing: true)
|
|
569
|
+
call.markStartedConnecting() // outgoing: will be reported via reportOutgoingCall(startedConnectingAt:)
|
|
570
|
+
|
|
527
571
|
let handleType = Settings.getHandleType("generic")
|
|
528
|
-
let uuid = storage.getOrCreateUUID(forCid: callId)
|
|
529
572
|
let callHandle = CXHandle(type: handleType, value: phoneNumber)
|
|
530
|
-
let startCallAction = CXStartCallAction(call: uuid, handle: callHandle)
|
|
573
|
+
let startCallAction = CXStartCallAction(call: call.uuid, handle: callHandle)
|
|
531
574
|
startCallAction.isVideo = hasVideo
|
|
532
575
|
startCallAction.contactIdentifier = callerName
|
|
533
576
|
|
|
@@ -568,7 +611,7 @@ import stream_react_native_webrtc
|
|
|
568
611
|
print("[Callingx][CXProviderDelegate][provider:performStartCallAction]")
|
|
569
612
|
#endif
|
|
570
613
|
|
|
571
|
-
guard let
|
|
614
|
+
guard let call = CallingxImpl.uuidStorage?.getCallByUUID(action.callUUID) else {
|
|
572
615
|
#if DEBUG
|
|
573
616
|
print("[Callingx][CXProviderDelegate][provider:performStartCallAction] callId not found")
|
|
574
617
|
#endif
|
|
@@ -576,22 +619,23 @@ import stream_react_native_webrtc
|
|
|
576
619
|
return
|
|
577
620
|
}
|
|
578
621
|
|
|
622
|
+
getAudioDeviceModule()?.reset()
|
|
579
623
|
AudioSessionManager.createAudioSessionIfNeeded()
|
|
580
624
|
|
|
581
625
|
sendEvent(CallingxEvents.didReceiveStartCallAction, body: [
|
|
582
|
-
"callId":
|
|
626
|
+
"callId": call.cid,
|
|
583
627
|
"handle": action.handle.value
|
|
584
628
|
])
|
|
585
629
|
|
|
586
630
|
action.fulfill()
|
|
631
|
+
|
|
632
|
+
// Report startedConnectingAt to CallKit now that the action is fulfilled.
|
|
633
|
+
// The timestamp was set in startCall when the call was created.
|
|
634
|
+
callKeepProvider?.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.startedConnectingAt ?? Date())
|
|
587
635
|
}
|
|
588
636
|
|
|
589
637
|
public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
|
|
590
|
-
|
|
591
|
-
print("[Callingx][CXProviderDelegate][provider:performAnswerCallAction] isSelfAnswered: \(isSelfAnswered)")
|
|
592
|
-
#endif
|
|
593
|
-
|
|
594
|
-
guard let callId = CallingxImpl.uuidStorage?.getCid(forUUID: action.callUUID) else {
|
|
638
|
+
guard let call = CallingxImpl.uuidStorage?.getCallByUUID(action.callUUID) else {
|
|
595
639
|
#if DEBUG
|
|
596
640
|
print("[Callingx][CXProviderDelegate][provider:performAnswerCallAction] callId not found")
|
|
597
641
|
#endif
|
|
@@ -599,24 +643,29 @@ import stream_react_native_webrtc
|
|
|
599
643
|
return
|
|
600
644
|
}
|
|
601
645
|
|
|
646
|
+
#if DEBUG
|
|
647
|
+
print("[Callingx][CXProviderDelegate][provider:performAnswerCallAction] isSelfAnswered: \(call.isSelfAnswered)")
|
|
648
|
+
#endif
|
|
649
|
+
|
|
650
|
+
getAudioDeviceModule()?.reset()
|
|
602
651
|
AudioSessionManager.createAudioSessionIfNeeded()
|
|
603
652
|
|
|
604
|
-
let source = isSelfAnswered ? "app" : "sys"
|
|
653
|
+
let source = call.isSelfAnswered ? "app" : "sys"
|
|
605
654
|
sendEvent(CallingxEvents.performAnswerCallAction, body: [
|
|
606
|
-
"callId":
|
|
655
|
+
"callId": call.cid,
|
|
607
656
|
"source": source
|
|
608
657
|
])
|
|
609
658
|
|
|
610
|
-
|
|
659
|
+
call.resetSelfAnswered()
|
|
660
|
+
call.markConnected() // incoming: call is now connected
|
|
661
|
+
// TODO: Use action.fulfill(withDateConnected: call.connectedAt ?? Date()) instead of bare
|
|
662
|
+
// action.fulfill() to give CallKit more accurate call duration tracking in the system call log.
|
|
663
|
+
// to be done with pending action fulfillment
|
|
611
664
|
action.fulfill()
|
|
612
665
|
}
|
|
613
666
|
|
|
614
667
|
public func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
|
615
|
-
|
|
616
|
-
print("[Callingx][CXProviderDelegate][provider:performEndCallAction] isSelfEnded: \(isSelfEnded)")
|
|
617
|
-
#endif
|
|
618
|
-
|
|
619
|
-
guard let callId = CallingxImpl.uuidStorage?.getCid(forUUID: action.callUUID) else {
|
|
668
|
+
guard let call = CallingxImpl.uuidStorage?.getCallByUUID(action.callUUID) else {
|
|
620
669
|
#if DEBUG
|
|
621
670
|
print("[Callingx][CXProviderDelegate][provider:performEndCallAction] callId not found")
|
|
622
671
|
#endif
|
|
@@ -624,14 +673,19 @@ import stream_react_native_webrtc
|
|
|
624
673
|
return
|
|
625
674
|
}
|
|
626
675
|
|
|
627
|
-
|
|
676
|
+
#if DEBUG
|
|
677
|
+
print("[Callingx][CXProviderDelegate][provider:performEndCallAction] isSelfEnded: \(call.isSelfEnded)")
|
|
678
|
+
#endif
|
|
679
|
+
|
|
680
|
+
let source = call.isSelfEnded ? "app" : "sys"
|
|
628
681
|
sendEvent(CallingxEvents.performEndCallAction, body: [
|
|
629
|
-
"callId":
|
|
682
|
+
"callId": call.cid,
|
|
630
683
|
"source": source
|
|
631
684
|
])
|
|
632
685
|
|
|
633
|
-
|
|
634
|
-
|
|
686
|
+
call.resetSelfEnded()
|
|
687
|
+
call.markEnded()
|
|
688
|
+
CallingxImpl.uuidStorage?.removeCid(call.cid)
|
|
635
689
|
|
|
636
690
|
action.fulfill()
|
|
637
691
|
}
|
|
@@ -658,14 +712,7 @@ import stream_react_native_webrtc
|
|
|
658
712
|
}
|
|
659
713
|
|
|
660
714
|
public func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
|
|
661
|
-
let
|
|
662
|
-
isSelfMuted = false
|
|
663
|
-
|
|
664
|
-
#if DEBUG
|
|
665
|
-
print("[Callingx][CXProviderDelegate][provider:performSetMutedCallAction] \(action.isMuted) isAppInitiated: \(isAppInitiated)")
|
|
666
|
-
#endif
|
|
667
|
-
|
|
668
|
-
guard let callId = CallingxImpl.uuidStorage?.getCid(forUUID: action.callUUID) else {
|
|
715
|
+
guard let call = CallingxImpl.uuidStorage?.getCallByUUID(action.callUUID) else {
|
|
669
716
|
#if DEBUG
|
|
670
717
|
print("[Callingx][CXProviderDelegate][provider:performSetMutedCallAction] callId not found")
|
|
671
718
|
#endif
|
|
@@ -673,6 +720,13 @@ import stream_react_native_webrtc
|
|
|
673
720
|
return
|
|
674
721
|
}
|
|
675
722
|
|
|
723
|
+
let isAppInitiated = call.isSelfMuted
|
|
724
|
+
call.resetSelfMuted()
|
|
725
|
+
|
|
726
|
+
#if DEBUG
|
|
727
|
+
print("[Callingx][CXProviderDelegate][provider:performSetMutedCallAction] \(action.isMuted) isAppInitiated: \(isAppInitiated)")
|
|
728
|
+
#endif
|
|
729
|
+
|
|
676
730
|
// Only send the event to JS when the mute was initiated by the system
|
|
677
731
|
// (e.g. user tapped mute on the native CallKit UI).
|
|
678
732
|
// Skip app-initiated actions to prevent the feedback loop:
|
|
@@ -680,7 +734,7 @@ import stream_react_native_webrtc
|
|
|
680
734
|
if !isAppInitiated {
|
|
681
735
|
sendEvent(CallingxEvents.didPerformSetMutedCallAction, body: [
|
|
682
736
|
"muted": action.isMuted,
|
|
683
|
-
"callId":
|
|
737
|
+
"callId": call.cid
|
|
684
738
|
])
|
|
685
739
|
}
|
|
686
740
|
|
|
@@ -731,6 +785,7 @@ import stream_react_native_webrtc
|
|
|
731
785
|
|
|
732
786
|
// When CallKit deactivates the AVAudioSession, inform WebRTC as well.
|
|
733
787
|
RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
|
|
788
|
+
getAudioDeviceModule()?.reset()
|
|
734
789
|
|
|
735
790
|
// Disable wake lock when the call ends
|
|
736
791
|
DispatchQueue.main.async {
|
|
@@ -753,5 +808,16 @@ import stream_react_native_webrtc
|
|
|
753
808
|
|
|
754
809
|
sendEvent(CallingxEvents.providerReset, body: nil)
|
|
755
810
|
}
|
|
811
|
+
|
|
812
|
+
// MARK: - Helper Methods
|
|
813
|
+
private func getAudioDeviceModule() -> AudioDeviceModule? {
|
|
814
|
+
guard let adm = webRTCModule?.audioDeviceModule else {
|
|
815
|
+
#if DEBUG
|
|
816
|
+
print("[Callingx] WebRTCModule is not available. Ensure it was injected from the TurboModule host.")
|
|
817
|
+
#endif
|
|
818
|
+
return nil
|
|
819
|
+
}
|
|
820
|
+
return adm
|
|
821
|
+
}
|
|
756
822
|
}
|
|
757
823
|
|
package/ios/CallingxPublic.h
CHANGED
|
@@ -21,9 +21,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
21
21
|
* supportsDTMF:NO
|
|
22
22
|
* supportsGrouping:NO
|
|
23
23
|
* supportsUngrouping:NO
|
|
24
|
-
* fromPushKit:YES
|
|
25
24
|
* payload:payload
|
|
26
|
-
* withCompletionHandler:^{ }];
|
|
25
|
+
* withCompletionHandler:^(void){ }];
|
|
27
26
|
* ```
|
|
28
27
|
*/
|
|
29
28
|
@interface Callingx : NSObject
|
|
@@ -41,9 +40,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
41
40
|
* @param supportsDTMF Whether the call supports DTMF tones
|
|
42
41
|
* @param supportsGrouping Whether the call can be grouped with other calls
|
|
43
42
|
* @param supportsUngrouping Whether the call can be ungrouped
|
|
44
|
-
* @param fromPushKit Whether this call is from a PushKit notification
|
|
45
43
|
* @param payload Optional payload data from the push notification
|
|
46
|
-
* @param completion Completion handler called after the call is reported
|
|
44
|
+
* @param completion Completion handler called after the call is reported, with an error if the call could not be displayed
|
|
47
45
|
*/
|
|
48
46
|
+ (void)reportNewIncomingCall:(NSString *)callId
|
|
49
47
|
handle:(NSString *)handle
|
|
@@ -54,7 +52,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
54
52
|
supportsDTMF:(BOOL)supportsDTMF
|
|
55
53
|
supportsGrouping:(BOOL)supportsGrouping
|
|
56
54
|
supportsUngrouping:(BOOL)supportsUngrouping
|
|
57
|
-
fromPushKit:(BOOL)fromPushKit
|
|
58
55
|
payload:(NSDictionary * _Nullable)payload
|
|
59
56
|
withCompletionHandler:(void (^_Nullable)(void))completion;
|
|
60
57
|
|
package/ios/UUIDStorage.swift
CHANGED
|
@@ -1,34 +1,78 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
@objcMembers public class UUIDStorage: NSObject {
|
|
4
|
-
|
|
5
|
-
private var
|
|
4
|
+
/// Primary storage: cid -> CallingxCall
|
|
5
|
+
private var callsByCid: [String: CallingxCall] = [:]
|
|
6
|
+
/// Reverse lookup: lowercased UUID string -> CallingxCall
|
|
7
|
+
private var callsByUUID: [String: CallingxCall] = [:]
|
|
6
8
|
private let queue = DispatchQueue(label: "com.stream.uuidstorage", attributes: [])
|
|
7
9
|
|
|
8
10
|
public override init() {
|
|
9
11
|
super.init()
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
// MARK: - CallingxCall-based API (new)
|
|
15
|
+
|
|
16
|
+
/// Returns the existing call for the given cid, or creates a new one.
|
|
17
|
+
public func getOrCreateCall(forCid cid: String, isOutgoing: Bool = false) -> CallingxCall {
|
|
18
|
+
return queue.sync {
|
|
19
|
+
if let existing = callsByCid[cid] {
|
|
20
|
+
#if DEBUG
|
|
21
|
+
print("[UUIDStorage] getOrCreateCall: found existing \(existing)")
|
|
22
|
+
#endif
|
|
23
|
+
return existing
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let uuid = UUID()
|
|
27
|
+
let call = CallingxCall(uuid: uuid, cid: cid, isOutgoing: isOutgoing)
|
|
28
|
+
let uuidString = uuid.uuidString.lowercased()
|
|
29
|
+
callsByCid[cid] = call
|
|
30
|
+
callsByUUID[uuidString] = call
|
|
31
|
+
#if DEBUG
|
|
32
|
+
print("[UUIDStorage] getOrCreateCall: created \(call)")
|
|
33
|
+
#endif
|
|
34
|
+
return call
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Returns the call for the given cid, or nil if not found.
|
|
39
|
+
public func getCall(forCid cid: String) -> CallingxCall? {
|
|
40
|
+
return queue.sync {
|
|
41
|
+
return callsByCid[cid]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Returns the call for the given UUID, or nil if not found.
|
|
46
|
+
public func getCallByUUID(_ uuid: UUID) -> CallingxCall? {
|
|
47
|
+
return queue.sync {
|
|
48
|
+
let uuidString = uuid.uuidString.lowercased()
|
|
49
|
+
return callsByUUID[uuidString]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// MARK: - Legacy API (preserved for backward compatibility)
|
|
54
|
+
|
|
12
55
|
public func allUUIDs() -> [UUID] {
|
|
13
56
|
return queue.sync {
|
|
14
|
-
return
|
|
57
|
+
return callsByCid.values.map { $0.uuid }
|
|
15
58
|
}
|
|
16
59
|
}
|
|
17
60
|
|
|
61
|
+
/// Returns the existing UUID for the given cid, or creates a new CallingxCall and returns its UUID.
|
|
18
62
|
public func getOrCreateUUID(forCid cid: String) -> UUID {
|
|
19
63
|
return queue.sync {
|
|
20
|
-
|
|
21
|
-
if let existingUUID = uuidDict[cid] {
|
|
64
|
+
if let existing = callsByCid[cid] {
|
|
22
65
|
#if DEBUG
|
|
23
|
-
print("[UUIDStorage] getUUIDForCid: found existing UUID \(
|
|
66
|
+
print("[UUIDStorage] getUUIDForCid: found existing UUID \(existing.uuid.uuidString.lowercased()) for cid \(cid)")
|
|
24
67
|
#endif
|
|
25
|
-
return
|
|
68
|
+
return existing.uuid
|
|
26
69
|
}
|
|
27
70
|
|
|
28
71
|
let uuid = UUID()
|
|
72
|
+
let call = CallingxCall(uuid: uuid, cid: cid, isOutgoing: false)
|
|
29
73
|
let uuidString = uuid.uuidString.lowercased()
|
|
30
|
-
|
|
31
|
-
|
|
74
|
+
callsByCid[cid] = call
|
|
75
|
+
callsByUUID[uuidString] = call
|
|
32
76
|
#if DEBUG
|
|
33
77
|
print("[UUIDStorage] getUUIDForCid: created new UUID \(uuidString) for cid \(cid)")
|
|
34
78
|
#endif
|
|
@@ -38,15 +82,14 @@ import Foundation
|
|
|
38
82
|
|
|
39
83
|
public func getUUID(forCid cid: String) -> UUID? {
|
|
40
84
|
return queue.sync {
|
|
41
|
-
|
|
42
|
-
return UUID(uuidString: uuidString)
|
|
85
|
+
return callsByCid[cid]?.uuid
|
|
43
86
|
}
|
|
44
87
|
}
|
|
45
88
|
|
|
46
89
|
public func getCid(forUUID uuid: UUID) -> String? {
|
|
47
90
|
return queue.sync {
|
|
48
91
|
let uuidString = uuid.uuidString.lowercased()
|
|
49
|
-
let cid =
|
|
92
|
+
let cid = callsByUUID[uuidString]?.cid
|
|
50
93
|
#if DEBUG
|
|
51
94
|
print("[UUIDStorage] getCidForUUID: UUID \(uuidString) -> cid \(cid ?? "(not found)")")
|
|
52
95
|
#endif
|
|
@@ -57,11 +100,11 @@ import Foundation
|
|
|
57
100
|
public func removeCid(forUUID uuid: UUID) {
|
|
58
101
|
queue.sync {
|
|
59
102
|
let uuidString = uuid.uuidString.lowercased()
|
|
60
|
-
if let
|
|
61
|
-
|
|
62
|
-
|
|
103
|
+
if let call = callsByUUID[uuidString] {
|
|
104
|
+
callsByCid.removeValue(forKey: call.cid)
|
|
105
|
+
callsByUUID.removeValue(forKey: uuidString)
|
|
63
106
|
#if DEBUG
|
|
64
|
-
print("[UUIDStorage] removeCidForUUID: removed cid \(cid) for UUID \(uuidString)")
|
|
107
|
+
print("[UUIDStorage] removeCidForUUID: removed cid \(call.cid) for UUID \(uuidString)")
|
|
65
108
|
#endif
|
|
66
109
|
} else {
|
|
67
110
|
#if DEBUG
|
|
@@ -73,9 +116,10 @@ import Foundation
|
|
|
73
116
|
|
|
74
117
|
public func removeCid(_ cid: String) {
|
|
75
118
|
queue.sync {
|
|
76
|
-
if let
|
|
77
|
-
|
|
78
|
-
|
|
119
|
+
if let call = callsByCid[cid] {
|
|
120
|
+
let uuidString = call.uuid.uuidString.lowercased()
|
|
121
|
+
callsByUUID.removeValue(forKey: uuidString)
|
|
122
|
+
callsByCid.removeValue(forKey: cid)
|
|
79
123
|
#if DEBUG
|
|
80
124
|
print("[UUIDStorage] removeCid: removed cid \(cid) with UUID \(uuidString)")
|
|
81
125
|
#endif
|
|
@@ -89,9 +133,9 @@ import Foundation
|
|
|
89
133
|
|
|
90
134
|
public func removeAllObjects() {
|
|
91
135
|
queue.sync {
|
|
92
|
-
let count =
|
|
93
|
-
|
|
94
|
-
|
|
136
|
+
let count = callsByCid.count
|
|
137
|
+
callsByCid.removeAll()
|
|
138
|
+
callsByUUID.removeAll()
|
|
95
139
|
#if DEBUG
|
|
96
140
|
print("[UUIDStorage] removeAllObjects: cleared \(count) entries")
|
|
97
141
|
#endif
|
|
@@ -100,25 +144,26 @@ import Foundation
|
|
|
100
144
|
|
|
101
145
|
public func count() -> Int {
|
|
102
146
|
return queue.sync {
|
|
103
|
-
return
|
|
147
|
+
return callsByCid.count
|
|
104
148
|
}
|
|
105
149
|
}
|
|
106
150
|
|
|
107
151
|
public func containsCid(_ cid: String) -> Bool {
|
|
108
152
|
return queue.sync {
|
|
109
|
-
return
|
|
153
|
+
return callsByCid[cid] != nil
|
|
110
154
|
}
|
|
111
155
|
}
|
|
112
156
|
|
|
113
157
|
public func containsUUID(_ uuid: UUID) -> Bool {
|
|
114
158
|
return queue.sync {
|
|
115
|
-
return
|
|
159
|
+
return callsByUUID[uuid.uuidString.lowercased()] != nil
|
|
116
160
|
}
|
|
117
161
|
}
|
|
118
162
|
|
|
119
163
|
public override var description: String {
|
|
120
164
|
return queue.sync {
|
|
121
|
-
|
|
165
|
+
let entries = callsByCid.map { "\($0.key): \($0.value)" }.joined(separator: ", ")
|
|
166
|
+
return "UUIDStorage: [\(entries)]"
|
|
122
167
|
}
|
|
123
168
|
}
|
|
124
169
|
}
|