@telnyx/react-voice-commons-sdk 0.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 (73) hide show
  1. package/TelnyxVoiceCommons.podspec +32 -0
  2. package/ios/CallKitBridge.m +44 -0
  3. package/ios/CallKitBridge.swift +879 -0
  4. package/ios/README.md +211 -0
  5. package/ios/VoicePnBridge.m +31 -0
  6. package/ios/VoicePnBridge.swift +87 -0
  7. package/lib/callkit/callkit-coordinator.d.ts +126 -0
  8. package/lib/callkit/callkit-coordinator.js +728 -0
  9. package/lib/callkit/callkit.d.ts +49 -0
  10. package/lib/callkit/callkit.js +262 -0
  11. package/lib/callkit/index.d.ts +4 -0
  12. package/lib/callkit/index.js +15 -0
  13. package/lib/callkit/use-callkit-coordinator.d.ts +21 -0
  14. package/lib/callkit/use-callkit-coordinator.js +53 -0
  15. package/lib/callkit/use-callkit.d.ts +28 -0
  16. package/lib/callkit/use-callkit.js +279 -0
  17. package/lib/context/TelnyxVoiceContext.d.ts +18 -0
  18. package/lib/context/TelnyxVoiceContext.js +18 -0
  19. package/lib/hooks/use-callkit-coordinator.d.ts +13 -0
  20. package/lib/hooks/use-callkit-coordinator.js +48 -0
  21. package/lib/hooks/useAppReadyNotifier.d.ts +9 -0
  22. package/lib/hooks/useAppReadyNotifier.js +25 -0
  23. package/lib/hooks/useAppStateHandler.d.ts +16 -0
  24. package/lib/hooks/useAppStateHandler.js +105 -0
  25. package/lib/index.d.ts +24 -0
  26. package/lib/index.js +66 -0
  27. package/lib/internal/CallKitHandler.d.ts +17 -0
  28. package/lib/internal/CallKitHandler.js +110 -0
  29. package/lib/internal/callkit-manager.d.ts +69 -0
  30. package/lib/internal/callkit-manager.js +326 -0
  31. package/lib/internal/calls/call-state-controller.d.ts +92 -0
  32. package/lib/internal/calls/call-state-controller.js +294 -0
  33. package/lib/internal/session/session-manager.d.ts +87 -0
  34. package/lib/internal/session/session-manager.js +385 -0
  35. package/lib/internal/user-defaults-helpers.d.ts +10 -0
  36. package/lib/internal/user-defaults-helpers.js +69 -0
  37. package/lib/internal/voice-pn-bridge.d.ts +14 -0
  38. package/lib/internal/voice-pn-bridge.js +5 -0
  39. package/lib/models/call-state.d.ts +61 -0
  40. package/lib/models/call-state.js +87 -0
  41. package/lib/models/call.d.ts +145 -0
  42. package/lib/models/call.js +372 -0
  43. package/lib/models/config.d.ts +64 -0
  44. package/lib/models/config.js +92 -0
  45. package/lib/models/connection-state.d.ts +34 -0
  46. package/lib/models/connection-state.js +50 -0
  47. package/lib/telnyx-voice-app.d.ts +48 -0
  48. package/lib/telnyx-voice-app.js +486 -0
  49. package/lib/telnyx-voip-client.d.ts +184 -0
  50. package/lib/telnyx-voip-client.js +386 -0
  51. package/package.json +104 -0
  52. package/src/callkit/callkit-coordinator.ts +846 -0
  53. package/src/callkit/callkit.ts +322 -0
  54. package/src/callkit/index.ts +4 -0
  55. package/src/callkit/use-callkit.ts +345 -0
  56. package/src/context/TelnyxVoiceContext.tsx +33 -0
  57. package/src/hooks/use-callkit-coordinator.ts +60 -0
  58. package/src/hooks/useAppReadyNotifier.ts +25 -0
  59. package/src/hooks/useAppStateHandler.ts +134 -0
  60. package/src/index.ts +56 -0
  61. package/src/internal/CallKitHandler.tsx +149 -0
  62. package/src/internal/callkit-manager.ts +335 -0
  63. package/src/internal/calls/call-state-controller.ts +384 -0
  64. package/src/internal/session/session-manager.ts +467 -0
  65. package/src/internal/user-defaults-helpers.ts +58 -0
  66. package/src/internal/voice-pn-bridge.ts +18 -0
  67. package/src/models/call-state.ts +98 -0
  68. package/src/models/call.ts +388 -0
  69. package/src/models/config.ts +125 -0
  70. package/src/models/connection-state.ts +50 -0
  71. package/src/telnyx-voice-app.tsx +690 -0
  72. package/src/telnyx-voip-client.ts +475 -0
  73. package/src/types/telnyx-sdk.d.ts +79 -0
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TelnyxConnectionState = void 0;
4
+ exports.isTelnyxConnectionState = isTelnyxConnectionState;
5
+ exports.canMakeCalls = canMakeCalls;
6
+ exports.isConnected = isConnected;
7
+ exports.isTransitioning = isTransitioning;
8
+ /**
9
+ * Represents the connection state to the Telnyx platform.
10
+ *
11
+ * This enum provides a simplified view of the connection status,
12
+ * abstracting away the complexity of the underlying WebSocket states.
13
+ */
14
+ var TelnyxConnectionState;
15
+ (function (TelnyxConnectionState) {
16
+ /** Initial state before any connection attempt */
17
+ TelnyxConnectionState["DISCONNECTED"] = "DISCONNECTED";
18
+ /** Attempting to establish connection */
19
+ TelnyxConnectionState["CONNECTING"] = "CONNECTING";
20
+ /** Successfully connected and authenticated */
21
+ TelnyxConnectionState["CONNECTED"] = "CONNECTED";
22
+ /** Connection lost, attempting to reconnect */
23
+ TelnyxConnectionState["RECONNECTING"] = "RECONNECTING";
24
+ /** Connection failed or authentication error */
25
+ TelnyxConnectionState["ERROR"] = "ERROR";
26
+ })(TelnyxConnectionState || (exports.TelnyxConnectionState = TelnyxConnectionState = {}));
27
+ /**
28
+ * Type guard to check if a value is a valid TelnyxConnectionState
29
+ */
30
+ function isTelnyxConnectionState(value) {
31
+ return Object.values(TelnyxConnectionState).includes(value);
32
+ }
33
+ /**
34
+ * Helper function to determine if the connection state allows making calls
35
+ */
36
+ function canMakeCalls(state) {
37
+ return state === TelnyxConnectionState.CONNECTED;
38
+ }
39
+ /**
40
+ * Helper function to determine if the connection state indicates an active connection
41
+ */
42
+ function isConnected(state) {
43
+ return state === TelnyxConnectionState.CONNECTED;
44
+ }
45
+ /**
46
+ * Helper function to determine if the connection is in a transitional state
47
+ */
48
+ function isTransitioning(state) {
49
+ return state === TelnyxConnectionState.CONNECTING || state === TelnyxConnectionState.RECONNECTING;
50
+ }
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { AppStateStatus } from 'react-native';
3
+ import { TelnyxVoipClient } from './telnyx-voip-client';
4
+ /**
5
+ * Configuration options for TelnyxVoiceApp
6
+ */
7
+ export interface TelnyxVoiceAppOptions {
8
+ /** The TelnyxVoipClient instance to manage */
9
+ voipClient: TelnyxVoipClient;
10
+ /** Optional callback when push notification processing starts */
11
+ onPushNotificationProcessingStarted?: () => void;
12
+ /** Optional callback when push notification processing completes */
13
+ onPushNotificationProcessingCompleted?: () => void;
14
+ /** Optional callback for additional background/foreground handling */
15
+ onAppStateChanged?: (state: AppStateStatus) => void;
16
+ /** Whether to enable automatic login/reconnection (default: true) */
17
+ enableAutoReconnect?: boolean;
18
+ /** Whether to skip web platform for background detection (default: true) */
19
+ skipWebBackgroundDetection?: boolean;
20
+ /** Enable debug logging */
21
+ debug?: boolean;
22
+ }
23
+ /**
24
+ * Props for the TelnyxVoiceApp component
25
+ */
26
+ export interface TelnyxVoiceAppProps extends TelnyxVoiceAppOptions {
27
+ /** The child components to render */
28
+ children: React.ReactNode;
29
+ }
30
+ /**
31
+ * Interface for the TelnyxVoiceApp component with static methods
32
+ */
33
+ export interface TelnyxVoiceAppComponent extends React.FC<TelnyxVoiceAppProps> {
34
+ initializeAndCreate: (options: {
35
+ voipClient: TelnyxVoipClient;
36
+ children: React.ReactNode;
37
+ backgroundMessageHandler?: (message: any) => Promise<void>;
38
+ onPushNotificationProcessingStarted?: () => void;
39
+ onPushNotificationProcessingCompleted?: () => void;
40
+ onAppStateChanged?: (state: AppStateStatus) => void;
41
+ enableAutoReconnect?: boolean;
42
+ skipWebBackgroundDetection?: boolean;
43
+ debug?: boolean;
44
+ }) => Promise<React.ComponentType>;
45
+ handleBackgroundPush: (message: any) => Promise<void>;
46
+ }
47
+ export declare const TelnyxVoiceApp: TelnyxVoiceAppComponent;
48
+ export default TelnyxVoiceApp;
@@ -0,0 +1,486 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TelnyxVoiceApp = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const react_native_1 = require("react-native");
7
+ const telnyx_voip_client_1 = require("./telnyx-voip-client");
8
+ const connection_state_1 = require("./models/connection-state");
9
+ const TelnyxVoiceContext_1 = require("./context/TelnyxVoiceContext");
10
+ /**
11
+ * A comprehensive wrapper component that handles all Telnyx SDK lifecycle management.
12
+ *
13
+ * This component automatically handles:
14
+ * - Push notification initialization from terminated state
15
+ * - Background/foreground lifecycle detection and auto-reconnection
16
+ * - Login state management with automatic reconnection
17
+ * - CallKit integration preparation
18
+ *
19
+ * Simply wrap your main app component with this to get full Telnyx functionality:
20
+ * ```tsx
21
+ * <TelnyxVoiceApp voipClient={myVoipClient}>
22
+ * <MyApp />
23
+ * </TelnyxVoiceApp>
24
+ * ```
25
+ */
26
+ const TelnyxVoiceAppComponent = ({ voipClient, children, onPushNotificationProcessingStarted, onPushNotificationProcessingCompleted, onAppStateChanged, enableAutoReconnect = true, skipWebBackgroundDetection = true, debug = false, }) => {
27
+ // State management
28
+ const [processingPushOnLaunch, setProcessingPushOnLaunch] = (0, react_1.useState)(false);
29
+ const [isHandlingForegroundCall, setIsHandlingForegroundCall] = (0, react_1.useState)(false);
30
+ const [currentConnectionState, setCurrentConnectionState] = (0, react_1.useState)(voipClient.currentConnectionState);
31
+ // Refs for tracking state
32
+ const appStateRef = (0, react_1.useRef)(react_native_1.AppState.currentState);
33
+ const backgroundDetectorIgnore = (0, react_1.useRef)(false);
34
+ // Static background client instance for singleton pattern
35
+ const backgroundClientRef = (0, react_1.useRef)(null);
36
+ const log = (0, react_1.useCallback)((message, ...args) => {
37
+ if (debug) {
38
+ console.log(`[TelnyxVoiceApp] ${message}`, ...args);
39
+ }
40
+ }, [debug]);
41
+ // Handle app state changes
42
+ const handleAppStateChange = (0, react_1.useCallback)(async (nextAppState) => {
43
+ const previousAppState = appStateRef.current;
44
+ appStateRef.current = nextAppState;
45
+ log(`App state changed from ${previousAppState} to ${nextAppState}`);
46
+ log(`Background detector ignore flag: ${backgroundDetectorIgnore.current}`);
47
+ log(`Handling foreground call: ${isHandlingForegroundCall}`);
48
+ // Call optional user callback first
49
+ onAppStateChanged?.(nextAppState);
50
+ // Only handle background disconnection when actually transitioning from active to background
51
+ // Don't disconnect on background-to-background transitions (e.g., during CallKit operations)
52
+ if ((nextAppState === 'background' || nextAppState === 'inactive') &&
53
+ previousAppState === 'active') {
54
+ log(`App transitioned from ${previousAppState} to ${nextAppState} - handling backgrounding`);
55
+ await handleAppBackgrounded();
56
+ }
57
+ else if (nextAppState === 'background' || nextAppState === 'inactive') {
58
+ log(`App state is ${nextAppState} but was already ${previousAppState} - skipping background handling`);
59
+ }
60
+ // Always check for push notifications when app becomes active (regardless of auto-reconnect setting)
61
+ if (nextAppState === 'active' && previousAppState !== 'active') {
62
+ log('App became active - checking for push notifications');
63
+ await checkForInitialPushNotification(true); // Pass true for fromAppResume
64
+ }
65
+ // Only handle auto-reconnection if auto-reconnect is enabled
66
+ if (enableAutoReconnect && nextAppState === 'active' && previousAppState !== 'active') {
67
+ await handleAppResumed();
68
+ }
69
+ }, [enableAutoReconnect, onAppStateChanged, isHandlingForegroundCall, log]);
70
+ // Handle app going to background - disconnect like the old implementation
71
+ const handleAppBackgrounded = (0, react_1.useCallback)(async () => {
72
+ // Check if we should ignore background detection (e.g., during active calls)
73
+ if (backgroundDetectorIgnore.current || isHandlingForegroundCall) {
74
+ log('Background detector ignore flag set or handling foreground call - skipping disconnection');
75
+ return;
76
+ }
77
+ // Check if there are any active calls that should prevent disconnection
78
+ const activeCalls = voipClient.currentCalls;
79
+ const hasOngoingCall = activeCalls.length > 0 &&
80
+ activeCalls.some((call) => call.currentState === 'ACTIVE' ||
81
+ call.currentState === 'HELD' ||
82
+ call.currentState === 'RINGING' ||
83
+ call.currentState === 'CONNECTING');
84
+ // Also check if there's an incoming call from push notification being processed
85
+ let isCallFromPush = false;
86
+ if (react_native_1.Platform.OS === 'ios') {
87
+ try {
88
+ const { callKitCoordinator } = require('./callkit/callkit-coordinator');
89
+ isCallFromPush = callKitCoordinator.getIsCallFromPush();
90
+ }
91
+ catch (e) {
92
+ log('Error checking isCallFromPush:', e);
93
+ }
94
+ }
95
+ if (hasOngoingCall || isCallFromPush) {
96
+ log('Active calls or push call detected - skipping background disconnection', {
97
+ callCount: activeCalls.length,
98
+ hasOngoingCall,
99
+ isCallFromPush,
100
+ callStates: activeCalls.map((call) => ({
101
+ callId: call.callId,
102
+ currentState: call.currentState,
103
+ destination: call.destination,
104
+ })),
105
+ });
106
+ return;
107
+ }
108
+ log('App backgrounded - disconnecting (matching old BackgroundDetector behavior)');
109
+ try {
110
+ // Always disconnect when backgrounded (matches old implementation)
111
+ await voipClient.logout();
112
+ log('Successfully disconnected on background');
113
+ }
114
+ catch (e) {
115
+ log('Error disconnecting on background:', e);
116
+ }
117
+ }, [voipClient, isHandlingForegroundCall, log]);
118
+ // Handle app resuming from background
119
+ const handleAppResumed = (0, react_1.useCallback)(async () => {
120
+ log('App resumed - checking reconnection needs');
121
+ // IMPORTANT: Check for push notifications first when resuming from background
122
+ // This handles the case where the user accepted a call while the app was backgrounded
123
+ await checkForInitialPushNotification(true); // Pass true for fromAppResume
124
+ // If we're ignoring (e.g., from push call) or handling foreground call, don't auto-reconnect
125
+ if (backgroundDetectorIgnore.current || isHandlingForegroundCall) {
126
+ log('Background detector ignore flag set or handling foreground call - skipping reconnection');
127
+ return;
128
+ }
129
+ // iOS-specific: If push notification handling just initiated a connection,
130
+ // skip auto-reconnection to prevent double login
131
+ if (react_native_1.Platform.OS === 'ios') {
132
+ // Check if connection state changed after push processing
133
+ const connectionStateAfterPush = voipClient.currentConnectionState;
134
+ if (connectionStateAfterPush === connection_state_1.TelnyxConnectionState.CONNECTING ||
135
+ connectionStateAfterPush === connection_state_1.TelnyxConnectionState.CONNECTED) {
136
+ log(`iOS: Push handling initiated connection (${connectionStateAfterPush}), skipping auto-reconnection`);
137
+ return;
138
+ }
139
+ }
140
+ // Check current connection state and reconnect if needed
141
+ const currentState = voipClient.currentConnectionState;
142
+ log(`Current connection state: ${currentState}`);
143
+ // If we're not connected and have stored credentials, attempt reconnection
144
+ if (currentState !== connection_state_1.TelnyxConnectionState.CONNECTED) {
145
+ await attemptAutoReconnection();
146
+ }
147
+ }, [voipClient, isHandlingForegroundCall, log]);
148
+ // Attempt to reconnect using stored credentials
149
+ const attemptAutoReconnection = (0, react_1.useCallback)(async () => {
150
+ try {
151
+ log('Attempting auto-reconnection...');
152
+ // Try to get stored config and reconnect
153
+ const success = await voipClient.loginFromStoredConfig();
154
+ log(`Auto-reconnection ${success ? 'successful' : 'failed'}`);
155
+ // If auto-reconnection fails, redirect to login screen
156
+ if (!success) {
157
+ log('Auto-reconnection failed - redirecting to login screen');
158
+ // Import router dynamically to avoid circular dependency issues
159
+ const { router } = require('expo-router');
160
+ // Small delay to ensure state is settled
161
+ setTimeout(() => {
162
+ router.replace('/');
163
+ }, 100);
164
+ }
165
+ }
166
+ catch (e) {
167
+ log('Auto-reconnection error:', e);
168
+ // On error, also redirect to login
169
+ log('Auto-reconnection error - redirecting to login screen');
170
+ const { router } = require('expo-router');
171
+ setTimeout(() => {
172
+ router.replace('/');
173
+ }, 100);
174
+ }
175
+ }, [voipClient, log]);
176
+ // Check for initial push notification when app launches
177
+ const checkForInitialPushNotification = (0, react_1.useCallback)(async (fromAppResume = false) => {
178
+ log(`checkForInitialPushNotification called${fromAppResume ? ' (from app resume)' : ''}`);
179
+ if (processingPushOnLaunch && !fromAppResume) {
180
+ log('Already processing push, returning early');
181
+ return;
182
+ }
183
+ // Only set the flag if this is not from app resume to allow resume processing
184
+ if (!fromAppResume) {
185
+ setProcessingPushOnLaunch(true);
186
+ }
187
+ onPushNotificationProcessingStarted?.();
188
+ try {
189
+ let pushData = null;
190
+ // Try to get push data from the native layer using our VoicePnBridge
191
+ try {
192
+ // Import the native bridge module dynamically
193
+ const { NativeModules } = require('react-native');
194
+ const VoicePnBridge = NativeModules.VoicePnBridge;
195
+ if (VoicePnBridge) {
196
+ log('Checking for pending push actions via VoicePnBridge');
197
+ const pendingAction = await VoicePnBridge.getPendingPushAction();
198
+ log('Raw pending action response:', pendingAction);
199
+ if (pendingAction && pendingAction.action != null && pendingAction.metadata != null) {
200
+ log('Found pending push action:', pendingAction);
201
+ // Parse the metadata if it's a string
202
+ let metadata = pendingAction.metadata;
203
+ if (typeof metadata === 'string') {
204
+ try {
205
+ // First try parsing as JSON
206
+ metadata = JSON.parse(metadata);
207
+ log('Parsed metadata as JSON:', metadata);
208
+ }
209
+ catch (e) {
210
+ // If JSON parsing fails, try parsing Android key-value format
211
+ // Format: "{call_id=value, action=value}"
212
+ log('JSON parse failed, trying Android key-value format');
213
+ try {
214
+ const cleanedString = metadata.replace(/[{}]/g, '').trim();
215
+ const pairs = cleanedString.split(',').map((pair) => pair.trim());
216
+ const parsed = {};
217
+ for (const pair of pairs) {
218
+ const [key, value] = pair.split('=').map((s) => s.trim());
219
+ if (key && value) {
220
+ parsed[key] = value;
221
+ }
222
+ }
223
+ metadata = parsed;
224
+ log('Parsed metadata as Android key-value format:', metadata);
225
+ }
226
+ catch (parseError) {
227
+ log('Failed to parse metadata in any format, using as-is:', parseError);
228
+ }
229
+ }
230
+ }
231
+ // Create push data structure that matches what the VoIP client expects
232
+ pushData = {
233
+ action: pendingAction.action,
234
+ metadata: metadata,
235
+ from_notification: true,
236
+ };
237
+ // Clear the pending action so it doesn't get processed again
238
+ await VoicePnBridge.clearPendingPushAction();
239
+ log('Cleared pending push action after retrieval');
240
+ }
241
+ else {
242
+ log('No pending push actions found');
243
+ }
244
+ }
245
+ else {
246
+ log('VoicePnBridge not available - this is expected on iOS');
247
+ }
248
+ }
249
+ catch (bridgeError) {
250
+ log('Error accessing VoicePnBridge:', bridgeError);
251
+ }
252
+ // Process the push notification if found
253
+ if (pushData) {
254
+ log('Processing initial push notification...');
255
+ // Check if we're already connected and handling a push - prevent duplicate processing
256
+ const isConnected = voipClient.currentConnectionState === connection_state_1.TelnyxConnectionState.CONNECTED;
257
+ if (isConnected) {
258
+ log('SKIPPING - Already connected, preventing duplicate processing');
259
+ // Clear the stored data since we're already handling it
260
+ // TODO: Implement clearPushMetaData
261
+ return;
262
+ }
263
+ // Set flags to prevent auto-reconnection during push call
264
+ setIsHandlingForegroundCall(true);
265
+ backgroundDetectorIgnore.current = true;
266
+ log(`Background detector ignore set to: true at ${new Date().toISOString()}`);
267
+ log(`Foreground call handling flag set to: true at ${new Date().toISOString()}`);
268
+ // Dispose any existing background client to prevent conflicts
269
+ disposeBackgroundClient();
270
+ // Handle the push notification using platform-specific approach
271
+ if (react_native_1.Platform.OS === 'ios') {
272
+ // On iOS, coordinate with CallKit by notifying the coordinator about the push
273
+ const { callKitCoordinator } = require('./callkit/callkit-coordinator');
274
+ // Extract call_id from nested metadata structure to use as CallKit UUID
275
+ const callId = pushData.metadata?.metadata?.call_id;
276
+ if (callId) {
277
+ log('Notifying CallKit coordinator about push notification:', callId);
278
+ await callKitCoordinator.handleCallKitPushReceived(callId, {
279
+ callData: { source: 'push_notification' },
280
+ pushData: pushData,
281
+ });
282
+ }
283
+ else {
284
+ log('No call_id found in push data, falling back to direct handling');
285
+ await voipClient.handlePushNotification(pushData);
286
+ }
287
+ }
288
+ else {
289
+ // On other platforms, handle push notification directly
290
+ await voipClient.handlePushNotification(pushData);
291
+ }
292
+ log('Initial push notification processed');
293
+ log('Cleared stored push data to prevent duplicate processing');
294
+ // Note: isHandlingForegroundCall will be reset when calls.length becomes 0
295
+ // This prevents premature disconnection during CallKit answer flow
296
+ }
297
+ else {
298
+ log('No initial push data found');
299
+ }
300
+ }
301
+ catch (e) {
302
+ log('Error processing initial push notification:', e);
303
+ // Reset flags on error
304
+ setIsHandlingForegroundCall(false);
305
+ }
306
+ finally {
307
+ // Always reset the processing flag - it should not remain stuck
308
+ setProcessingPushOnLaunch(false);
309
+ onPushNotificationProcessingCompleted?.();
310
+ }
311
+ }, [
312
+ processingPushOnLaunch,
313
+ voipClient,
314
+ onPushNotificationProcessingStarted,
315
+ onPushNotificationProcessingCompleted,
316
+ log,
317
+ ]);
318
+ // Dispose background client instance when no longer needed
319
+ const disposeBackgroundClient = (0, react_1.useCallback)(() => {
320
+ if (backgroundClientRef.current) {
321
+ log('Disposing background client instance');
322
+ backgroundClientRef.current.dispose();
323
+ backgroundClientRef.current = null;
324
+ }
325
+ }, [log]);
326
+ // Create background client for push notification handling
327
+ const createBackgroundClient = (0, react_1.useCallback)(() => {
328
+ log('Creating background client instance');
329
+ const backgroundClient = (0, telnyx_voip_client_1.createBackgroundTelnyxVoipClient)({
330
+ debug,
331
+ });
332
+ return backgroundClient;
333
+ }, [debug, log]);
334
+ // Setup effect
335
+ (0, react_1.useEffect)(() => {
336
+ // Listen to connection state changes
337
+ const connectionStateSubscription = voipClient.connectionState$.subscribe((state) => {
338
+ setCurrentConnectionState(state);
339
+ // Just log connection changes, let the app handle navigation
340
+ log(`Connection state changed to: ${state}`);
341
+ });
342
+ // Listen to call changes to reset flags when no active calls
343
+ const callsSubscription = voipClient.calls$.subscribe((calls) => {
344
+ // Check if we should reset flags - only reset if:
345
+ // 1. No active WebRTC calls AND
346
+ // 2. No CallKit operations in progress (to prevent disconnection during CallKit answer flow)
347
+ const hasActiveWebRTCCalls = calls.length > 0;
348
+ let hasCallKitProcessing = false;
349
+ // Check CallKit processing calls only on iOS
350
+ if (react_native_1.Platform.OS === 'ios') {
351
+ try {
352
+ const { callKitCoordinator } = require('./callkit/callkit-coordinator');
353
+ hasCallKitProcessing = callKitCoordinator.hasProcessingCalls();
354
+ log(`CallKit processing check: hasProcessingCalls=${hasCallKitProcessing}`);
355
+ }
356
+ catch (e) {
357
+ log('Error checking CallKit processing calls:', e);
358
+ }
359
+ }
360
+ log(`Flag reset check: WebRTC calls=${calls.length}, CallKit processing=${hasCallKitProcessing}, isHandlingForegroundCall=${isHandlingForegroundCall}, backgroundDetectorIgnore=${backgroundDetectorIgnore.current}`);
361
+ if (!hasActiveWebRTCCalls &&
362
+ !hasCallKitProcessing &&
363
+ (isHandlingForegroundCall || backgroundDetectorIgnore.current)) {
364
+ log(`No active calls and no CallKit processing - resetting ignore flags at ${new Date().toISOString()}`);
365
+ setIsHandlingForegroundCall(false);
366
+ backgroundDetectorIgnore.current = false;
367
+ }
368
+ else if (!hasActiveWebRTCCalls && hasCallKitProcessing) {
369
+ log(`No WebRTC calls but CallKit operations in progress - keeping ignore flags active at ${new Date().toISOString()}`);
370
+ }
371
+ else if (hasActiveWebRTCCalls) {
372
+ log(`WebRTC calls active - keeping ignore flags active at ${new Date().toISOString()}`);
373
+ }
374
+ // Also reset processingPushOnLaunch if no calls are active
375
+ // This ensures the flag doesn't get stuck after call ends
376
+ if (calls.length === 0 && processingPushOnLaunch) {
377
+ log('No active calls - resetting processing push flag');
378
+ setProcessingPushOnLaunch(false);
379
+ }
380
+ });
381
+ // Add app state listener if not skipping web background detection or not on web
382
+ // AND if app state management is enabled in the client options
383
+ let appStateSubscription = null;
384
+ if ((!skipWebBackgroundDetection || react_native_1.Platform.OS !== 'web') &&
385
+ voipClient.options.enableAppStateManagement) {
386
+ appStateSubscription = react_native_1.AppState.addEventListener('change', handleAppStateChange);
387
+ }
388
+ // Handle initial push notification if app was launched from terminated state
389
+ // Only check if we're not already processing to prevent infinite loops
390
+ const timeoutId = setTimeout(() => {
391
+ if (!processingPushOnLaunch) {
392
+ checkForInitialPushNotification();
393
+ }
394
+ }, 100);
395
+ // Cleanup function
396
+ return () => {
397
+ connectionStateSubscription.unsubscribe();
398
+ callsSubscription.unsubscribe();
399
+ if (appStateSubscription) {
400
+ appStateSubscription.remove();
401
+ }
402
+ clearTimeout(timeoutId);
403
+ // Clean up background client instance
404
+ disposeBackgroundClient();
405
+ };
406
+ }, [
407
+ voipClient,
408
+ handleAppStateChange,
409
+ disposeBackgroundClient,
410
+ skipWebBackgroundDetection,
411
+ isHandlingForegroundCall,
412
+ log,
413
+ ]);
414
+ // Simply return the children wrapped in context provider - all lifecycle management is handled internally
415
+ return (0, jsx_runtime_1.jsx)(TelnyxVoiceContext_1.TelnyxVoiceProvider, { voipClient: voipClient, children: children });
416
+ };
417
+ /**
418
+ * Static factory method that handles all common SDK initialization boilerplate.
419
+ *
420
+ * This is the recommended way to initialize the Telnyx Voice SDK in your app.
421
+ * It ensures that push notifications, background handlers, and other dependencies are
422
+ * set up correctly before the app runs.
423
+ *
424
+ * This method:
425
+ * - Initializes push notification handling
426
+ * - Registers the background push notification handler
427
+ * - Returns a fully configured TelnyxVoiceApp component to be used in your app
428
+ *
429
+ * ## Usage:
430
+ * ```tsx
431
+ * const App = TelnyxVoiceApp.initializeAndCreate({
432
+ * voipClient: myVoipClient,
433
+ * backgroundHandler: backgroundHandler,
434
+ * children: <MyApp />,
435
+ * });
436
+ * ```
437
+ */
438
+ const initializeAndCreate = async (options) => {
439
+ const { voipClient, children, backgroundMessageHandler, onPushNotificationProcessingStarted, onPushNotificationProcessingCompleted, onAppStateChanged, enableAutoReconnect = true, skipWebBackgroundDetection = true, debug = false, } = options;
440
+ // Initialize push notification handling for Android
441
+ if (react_native_1.Platform.OS === 'android') {
442
+ // TODO: Initialize Firebase or other push notification service
443
+ if (debug) {
444
+ console.log('[TelnyxVoiceApp] Android push notification initialization needed');
445
+ }
446
+ }
447
+ // Register background message handler if provided
448
+ if (backgroundMessageHandler) {
449
+ // TODO: Register the background message handler with the push notification service
450
+ if (debug) {
451
+ console.log('[TelnyxVoiceApp] Background message handler registration needed');
452
+ }
453
+ }
454
+ if (debug) {
455
+ console.log('[TelnyxVoiceApp] SDK initialization complete');
456
+ }
457
+ // Return a component that renders TelnyxVoiceApp with the provided options
458
+ return () => ((0, jsx_runtime_1.jsx)(exports.TelnyxVoiceApp, { voipClient: voipClient, onPushNotificationProcessingStarted: onPushNotificationProcessingStarted, onPushNotificationProcessingCompleted: onPushNotificationProcessingCompleted, onAppStateChanged: onAppStateChanged, enableAutoReconnect: enableAutoReconnect, skipWebBackgroundDetection: skipWebBackgroundDetection, debug: debug, children: children }));
459
+ };
460
+ /**
461
+ * Handles background push notifications in the background isolate.
462
+ * This should be called from your background message handler.
463
+ */
464
+ const handleBackgroundPush = async (message) => {
465
+ console.log('[TelnyxVoiceApp] Background push received:', message);
466
+ try {
467
+ // TODO: Initialize push notification service in isolate if needed
468
+ // Use singleton pattern for background client to prevent multiple instances
469
+ let backgroundClient = (0, telnyx_voip_client_1.createBackgroundTelnyxVoipClient)({
470
+ debug: true,
471
+ });
472
+ await backgroundClient.handlePushNotification(message);
473
+ console.log('[TelnyxVoiceApp] Background push processed successfully');
474
+ // Clean up the background client
475
+ backgroundClient.dispose();
476
+ }
477
+ catch (e) {
478
+ console.log('[TelnyxVoiceApp] Error processing background push:', e);
479
+ }
480
+ };
481
+ // Create the component with static methods
482
+ exports.TelnyxVoiceApp = Object.assign(TelnyxVoiceAppComponent, {
483
+ initializeAndCreate,
484
+ handleBackgroundPush,
485
+ });
486
+ exports.default = exports.TelnyxVoiceApp;