@telnyx/react-voice-commons-sdk 0.4.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +28 -7
  3. package/android/src/main/java/com/telnyx/react_voice_commons/CallForegroundService.kt +16 -2
  4. package/android/src/main/java/com/telnyx/react_voice_commons/TelnyxFirebaseMessagingService.kt +16 -2
  5. package/android/src/main/java/com/telnyx/react_voice_commons/TelnyxNotificationHelper.kt +345 -24
  6. package/android/src/main/java/com/telnyx/react_voice_commons/VoicePnBridgeModule.kt +74 -1
  7. package/android/src/main/java/com/telnyx/react_voice_commons/VoicePnManager.kt +36 -1
  8. package/ios/CallKitBridge.m +20 -1
  9. package/ios/CallKitBridge.swift +935 -60
  10. package/ios/README.md +2 -2
  11. package/ios/VoicePnBridge.m +21 -1
  12. package/ios/VoicePnBridge.swift +62 -1
  13. package/lib/callkit/callkit-coordinator.d.ts +39 -3
  14. package/lib/callkit/callkit-coordinator.js +561 -148
  15. package/lib/callkit/callkit.d.ts +7 -0
  16. package/lib/callkit/callkit.js +83 -11
  17. package/lib/hooks/useAppStateHandler.js +6 -2
  18. package/lib/index.d.ts +1 -0
  19. package/lib/index.js +9 -0
  20. package/lib/internal/CallKitHandler.d.ts +3 -0
  21. package/lib/internal/CallKitHandler.js +52 -46
  22. package/lib/internal/calls/call-state-controller.d.ts +39 -1
  23. package/lib/internal/calls/call-state-controller.js +152 -64
  24. package/lib/internal/session/session-manager.d.ts +16 -1
  25. package/lib/internal/session/session-manager.js +229 -58
  26. package/lib/internal/voice-pn-bridge.d.ts +44 -0
  27. package/lib/internal/voice-pn-bridge.js +94 -0
  28. package/lib/models/call.js +35 -0
  29. package/lib/models/config.d.ts +18 -0
  30. package/lib/models/config.js +2 -0
  31. package/lib/telnyx-voice-app.js +85 -43
  32. package/lib/telnyx-voip-client.d.ts +48 -5
  33. package/lib/telnyx-voip-client.js +276 -22
  34. package/package.json +7 -5
  35. package/src/callkit/callkit-coordinator.ts +642 -160
  36. package/src/callkit/callkit.ts +110 -12
  37. package/src/hooks/useAppStateHandler.ts +7 -2
  38. package/src/index.ts +3 -0
  39. package/src/internal/CallKitHandler.tsx +60 -55
  40. package/src/internal/calls/call-state-controller.ts +173 -74
  41. package/src/internal/session/session-manager.ts +277 -61
  42. package/src/internal/voice-pn-bridge.ts +111 -0
  43. package/src/models/call.ts +34 -0
  44. package/src/models/config.ts +18 -0
  45. package/src/telnyx-voice-app.tsx +96 -45
  46. package/src/telnyx-voip-client.ts +260 -24
  47. package/src/types/telnyx-sdk.d.ts +12 -1
package/ios/README.md CHANGED
@@ -62,7 +62,7 @@ class AppDelegate: ExpoAppDelegate, TelnyxVoiceAppDelegate {
62
62
  override func customCallKitConfiguration() -> CXProviderConfiguration {
63
63
  let configuration = CXProviderConfiguration(localizedName: "My App Voice")
64
64
  configuration.supportsVideo = false
65
- configuration.maximumCallGroups = 1
65
+ configuration.maximumCallGroups = 2
66
66
  configuration.maximumCallsPerCallGroup = 1
67
67
  configuration.supportedHandleTypes = [.phoneNumber, .generic]
68
68
  configuration.includesCallsInRecents = true
@@ -113,7 +113,7 @@ class AppDelegate: ExpoAppDelegate {
113
113
  private func setupCallKit() {
114
114
  let configuration = CXProviderConfiguration(localizedName: "Voice Call")
115
115
  configuration.supportsVideo = false
116
- configuration.maximumCallGroups = 1
116
+ configuration.maximumCallGroups = 2
117
117
  configuration.maximumCallsPerCallGroup = 1
118
118
  configuration.supportedHandleTypes = [.phoneNumber, .generic]
119
119
  configuration.includesCallsInRecents = true
@@ -22,10 +22,30 @@ RCT_EXTERN_METHOD(getPendingVoipPush:(RCTPromiseResolveBlock)resolve
22
22
  RCT_EXTERN_METHOD(clearPendingVoipPush:(RCTPromiseResolveBlock)resolve
23
23
  rejecter:(RCTPromiseRejectBlock)reject)
24
24
 
25
+ RCT_EXTERN_METHOD(getPendingCallKitAnswer:(RCTPromiseResolveBlock)resolve
26
+ rejecter:(RCTPromiseRejectBlock)reject)
27
+
28
+ RCT_EXTERN_METHOD(clearPendingCallKitAnswer:(RCTPromiseResolveBlock)resolve
29
+ rejecter:(RCTPromiseRejectBlock)reject)
30
+
25
31
  RCT_EXTERN_METHOD(getPendingVoipAction:(RCTPromiseResolveBlock)resolve
26
32
  rejecter:(RCTPromiseRejectBlock)reject)
27
33
 
28
34
  RCT_EXTERN_METHOD(clearPendingVoipAction:(RCTPromiseResolveBlock)resolve
29
35
  rejecter:(RCTPromiseRejectBlock)reject)
30
36
 
31
- @end
37
+ RCT_EXTERN_METHOD(setMissedCallNotificationsEnabled:(BOOL)enabled
38
+ resolver:(RCTPromiseResolveBlock)resolve
39
+ rejecter:(RCTPromiseRejectBlock)reject)
40
+
41
+ RCT_EXTERN_METHOD(getMissedCallNotificationsEnabled:(RCTPromiseResolveBlock)resolve
42
+ rejecter:(RCTPromiseRejectBlock)reject)
43
+
44
+ RCT_EXTERN_METHOD(setSpeakerEnabled:(BOOL)enabled
45
+ resolver:(RCTPromiseResolveBlock)resolve
46
+ rejecter:(RCTPromiseRejectBlock)reject)
47
+
48
+ RCT_EXTERN_METHOD(isSpeakerEnabled:(RCTPromiseResolveBlock)resolve
49
+ rejecter:(RCTPromiseRejectBlock)reject)
50
+
51
+ @end
@@ -1,5 +1,6 @@
1
1
  import Foundation
2
2
  import React
3
+ import AVFoundation
3
4
 
4
5
  @objc(VoicePnBridge)
5
6
  class VoicePnBridge: NSObject {
@@ -70,6 +71,21 @@ class VoicePnBridge: NSObject {
70
71
  resolve(true)
71
72
  }
72
73
 
74
+ @objc
75
+ func getPendingCallKitAnswer(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
76
+ let answer = UserDefaults.standard.string(forKey: "pending_callkit_answer")
77
+ NSLog("[VoicePnBridge] getPendingCallKitAnswer - answer: \(answer ?? "nil")")
78
+ resolve(answer)
79
+ }
80
+
81
+ @objc
82
+ func clearPendingCallKitAnswer(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
83
+ UserDefaults.standard.removeObject(forKey: "pending_callkit_answer")
84
+ UserDefaults.standard.synchronize()
85
+ NSLog("[VoicePnBridge] clearPendingCallKitAnswer - cleared")
86
+ resolve(true)
87
+ }
88
+
73
89
  @objc
74
90
  func getPendingVoipAction(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
75
91
  let pending = UserDefaults.standard.string(forKey: "pending_voip_action")
@@ -84,4 +100,49 @@ class VoicePnBridge: NSObject {
84
100
  NSLog("[VoicePnBridge] clearPendingVoipAction called. Cleared key pending_voip_action")
85
101
  resolve(true)
86
102
  }
87
- }
103
+
104
+ @objc
105
+ func setMissedCallNotificationsEnabled(_ enabled: Bool, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
106
+ UserDefaults.standard.set(enabled, forKey: TelnyxVoiceUserDefaultsKey.missedCallNotificationsEnabled)
107
+ UserDefaults.standard.synchronize()
108
+ NSLog("[VoicePnBridge] setMissedCallNotificationsEnabled called. enabled=\(enabled)")
109
+ resolve(true)
110
+ }
111
+
112
+ @objc
113
+ func getMissedCallNotificationsEnabled(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
114
+ if UserDefaults.standard.object(forKey: TelnyxVoiceUserDefaultsKey.missedCallNotificationsEnabled) == nil {
115
+ resolve(true)
116
+ return
117
+ }
118
+
119
+ resolve(UserDefaults.standard.bool(forKey: TelnyxVoiceUserDefaultsKey.missedCallNotificationsEnabled))
120
+ }
121
+
122
+ @objc
123
+ func setSpeakerEnabled(_ enabled: Bool, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
124
+ do {
125
+ let audioSession = AVAudioSession.sharedInstance()
126
+ // The Telnyx/WebRTC call setup is expected to configure a .playAndRecord-compatible
127
+ // session. Passing .none clears only the speaker override; accessories/session options
128
+ // can still decide the resulting output route.
129
+ try audioSession.overrideOutputAudioPort(enabled ? .speaker : .none)
130
+ let actualEnabled = audioSession.currentRoute.outputs.contains { output in
131
+ output.portType == .builtInSpeaker
132
+ }
133
+ NSLog("[VoicePnBridge] setSpeakerEnabled called. requested=\(enabled), actual=\(actualEnabled)")
134
+ resolve(actualEnabled)
135
+ } catch {
136
+ NSLog("[VoicePnBridge] setSpeakerEnabled failed. enabled=\(enabled), error=\(error)")
137
+ reject("SET_SPEAKER_ENABLED_ERROR", error.localizedDescription, error)
138
+ }
139
+ }
140
+
141
+ @objc
142
+ func isSpeakerEnabled(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
143
+ let isEnabled = AVAudioSession.sharedInstance().currentRoute.outputs.contains { output in
144
+ output.portType == .builtInSpeaker
145
+ }
146
+ resolve(isEnabled)
147
+ }
148
+ }
@@ -1,5 +1,5 @@
1
1
  import { Call } from '@telnyx/react-native-voice-sdk';
2
- import { TelnyxVoipClient } from '../telnyx-voip-client';
2
+ import type { TelnyxVoipClient } from '../telnyx-voip-client';
3
3
  /**
4
4
  * CallKit Coordinator - Manages the proper CallKit-first flow for iOS
5
5
  *
@@ -14,7 +14,12 @@ declare class CallKitCoordinator {
14
14
  private endedCalls;
15
15
  private connectedCalls;
16
16
  private isCallFromPush;
17
- private shouldAutoAnswerNextCall;
17
+ private pendingPushCallUUIDs;
18
+ private selectedCallKitUUID;
19
+ private restoreHeldCallPromise;
20
+ private pendingSwap;
21
+ private pendingHeldRequests;
22
+ private autoAnswerCallUUIDs;
18
23
  private voipClient;
19
24
  static getInstance(): CallKitCoordinator;
20
25
  private constructor();
@@ -36,6 +41,17 @@ declare class CallKitCoordinator {
36
41
  * Answer a call from the app UI (CallKit-first approach)
37
42
  */
38
43
  answerCallFromUI(call: Call): Promise<boolean>;
44
+ /**
45
+ * Change held state from app UI through CallKit, then wait for signaling and
46
+ * the corresponding CXSetHeldCallAction to complete.
47
+ */
48
+ setHeldFromUI(call: Call, isOnHold: boolean): Promise<boolean>;
49
+ /**
50
+ * Swap an active call with a held call through one CallKit transaction.
51
+ * The promise settles only after both corresponding WebRTC state changes
52
+ * have completed and their CallKit actions have been fulfilled.
53
+ */
54
+ swapCallsFromUI(activeCall: Call, heldCall: Call): Promise<boolean>;
39
55
  /**
40
56
  * End a call from the app UI (CallKit-first approach)
41
57
  */
@@ -44,6 +60,8 @@ declare class CallKitCoordinator {
44
60
  * Handle CallKit answer action (triggered by CallKit)
45
61
  */
46
62
  handleCallKitAnswer(callKitUUID: string, event?: any): Promise<void>;
63
+ /** Handle a UUID-targeted CallKit hold or resume action. */
64
+ handleCallKitHeld(callKitUUID: string, isOnHold: boolean): Promise<void>;
47
65
  /**
48
66
  * Handle CallKit end action (triggered by CallKit)
49
67
  */
@@ -55,8 +73,13 @@ declare class CallKitCoordinator {
55
73
  /**
56
74
  * Handle CallKit push received event
57
75
  * This allows us to coordinate between the push notification and any subsequent WebRTC calls
76
+ *
77
+ * Returns `true` when the push was accepted and call processing was started,
78
+ * or `false` when the push was filtered, rejected, or otherwise ignored so
79
+ * that callers can clean up any foreground-push lifecycle flags they set
80
+ * before calling this method.
58
81
  */
59
- handleCallKitPushReceived(callKitUUID: string, event?: any): Promise<void>;
82
+ handleCallKitPushReceived(callKitUUID: string, event?: any): Promise<boolean>;
60
83
  /**
61
84
  * Handle push notification answer - when user answers from CallKit but we don't have a WebRTC call yet
62
85
  * This is the iOS equivalent of the Android FCM handler
@@ -99,10 +122,23 @@ declare class CallKitCoordinator {
99
122
  * Helper method to clean up push notification state
100
123
  */
101
124
  private cleanupPushNotificationState;
125
+ private rejectUnregisteredIncomingCall;
126
+ private clearMatchingPendingVoipPush;
102
127
  /**
103
128
  * Get reference to the SDK client (for queuing actions when call doesn't exist yet)
104
129
  */
105
130
  private getSDKClient;
131
+ private normalizeUUID;
132
+ private actionKey;
133
+ private selectCall;
134
+ private isSelectedCall;
135
+ private recordPendingSwapAction;
136
+ private createPendingSwap;
137
+ private beginSwapRollback;
138
+ private restoreOriginalWebRTCStates;
139
+ private finishPendingSwap;
140
+ private finishPendingHeldRequest;
141
+ private restoreRemainingHeldCall;
106
142
  /**
107
143
  * Check if app is in background and disconnect client if no active calls
108
144
  */