@stream-io/react-native-callingx 0.6.0 → 0.6.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [0.6.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/react-native-callingx-0.6.0...@stream-io/react-native-callingx-0.6.1) (2026-07-02)
6
+
7
+ ### Dependency Updates
8
+
9
+ - `@stream-io/typescript-config` updated to version `0.1.0`
10
+
11
+ ### Bug Fixes
12
+
13
+ - made CXCallObserver static warm instance ([#2306](https://github.com/GetStream/stream-video-js/issues/2306)) ([ac79c64](https://github.com/GetStream/stream-video-js/commit/ac79c64f7231b12295e726f258e92c28b239d28b))
14
+
5
15
  ## [0.6.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/react-native-callingx-0.5.1...@stream-io/react-native-callingx-0.6.0) (2026-06-26)
6
16
 
7
17
  ### Dependency Updates
@@ -208,26 +208,15 @@ import stream_react_native_webrtc
208
208
  }
209
209
 
210
210
  @objc public static func canRegisterCall() -> Bool {
211
- let hasCall = hasRegisteredCall()
212
211
  let shouldReject = Settings.getShouldRejectCallWhenBusy()
213
- return !shouldReject || (shouldReject && !hasCall)
212
+ guard shouldReject else { return true }
213
+ return !hasRegisteredCall()
214
214
  }
215
-
215
+
216
216
  @objc public static func hasRegisteredCall() -> Bool {
217
- guard let storage = uuidStorage else { return false }
218
-
219
- let appUUIDs = storage.allUUIDs()
220
- if appUUIDs.isEmpty { return false }
221
-
222
- let observer = CXCallObserver()
223
- for call in observer.calls {
224
- for uuid in appUUIDs {
225
- if call.uuid == uuid {
226
- return true
227
- }
228
- }
229
- }
230
- return false
217
+ // Backed by the warm CXCallObserver maintained in UUIDStorage — no per-call observer
218
+ // construction and no cold-snapshot misses. Intersects our calls with CallKit's live set.
219
+ return uuidStorage?.hasRegisteredCall() ?? false
231
220
  }
232
221
 
233
222
  @objc public static func getAudioOutput() -> String? {
@@ -427,8 +416,12 @@ import stream_react_native_webrtc
427
416
 
428
417
  // This is mostly needed for very first setup, as we need to override the default
429
418
  // provider configuration which is set in the constructor.
430
- // IMPORTANT: We override CXProvider instance only if there is no registered call, otherwise we may lose corrsponding call state/events from CallKit
431
- if !CallingxImpl.hasRegisteredCall() {
419
+ // IMPORTANT: We override the CXProvider instance only when there are no calls in
420
+ // flight, otherwise we'd destroy CallKit state/events for a live call. We check our
421
+ // own storage rather than CXCallObserver here: the observer trails registration and
422
+ // can briefly report empty (e.g. a VoIP-push call reported just before setup runs),
423
+ // which would wrongly tear down the provider mid-call.
424
+ if (CallingxImpl.uuidStorage?.count() ?? 0) == 0 {
432
425
  let oldProvider = CallingxImpl.sharedProvider
433
426
  let newProvider = CXProvider(configuration: Settings.getProviderConfiguration())
434
427
  newProvider.setDelegate(self, queue: nil)
@@ -581,18 +574,9 @@ import stream_react_native_webrtc
581
574
  }
582
575
 
583
576
  @objc public func isCallTracked(_ callId: String) -> Bool {
584
- guard let uuid = CallingxImpl.uuidStorage?.getUUID(forCid: callId) else {
585
- CallingxLog.core.debugPublic("[isCallTracked] callId not found")
586
- return false
587
- }
588
-
589
- let observer = CXCallObserver()
590
- for call in observer.calls {
591
- if call.uuid == uuid {
592
- return true
593
- }
594
- }
595
- return false
577
+ // Backed by the warm CXCallObserver in UUIDStorage — no per-call observer construction
578
+ // and no cold-snapshot misses. True when the call is tracked AND confirmed live by CallKit.
579
+ return CallingxImpl.uuidStorage?.isCallTracked(forCid: callId) ?? false
596
580
  }
597
581
 
598
582
  @objc public func setCurrentCallActive(_ callId: String) -> Bool {
@@ -1,14 +1,58 @@
1
1
  import Foundation
2
+ import CallKit
2
3
 
3
- @objcMembers public class UUIDStorage: NSObject {
4
+ @objcMembers public class UUIDStorage: NSObject, CXCallObserverDelegate {
4
5
  /// Primary storage: cid -> CallingxCall
5
6
  private var callsByCid: [String: CallingxCall] = [:]
6
7
  /// Reverse lookup: lowercased UUID string -> CallingxCall
7
8
  private var callsByUUID: [String: CallingxCall] = [:]
8
9
  private let queue = DispatchQueue(label: "com.stream.uuidstorage", attributes: [])
9
10
 
11
+ /// Warm, long-lived observer of CallKit's call state — a cold observer can report empty even for a
12
+ /// call that's been live for a while.
13
+ private let callObserver = CXCallObserver()
14
+ /// CallKit's live view, maintained from observer callbacks. NOTE: contains UUIDs of ALL
15
+ /// system calls. Only ever intersect it with our own UUIDs — never treat it as "our calls".
16
+ private var liveCallKitUUIDs: Set<UUID> = []
17
+
10
18
  public override init() {
11
19
  super.init()
20
+ callObserver.setDelegate(self, queue: queue)
21
+ queue.async { [weak self] in
22
+ guard let self = self else { return }
23
+ self.liveCallKitUUIDs = Set(
24
+ self.callObserver.calls.filter { !$0.hasEnded }.map { $0.uuid }
25
+ )
26
+ }
27
+ }
28
+
29
+ // MARK: - CXCallObserverDelegate
30
+
31
+ /// IMPORTANT: delivered on `queue`, NEVER call the `queue.sync` helpers below —
32
+ /// re-entering the serial queue would deadlock.
33
+ public func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
34
+ if call.hasEnded {
35
+ liveCallKitUUIDs.remove(call.uuid)
36
+ } else {
37
+ liveCallKitUUIDs.insert(call.uuid)
38
+ }
39
+ }
40
+
41
+ // MARK: - CallKit liveness queries
42
+
43
+ public func hasRegisteredCall() -> Bool {
44
+ return queue.sync {
45
+ guard !callsByCid.isEmpty else { return false }
46
+ let ours = Set(callsByCid.values.map { $0.uuid })
47
+ return !ours.isDisjoint(with: liveCallKitUUIDs)
48
+ }
49
+ }
50
+
51
+ public func isCallTracked(forCid cid: String) -> Bool {
52
+ return queue.sync {
53
+ guard let call = callsByCid[cid] else { return false }
54
+ return liveCallKitUUIDs.contains(call.uuid)
55
+ }
12
56
  }
13
57
 
14
58
  // MARK: - CallingxCall-based API (new)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/react-native-callingx",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "CallKit and Telecom API capabilities for React Native",
5
5
  "main": "./dist/module/index.js",
6
6
  "module": "./dist/module/index.js",