@stream-io/react-native-callingx 0.4.0 → 0.5.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.
@@ -0,0 +1,67 @@
1
+ import Foundation
2
+ import os
3
+
4
+ /// Unified-logging namespace for the Callingx package.
5
+ ///
6
+ /// Replaces the previous `NSLog` usage. Each category maps to one of the
7
+ /// `[Tag]` prefixes that used to be hand-built into the log strings, so logs
8
+ /// can be filtered by subsystem/category in Console.app or `log stream`:
9
+ ///
10
+ /// log stream --predicate 'subsystem == "io.getstream.callingx"' --level debug
11
+ ///
12
+ /// The subsystem matches the queue-label convention already used across this
13
+ /// package (e.g. `io.getstream.callingx.pendingActions`).
14
+ enum CallingxLog {
15
+ private static let subsystem = "io.getstream.callingx"
16
+
17
+ static let core = Logger(subsystem: subsystem, category: "Callingx")
18
+ static let uuid = Logger(subsystem: subsystem, category: "UUIDStorage")
19
+ static let voip = Logger(subsystem: subsystem, category: "VoipNotifications")
20
+ static let push = Logger(subsystem: subsystem, category: "VoipPush")
21
+ static let settings = Logger(subsystem: subsystem, category: "Settings")
22
+ static let audio = Logger(subsystem: subsystem, category: "AudioSession")
23
+ static let js = Logger(subsystem: subsystem, category: "JS")
24
+ }
25
+
26
+ extension Logger {
27
+ /// Logs a pre-built message at `.debug`, marking the whole string public so
28
+ /// values stay readable when streaming logs (incl. release/TestFlight).
29
+ /// `@autoclosure` keeps construction lazy: when the log is not being
30
+ /// collected the string is never built.
31
+ func debugPublic(_ message: @autoclosure @escaping () -> String) {
32
+ debug("\(message(), privacy: .public)")
33
+ }
34
+
35
+ /// Public-message variant at `.error` (persisted by default in the unified log).
36
+ func errorPublic(_ message: @autoclosure @escaping () -> String) {
37
+ error("\(message(), privacy: .public)")
38
+ }
39
+ }
40
+
41
+ /// Objective-C bridge so `Callingx.mm` and `VoipPushHandler.m` log through the
42
+ /// same `os.Logger` path. `Logger`'s interpolation API is Swift-only, hence the
43
+ /// thin `@objc` wrapper.
44
+ @objc public final class CallingxLogBridge: NSObject {
45
+ @objc public static func pushDebug(_ message: String) {
46
+ CallingxLog.push.debugPublic(message)
47
+ }
48
+
49
+ @objc public static func pushError(_ message: String) {
50
+ CallingxLog.push.errorPublic(message)
51
+ }
52
+
53
+ /// Routes JS-originated logs (from the `log(message, level)` TurboModule
54
+ /// method) to the matching os.Logger severity.
55
+ @objc public static func js(_ message: String, level: String) {
56
+ switch level {
57
+ case "error":
58
+ CallingxLog.js.error("\(message, privacy: .public)")
59
+ case "warn":
60
+ CallingxLog.js.warning("\(message, privacy: .public)")
61
+ case "info":
62
+ CallingxLog.js.info("\(message, privacy: .public)")
63
+ default:
64
+ CallingxLog.js.debug("\(message, privacy: .public)")
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,37 @@
1
+ import Foundation
2
+
3
+ /// Cross-package handoff guard. `CallingxImpl` flips `callingxOwnsSession`
4
+ /// to `true` at CX-action entry points (`CXStartCallAction.perform`,
5
+ /// `CXAnswerCallAction.perform`, re-asserted in `provider(_:didActivate:)`)
6
+ /// and back to `false` in `provider(_:didDeactivate:)` when no calls remain
7
+ /// (`UUIDStorage.count() == 0`) or on `providerDidReset`.
8
+ ///
9
+ /// `StreamInCallManager` (in `@stream-io/video-react-native-sdk`) consults this
10
+ /// flag from its AudioDeviceModule publisher sink and no-ops when callingx owns
11
+ /// the session, so the two packages don't write conflicting `AVAudioSession`
12
+ /// configurations during the transient overlap when a CallKit call is winding
13
+ /// down and the SDK is about to take over (or vice versa).
14
+ ///
15
+ /// Exposed as an `@objcMembers NSObject` with an `@objc class var` so that
16
+ /// react-native-sdk can read it via `NSClassFromString("Callingx.CallingxSessionOwnership")`
17
+ /// + KVC on the class object — `@stream-io/react-native-callingx` is an *optional*
18
+ /// peer dependency of react-native-sdk, so a direct `import Callingx` is not safe.
19
+ @objcMembers
20
+ public class CallingxSessionOwnership: NSObject {
21
+
22
+ private static let lock = NSLock()
23
+ private static var _callingxOwnsSession: Bool = false
24
+
25
+ @objc public class var callingxOwnsSession: Bool {
26
+ get {
27
+ lock.lock()
28
+ defer { lock.unlock() }
29
+ return _callingxOwnsSession
30
+ }
31
+ set {
32
+ lock.lock()
33
+ defer { lock.unlock() }
34
+ _callingxOwnsSession = newValue
35
+ }
36
+ }
37
+ }
@@ -10,9 +10,7 @@ import UIKit
10
10
  }
11
11
 
12
12
  public static func setSettings(_ options: [String: Any]?) {
13
- #if DEBUG
14
- NSLog("%@","[Settings][setSettings] options = \(String(describing: options))")
15
- #endif
13
+ CallingxLog.settings.debugPublic("[setSettings] options = \(String(describing: options))")
16
14
 
17
15
  var settings: [String: Any] = getSettings()
18
16
 
@@ -45,9 +43,7 @@ import UIKit
45
43
  }
46
44
 
47
45
  public static func getProviderConfiguration() -> CXProviderConfiguration {
48
- #if DEBUG
49
- NSLog("%@","[Settings][getProviderConfiguration]")
50
- #endif
46
+ CallingxLog.settings.debugPublic("[getProviderConfiguration]")
51
47
 
52
48
  let settings = getSettings()
53
49
  let providerConfiguration = CXProviderConfiguration()
@@ -17,9 +17,7 @@ import Foundation
17
17
  public func getOrCreateCall(forCid cid: String, isOutgoing: Bool = false) -> CallingxCall {
18
18
  return queue.sync {
19
19
  if let existing = callsByCid[cid] {
20
- #if DEBUG
21
- NSLog("%@","[UUIDStorage] getOrCreateCall: found existing \(existing)")
22
- #endif
20
+ CallingxLog.uuid.debugPublic("getOrCreateCall: found existing \(existing)")
23
21
  return existing
24
22
  }
25
23
 
@@ -28,9 +26,7 @@ import Foundation
28
26
  let uuidString = uuid.uuidString.lowercased()
29
27
  callsByCid[cid] = call
30
28
  callsByUUID[uuidString] = call
31
- #if DEBUG
32
- NSLog("%@","[UUIDStorage] getOrCreateCall: created \(call)")
33
- #endif
29
+ CallingxLog.uuid.debugPublic("getOrCreateCall: created \(call)")
34
30
  return call
35
31
  }
36
32
  }
@@ -62,9 +58,7 @@ import Foundation
62
58
  public func getOrCreateUUID(forCid cid: String) -> UUID {
63
59
  return queue.sync {
64
60
  if let existing = callsByCid[cid] {
65
- #if DEBUG
66
- NSLog("%@","[UUIDStorage] getUUIDForCid: found existing UUID \(existing.uuid.uuidString.lowercased()) for cid \(cid)")
67
- #endif
61
+ CallingxLog.uuid.debugPublic("getUUIDForCid: found existing UUID \(existing.uuid.uuidString.lowercased()) for cid \(cid)")
68
62
  return existing.uuid
69
63
  }
70
64
 
@@ -73,9 +67,7 @@ import Foundation
73
67
  let uuidString = uuid.uuidString.lowercased()
74
68
  callsByCid[cid] = call
75
69
  callsByUUID[uuidString] = call
76
- #if DEBUG
77
- NSLog("%@","[UUIDStorage] getUUIDForCid: created new UUID \(uuidString) for cid \(cid)")
78
- #endif
70
+ CallingxLog.uuid.debugPublic("getUUIDForCid: created new UUID \(uuidString) for cid \(cid)")
79
71
  return uuid
80
72
  }
81
73
  }
@@ -90,9 +82,7 @@ import Foundation
90
82
  return queue.sync {
91
83
  let uuidString = uuid.uuidString.lowercased()
92
84
  let cid = callsByUUID[uuidString]?.cid
93
- #if DEBUG
94
- NSLog("%@","[UUIDStorage] getCidForUUID: UUID \(uuidString) -> cid \(cid ?? "(not found)")")
95
- #endif
85
+ CallingxLog.uuid.debugPublic("getCidForUUID: UUID \(uuidString) -> cid \(cid ?? "(not found)")")
96
86
  return cid
97
87
  }
98
88
  }
@@ -103,13 +93,9 @@ import Foundation
103
93
  if let call = callsByUUID[uuidString] {
104
94
  callsByCid.removeValue(forKey: call.cid)
105
95
  callsByUUID.removeValue(forKey: uuidString)
106
- #if DEBUG
107
- NSLog("%@","[UUIDStorage] removeCidForUUID: removed cid \(call.cid) for UUID \(uuidString)")
108
- #endif
96
+ CallingxLog.uuid.debugPublic("removeCidForUUID: removed cid \(call.cid) for UUID \(uuidString)")
109
97
  } else {
110
- #if DEBUG
111
- NSLog("%@","[UUIDStorage] removeCidForUUID: no cid found for UUID \(uuidString)")
112
- #endif
98
+ CallingxLog.uuid.debugPublic("removeCidForUUID: no cid found for UUID \(uuidString)")
113
99
  }
114
100
  }
115
101
  }
@@ -120,13 +106,9 @@ import Foundation
120
106
  let uuidString = call.uuid.uuidString.lowercased()
121
107
  callsByUUID.removeValue(forKey: uuidString)
122
108
  callsByCid.removeValue(forKey: cid)
123
- #if DEBUG
124
- NSLog("%@","[UUIDStorage] removeCid: removed cid \(cid) with UUID \(uuidString)")
125
- #endif
109
+ CallingxLog.uuid.debugPublic("removeCid: removed cid \(cid) with UUID \(uuidString)")
126
110
  } else {
127
- #if DEBUG
128
- NSLog("%@","[UUIDStorage] removeCid: no UUID found for cid \(cid)")
129
- #endif
111
+ CallingxLog.uuid.debugPublic("removeCid: no UUID found for cid \(cid)")
130
112
  }
131
113
  }
132
114
  }
@@ -136,9 +118,7 @@ import Foundation
136
118
  let count = callsByCid.count
137
119
  callsByCid.removeAll()
138
120
  callsByUUID.removeAll()
139
- #if DEBUG
140
- NSLog("%@","[UUIDStorage] removeAllObjects: cleared \(count) entries")
141
- #endif
121
+ CallingxLog.uuid.debugPublic("removeAllObjects: cleared \(count) entries")
142
122
  }
143
123
  }
144
124
 
@@ -55,17 +55,13 @@ typealias RNVoipPushNotificationCompletion = () -> Void
55
55
 
56
56
  @objc public static func voipRegistration() {
57
57
  if isVoipRegistered {
58
- #if DEBUG
59
- NSLog("%@","[VoipNotificationsManager] voipRegistration is already registered. return _lastVoipToken = \(lastVoipToken)")
60
- #endif
58
+ CallingxLog.voip.debugPublic("voipRegistration is already registered. return _lastVoipToken = \(lastVoipToken)")
61
59
  let voipPushManager = VoipNotificationsManager.shared()
62
60
  voipPushManager.sendEventWithNameWrapper(name: VoipNotificationsEvents.registered, body: ["token": lastVoipToken])
63
61
  return
64
62
  }
65
63
 
66
- #if DEBUG
67
- NSLog("%@","[VoipNotificationsManager] voipRegistration enter")
68
- #endif
64
+ CallingxLog.voip.debugPublic("voipRegistration enter")
69
65
 
70
66
  DispatchQueue.main.async {
71
67
  let registry = PKPushRegistry(queue: DispatchQueue.main)
@@ -77,10 +73,8 @@ typealias RNVoipPushNotificationCompletion = () -> Void
77
73
  }
78
74
 
79
75
  @objc public static func didUpdatePushCredentials(_ credentials: PKPushCredentials, forType type: String) {
80
- #if DEBUG
81
- NSLog("%@","[VoipNotificationsManager] didUpdatePushCredentials credentials.token = \(credentials.token), type = \(type)")
82
- #endif
83
-
76
+ CallingxLog.voip.debug("didUpdatePushCredentials credentials.token = \(credentials.token, privacy: .private), type = \(type, privacy: .public)")
77
+
84
78
  let voipTokenLength = credentials.token.count
85
79
  if voipTokenLength == 0 {
86
80
  return
@@ -93,10 +87,8 @@ typealias RNVoipPushNotificationCompletion = () -> Void
93
87
  }
94
88
 
95
89
  @objc public static func didReceiveIncomingPushWithPayload(_ payload: PKPushPayload, forType type: String) {
96
- #if DEBUG
97
- NSLog("%@","[VoipNotificationsManager] didReceiveIncomingPushWithPayload payload.dictionaryPayload = \(payload.dictionaryPayload), type = \(type)")
98
- #endif
99
-
90
+ CallingxLog.voip.debug("didReceiveIncomingPushWithPayload payload.dictionaryPayload = \(payload.dictionaryPayload, privacy: .private), type = \(type, privacy: .public)")
91
+
100
92
  let dictionaryPayload: [String: Any] = Dictionary(uniqueKeysWithValues: payload.dictionaryPayload.map { (key, value) in
101
93
  (String(describing: key), value)
102
94
  })
@@ -109,9 +101,7 @@ typealias RNVoipPushNotificationCompletion = () -> Void
109
101
  @objc public func getInitialEvents() -> [[String: Any]] {
110
102
  var events: [[String: Any]] = []
111
103
  let action = {
112
- #if DEBUG
113
- NSLog("%@","[VoipNotificationsManager][getInitialEvents] delayedEvents = \(self.delayedEvents)")
114
- #endif
104
+ CallingxLog.voip.debugPublic("[getInitialEvents] delayedEvents = \(self.delayedEvents)")
115
105
 
116
106
  events = self.delayedEvents
117
107
  self.delayedEvents = []
@@ -138,9 +128,7 @@ typealias RNVoipPushNotificationCompletion = () -> Void
138
128
  }
139
129
 
140
130
  private func sendEventWithNameWrapper(name: String, body: [String: Any]?) {
141
- #if DEBUG
142
- NSLog("%@","[VoipNotificationsManager] sendEventWithNameWrapper: \(name)")
143
- #endif
131
+ CallingxLog.voip.debugPublic("sendEventWithNameWrapper: \(name)")
144
132
 
145
133
  let sendEventAction = {
146
134
  var dictionary: [String: Any] = ["eventName": name]
@@ -152,9 +140,7 @@ typealias RNVoipPushNotificationCompletion = () -> Void
152
140
  self.eventEmitter?.emitVoipEvent(dictionary)
153
141
  } else {
154
142
  self.delayedEvents.append(dictionary)
155
- #if DEBUG
156
- NSLog("%@","[VoipNotificationsManager] delayedEvents: \(self.delayedEvents)")
157
- #endif
143
+ CallingxLog.voip.debugPublic("delayedEvents: \(self.delayedEvents)")
158
144
  }
159
145
  }
160
146
 
@@ -80,9 +80,7 @@ static void reportIncomingCallFromStreamPayload(NSDictionary *streamPayload,
80
80
  completionHandler:(void (^_Nullable)(void))completion {
81
81
  NSDictionary *streamPayload = payload.dictionaryPayload[@"stream"];
82
82
  if (!streamPayload) {
83
- #if DEBUG
84
- NSLog(@"[VoipPushHandler][handleIncomingPush] Stream payload not found");
85
- #endif
83
+ [CallingxLogBridge pushError:@"[handleIncomingPush] Stream payload not found"];
86
84
  if (completion) {
87
85
  completion();
88
86
  }
@@ -91,9 +89,7 @@ static void reportIncomingCallFromStreamPayload(NSDictionary *streamPayload,
91
89
 
92
90
  NSString *callCid = streamPayload[@"call_cid"];
93
91
  if (!callCid) {
94
- #if DEBUG
95
- NSLog(@"[VoipPushHandler][handleIncomingPush] Missing required field: call_cid");
96
- #endif
92
+ [CallingxLogBridge pushError:@"[handleIncomingPush] Missing required field: call_cid"];
97
93
  if (completion) {
98
94
  completion();
99
95
  }
@@ -126,7 +122,7 @@ static void reportIncomingCallFromStreamPayload(NSDictionary *streamPayload,
126
122
  [VoipPushHandler handleIncomingPush:payload
127
123
  forType:(NSString *)type
128
124
  completionHandler:completion];
129
- NSLog(@"[VoipPushHandler][pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:] completion");
125
+ [CallingxLogBridge pushDebug:@"[pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:] completion"];
130
126
  }
131
127
 
132
128
  #ifdef __IPHONE_26_4
@@ -135,9 +131,7 @@ static void reportIncomingCallFromStreamPayload(NSDictionary *streamPayload,
135
131
  completionHandler:(void (^_Nullable)(void))completion {
136
132
  NSDictionary *streamPayload = payload.dictionaryPayload[@"stream"];
137
133
  if (!streamPayload) {
138
- #if DEBUG
139
- NSLog(@"[VoipPushHandler][handleIncomingVoIPPush] Stream payload not found");
140
- #endif
134
+ [CallingxLogBridge pushError:@"[handleIncomingVoIPPush] Stream payload not found"];
141
135
  if (completion) {
142
136
  completion();
143
137
  }
@@ -146,9 +140,7 @@ static void reportIncomingCallFromStreamPayload(NSDictionary *streamPayload,
146
140
 
147
141
  NSString *callCid = streamPayload[@"call_cid"];
148
142
  if (!callCid) {
149
- #if DEBUG
150
- NSLog(@"[VoipPushHandler][handleIncomingVoIPPush] Missing required field: call_cid");
151
- #endif
143
+ [CallingxLogBridge pushError:@"[handleIncomingVoIPPush] Missing required field: call_cid"];
152
144
  if (completion) {
153
145
  completion();
154
146
  }
@@ -196,7 +188,7 @@ static void reportIncomingCallFromStreamPayload(NSDictionary *streamPayload,
196
188
  [VoipPushHandler handleIncomingVoIPPush:payload
197
189
  metadata:metadata
198
190
  completionHandler:completion];
199
- NSLog(@"[VoipPushHandler][pushRegistry:didReceiveIncomingVoIPPushWithPayload:metadata:withCompletionHandler:] completion");
191
+ [CallingxLogBridge pushDebug:@"[pushRegistry:didReceiveIncomingVoIPPushWithPayload:metadata:withCompletionHandler:] completion"];
200
192
  }
201
193
  #endif
202
194
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/react-native-callingx",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",
@@ -61,7 +61,7 @@
61
61
  "devDependencies": {
62
62
  "@react-native-community/cli": "20.1.3",
63
63
  "@react-native/babel-preset": "0.85.3",
64
- "@stream-io/react-native-webrtc": "137.2.0",
64
+ "@stream-io/react-native-webrtc": "145.0.0",
65
65
  "@types/react": "^19.2.15",
66
66
  "del-cli": "^6.0.0",
67
67
  "react": "19.2.3",
@@ -72,7 +72,7 @@
72
72
  "peerDependencies": {
73
73
  "@react-native-firebase/app": ">=23.0.0",
74
74
  "@react-native-firebase/messaging": ">=23.0.0",
75
- "@stream-io/react-native-webrtc": ">=137.1.2",
75
+ "@stream-io/react-native-webrtc": "^145.0.0",
76
76
  "react": "*",
77
77
  "react-native": "*"
78
78
  },
@@ -48,11 +48,14 @@ export interface Spec extends TurboModule {
48
48
  getInitialEvents(): Array<{
49
49
  eventName: string;
50
50
  params: {
51
- callId: string;
51
+ callId?: string;
52
52
  cause?: string;
53
53
  muted?: boolean;
54
54
  hold?: boolean;
55
55
  source?: string;
56
+ phase?: string;
57
+ reason?: string;
58
+ shouldResume?: boolean;
56
59
  };
57
60
  }>;
58
61
 
@@ -147,10 +150,14 @@ export interface Spec extends TurboModule {
147
150
  readonly onNewEvent: EventEmitter<{
148
151
  eventName: string;
149
152
  params: {
150
- callId: string;
153
+ callId?: string;
151
154
  cause?: string;
152
155
  muted?: boolean;
153
156
  hold?: boolean;
157
+ source?: string;
158
+ phase?: string;
159
+ reason?: string;
160
+ shouldResume?: boolean;
154
161
  };
155
162
  }>;
156
163
 
package/src/types.ts CHANGED
@@ -292,14 +292,12 @@ export type InfoDisplayOptions = {
292
292
  };
293
293
 
294
294
  export type EventData = {
295
- eventName: EventName;
296
- params: EventParams[EventName];
297
- };
295
+ [K in EventName]: { eventName: K; params: EventParams[K] };
296
+ }[EventName];
298
297
 
299
298
  export type VoipEventData = {
300
- eventName: VoipEventName;
301
- params: VoipEventParams[VoipEventName];
302
- };
299
+ [K in VoipEventName]: { eventName: K; params: VoipEventParams[K] };
300
+ }[VoipEventName];
303
301
 
304
302
  export type EventName =
305
303
  | 'answerCall'
@@ -307,11 +305,19 @@ export type EventName =
307
305
  | 'didDisplayIncomingCall'
308
306
  | 'didToggleHoldCallAction'
309
307
  | 'didChangeAudioRoute'
308
+ | 'didAudioInterruption'
310
309
  | 'didReceiveStartCallAction'
311
310
  | 'didPerformSetMutedCallAction'
312
311
  | 'didActivateAudioSession'
313
312
  | 'didDeactivateAudioSession';
314
313
 
314
+ export type IOSAudioInterruptionEvent = {
315
+ source: 'callingx';
316
+ phase: 'began' | 'ended';
317
+ reason?: 'default' | 'builtInMicMuted' | 'routeDisconnected' | (string & {});
318
+ shouldResume?: boolean;
319
+ };
320
+
315
321
  export type EventParams = {
316
322
  answerCall: {
317
323
  callId: string;
@@ -337,6 +343,7 @@ export type EventParams = {
337
343
  callId: string;
338
344
  output: string;
339
345
  };
346
+ didAudioInterruption: IOSAudioInterruptionEvent;
340
347
  didReceiveStartCallAction: {
341
348
  callId: string;
342
349
  };