@telnyx/react-voice-commons-sdk 0.1.2 → 0.1.4
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 +60 -0
- package/README.md +469 -483
- package/ios/CallKitBridge.swift +2 -7
- package/lib/callkit/callkit-coordinator.d.ts +110 -117
- package/lib/callkit/callkit-coordinator.js +664 -727
- package/lib/callkit/callkit.d.ts +41 -41
- package/lib/callkit/callkit.js +252 -242
- package/lib/callkit/index.js +15 -47
- package/lib/callkit/use-callkit.d.ts +19 -19
- package/lib/callkit/use-callkit.js +270 -310
- package/lib/context/TelnyxVoiceContext.d.ts +9 -9
- package/lib/context/TelnyxVoiceContext.js +10 -13
- package/lib/hooks/use-callkit-coordinator.d.ts +9 -17
- package/lib/hooks/use-callkit-coordinator.js +45 -50
- package/lib/hooks/useAppReadyNotifier.js +13 -15
- package/lib/hooks/useAppStateHandler.d.ts +6 -11
- package/lib/hooks/useAppStateHandler.js +95 -110
- package/lib/hooks/useNetworkStateHandler.d.ts +0 -0
- package/lib/hooks/useNetworkStateHandler.js +0 -0
- package/lib/index.d.ts +3 -21
- package/lib/index.js +50 -201
- package/lib/internal/CallKitHandler.d.ts +6 -6
- package/lib/internal/CallKitHandler.js +96 -104
- package/lib/internal/callkit-manager.d.ts +57 -57
- package/lib/internal/callkit-manager.js +299 -316
- package/lib/internal/calls/call-state-controller.d.ts +73 -86
- package/lib/internal/calls/call-state-controller.js +263 -307
- package/lib/internal/session/session-manager.d.ts +71 -75
- package/lib/internal/session/session-manager.js +360 -424
- package/lib/internal/user-defaults-helpers.js +49 -39
- package/lib/internal/voice-pn-bridge.d.ts +114 -12
- package/lib/internal/voice-pn-bridge.js +212 -5
- package/lib/models/call-state.d.ts +46 -44
- package/lib/models/call-state.js +70 -68
- package/lib/models/call.d.ts +161 -133
- package/lib/models/call.js +454 -382
- package/lib/models/config.d.ts +11 -18
- package/lib/models/config.js +37 -35
- package/lib/models/connection-state.d.ts +10 -10
- package/lib/models/connection-state.js +16 -16
- package/lib/telnyx-voice-app.d.ts +28 -28
- package/lib/telnyx-voice-app.js +513 -480
- package/lib/telnyx-voip-client.d.ts +167 -167
- package/lib/telnyx-voip-client.js +385 -390
- package/package.json +115 -104
- package/src/callkit/callkit-coordinator.ts +830 -846
- package/src/hooks/useNetworkStateHandler.ts +0 -0
- package/src/internal/calls/call-state-controller.ts +407 -384
- package/src/internal/session/session-manager.ts +483 -467
- package/src/internal/voice-pn-bridge.ts +266 -18
- package/src/models/call-state.ts +105 -98
- package/src/models/call.ts +502 -388
- package/src/telnyx-voice-app.tsx +788 -690
- package/src/telnyx-voip-client.ts +551 -539
- package/src/types/telnyx-sdk.d.ts +93 -79
package/lib/telnyx-voice-app.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports,
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TelnyxVoiceApp = void 0;
|
|
4
|
-
const jsx_runtime_1 = require(
|
|
5
|
-
const react_1 = require(
|
|
6
|
-
const react_native_1 = require(
|
|
7
|
-
const telnyx_voip_client_1 = require(
|
|
8
|
-
const connection_state_1 = require(
|
|
9
|
-
const TelnyxVoiceContext_1 = require(
|
|
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
10
|
/**
|
|
11
11
|
* A comprehensive wrapper component that handles all Telnyx SDK lifecycle management.
|
|
12
12
|
*
|
|
@@ -23,435 +23,487 @@ const TelnyxVoiceContext_1 = require('./context/TelnyxVoiceContext');
|
|
|
23
23
|
* </TelnyxVoiceApp>
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
|
-
const TelnyxVoiceAppComponent = ({
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
83
123
|
await checkForInitialPushNotification(true); // Pass true for fromAppResume
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
call.currentState === 'CONNECTING'
|
|
111
|
-
);
|
|
112
|
-
// Also check if there's an incoming call from push notification being processed
|
|
113
|
-
let isCallFromPush = false;
|
|
114
|
-
if (react_native_1.Platform.OS === 'ios') {
|
|
115
|
-
try {
|
|
116
|
-
const { callKitCoordinator } = require('./callkit/callkit-coordinator');
|
|
117
|
-
isCallFromPush = callKitCoordinator.getIsCallFromPush();
|
|
118
|
-
} catch (e) {
|
|
119
|
-
log('Error checking isCallFromPush:', e);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (hasOngoingCall || isCallFromPush) {
|
|
123
|
-
log('Active calls or push call detected - skipping background disconnection', {
|
|
124
|
-
callCount: activeCalls.length,
|
|
125
|
-
hasOngoingCall,
|
|
126
|
-
isCallFromPush,
|
|
127
|
-
callStates: activeCalls.map((call) => ({
|
|
128
|
-
callId: call.callId,
|
|
129
|
-
currentState: call.currentState,
|
|
130
|
-
destination: call.destination,
|
|
131
|
-
})),
|
|
132
|
-
});
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
log('App backgrounded - disconnecting (matching old BackgroundDetector behavior)');
|
|
136
|
-
try {
|
|
137
|
-
// Always disconnect when backgrounded (matches old implementation)
|
|
138
|
-
await voipClient.logout();
|
|
139
|
-
log('Successfully disconnected on background');
|
|
140
|
-
} catch (e) {
|
|
141
|
-
log('Error disconnecting on background:', e);
|
|
142
|
-
}
|
|
143
|
-
}, [voipClient, isHandlingForegroundCall, log]);
|
|
144
|
-
// Handle app resuming from background
|
|
145
|
-
const handleAppResumed = (0, react_1.useCallback)(async () => {
|
|
146
|
-
log('App resumed - checking reconnection needs');
|
|
147
|
-
// IMPORTANT: Check for push notifications first when resuming from background
|
|
148
|
-
// This handles the case where the user accepted a call while the app was backgrounded
|
|
149
|
-
await checkForInitialPushNotification(true); // Pass true for fromAppResume
|
|
150
|
-
// If we're ignoring (e.g., from push call) or handling foreground call, don't auto-reconnect
|
|
151
|
-
if (backgroundDetectorIgnore.current || isHandlingForegroundCall) {
|
|
152
|
-
log(
|
|
153
|
-
'Background detector ignore flag set or handling foreground call - skipping reconnection'
|
|
154
|
-
);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
// iOS-specific: If push notification handling just initiated a connection,
|
|
158
|
-
// skip auto-reconnection to prevent double login
|
|
159
|
-
if (react_native_1.Platform.OS === 'ios') {
|
|
160
|
-
// Check if connection state changed after push processing
|
|
161
|
-
const connectionStateAfterPush = voipClient.currentConnectionState;
|
|
162
|
-
if (
|
|
163
|
-
connectionStateAfterPush === connection_state_1.TelnyxConnectionState.CONNECTING ||
|
|
164
|
-
connectionStateAfterPush === connection_state_1.TelnyxConnectionState.CONNECTED
|
|
165
|
-
) {
|
|
166
|
-
log(
|
|
167
|
-
`iOS: Push handling initiated connection (${connectionStateAfterPush}), skipping auto-reconnection`
|
|
168
|
-
);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// Check current connection state and reconnect if needed
|
|
173
|
-
const currentState = voipClient.currentConnectionState;
|
|
174
|
-
log(`Current connection state: ${currentState}`);
|
|
175
|
-
// If we're not connected and have stored credentials, attempt reconnection
|
|
176
|
-
if (currentState !== connection_state_1.TelnyxConnectionState.CONNECTED) {
|
|
177
|
-
await attemptAutoReconnection();
|
|
178
|
-
}
|
|
179
|
-
}, [voipClient, isHandlingForegroundCall, log]);
|
|
180
|
-
// Attempt to reconnect using stored credentials
|
|
181
|
-
const attemptAutoReconnection = (0, react_1.useCallback)(async () => {
|
|
182
|
-
try {
|
|
183
|
-
log('Attempting auto-reconnection...');
|
|
184
|
-
// Try to get stored config and reconnect
|
|
185
|
-
const success = await voipClient.loginFromStoredConfig();
|
|
186
|
-
log(`Auto-reconnection ${success ? 'successful' : 'failed'}`);
|
|
187
|
-
// If auto-reconnection fails, redirect to login screen
|
|
188
|
-
if (!success) {
|
|
189
|
-
log('Auto-reconnection failed - redirecting to login screen');
|
|
190
|
-
// Import router dynamically to avoid circular dependency issues
|
|
191
|
-
const { router } = require('expo-router');
|
|
192
|
-
// Small delay to ensure state is settled
|
|
193
|
-
setTimeout(() => {
|
|
194
|
-
router.replace('/');
|
|
195
|
-
}, 100);
|
|
196
|
-
}
|
|
197
|
-
} catch (e) {
|
|
198
|
-
log('Auto-reconnection error:', e);
|
|
199
|
-
// On error, also redirect to login
|
|
200
|
-
log('Auto-reconnection error - redirecting to login screen');
|
|
201
|
-
const { router } = require('expo-router');
|
|
202
|
-
setTimeout(() => {
|
|
203
|
-
router.replace('/');
|
|
204
|
-
}, 100);
|
|
205
|
-
}
|
|
206
|
-
}, [voipClient, log]);
|
|
207
|
-
// Check for initial push notification when app launches
|
|
208
|
-
const checkForInitialPushNotification = (0, react_1.useCallback)(
|
|
209
|
-
async (fromAppResume = false) => {
|
|
210
|
-
log(`checkForInitialPushNotification called${fromAppResume ? ' (from app resume)' : ''}`);
|
|
211
|
-
if (processingPushOnLaunch && !fromAppResume) {
|
|
212
|
-
log('Already processing push, returning early');
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
// Only set the flag if this is not from app resume to allow resume processing
|
|
216
|
-
if (!fromAppResume) {
|
|
217
|
-
setProcessingPushOnLaunch(true);
|
|
218
|
-
}
|
|
219
|
-
onPushNotificationProcessingStarted?.();
|
|
220
|
-
try {
|
|
221
|
-
let pushData = null;
|
|
222
|
-
// Try to get push data from the native layer using our VoicePnBridge
|
|
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 () => {
|
|
223
150
|
try {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
log('Auto-reconnection error:', e);
|
|
162
|
+
// On error, also redirect to login
|
|
163
|
+
log('Auto-reconnection error - redirecting to login screen');
|
|
164
|
+
}
|
|
165
|
+
}, [voipClient, log]);
|
|
166
|
+
// Check for initial push notification action when app launches
|
|
167
|
+
const checkForInitialPushNotification = (0, react_1.useCallback)(async (fromAppResume = false) => {
|
|
168
|
+
log(`checkForInitialPushNotification called${fromAppResume ? ' (from app resume)' : ''}`);
|
|
169
|
+
if (processingPushOnLaunch && !fromAppResume) {
|
|
170
|
+
log('Already processing push, returning early');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// Only set the flag if this is not from app resume to allow resume processing
|
|
174
|
+
if (!fromAppResume) {
|
|
175
|
+
setProcessingPushOnLaunch(true);
|
|
176
|
+
}
|
|
177
|
+
onPushNotificationProcessingStarted?.();
|
|
178
|
+
try {
|
|
179
|
+
let pushData = null;
|
|
180
|
+
// Try to get push data from the native layer using platform-specific methods
|
|
181
|
+
if (react_native_1.Platform.OS === 'android') {
|
|
236
182
|
try {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
183
|
+
// Import the native bridge module dynamically
|
|
184
|
+
const { NativeModules } = require('react-native');
|
|
185
|
+
const VoicePnBridge = NativeModules.VoicePnBridge;
|
|
186
|
+
if (VoicePnBridge) {
|
|
187
|
+
log('Checking for pending push actions via VoicePnBridge');
|
|
188
|
+
// First check for pending call actions (notification button taps like hangup/answer)
|
|
189
|
+
const pendingCallAction = await VoicePnBridge.getPendingCallAction();
|
|
190
|
+
log('Raw pending call action response:', pendingCallAction);
|
|
191
|
+
if (pendingCallAction && pendingCallAction.action != null) {
|
|
192
|
+
log('Found pending call action:', pendingCallAction);
|
|
193
|
+
// Handle call actions directly
|
|
194
|
+
if (pendingCallAction.action === 'hangup' && pendingCallAction.callId) {
|
|
195
|
+
log('Processing hangup action from notification for call:', pendingCallAction.callId);
|
|
196
|
+
// Find and hangup the call
|
|
197
|
+
const activeCall = voipClient.currentActiveCall;
|
|
198
|
+
if (activeCall && activeCall.callId === pendingCallAction.callId) {
|
|
199
|
+
log('Hanging up active call from notification action');
|
|
200
|
+
try {
|
|
201
|
+
await activeCall.hangup();
|
|
202
|
+
log('Call hung up successfully from notification action');
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
log('Error hanging up call from notification action:', error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
log('No matching active call found for hangup action');
|
|
210
|
+
}
|
|
211
|
+
// Clear the pending action
|
|
212
|
+
await VoicePnBridge.clearPendingCallAction();
|
|
213
|
+
return; // Don't process as push data
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Then check for regular push notification data
|
|
217
|
+
const pendingAction = await VoicePnBridge.getPendingPushAction();
|
|
218
|
+
log('Raw pending action response:', pendingAction);
|
|
219
|
+
if (pendingAction && pendingAction.action != null && pendingAction.metadata != null) {
|
|
220
|
+
log('Found pending push action:', pendingAction);
|
|
221
|
+
// Parse the metadata if it's a string
|
|
222
|
+
let metadata = pendingAction.metadata;
|
|
223
|
+
try {
|
|
224
|
+
// First try parsing as JSON
|
|
225
|
+
metadata = JSON.parse(metadata);
|
|
226
|
+
log('Parsed metadata as JSON:', metadata);
|
|
227
|
+
}
|
|
228
|
+
catch (e) {
|
|
229
|
+
log('JSON parse failed, trying Android key-value format');
|
|
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
|
+
}
|
|
253
244
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
245
|
+
else {
|
|
246
|
+
log('VoicePnBridge not available on Android');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (bridgeError) {
|
|
250
|
+
log('Error accessing VoicePnBridge on Android:', bridgeError);
|
|
259
251
|
}
|
|
260
|
-
}
|
|
261
|
-
// Create push data structure that matches what the VoIP client expects
|
|
262
|
-
pushData = {
|
|
263
|
-
action: pendingAction.action,
|
|
264
|
-
metadata: metadata,
|
|
265
|
-
from_notification: true,
|
|
266
|
-
};
|
|
267
|
-
// Clear the pending action so it doesn't get processed again
|
|
268
|
-
await VoicePnBridge.clearPendingPushAction();
|
|
269
|
-
log('Cleared pending push action after retrieval');
|
|
270
|
-
} else {
|
|
271
|
-
log('No pending push actions found');
|
|
272
252
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
253
|
+
else if (react_native_1.Platform.OS === 'ios') {
|
|
254
|
+
try {
|
|
255
|
+
// Import the native bridge module dynamically (same as Android)
|
|
256
|
+
const { NativeModules } = require('react-native');
|
|
257
|
+
const VoicePnBridge = NativeModules.VoicePnBridge;
|
|
258
|
+
if (VoicePnBridge) {
|
|
259
|
+
log('Checking for pending VoIP push data via iOS VoicePnBridge');
|
|
260
|
+
// Check for VoIP push notification data stored by the native push handler
|
|
261
|
+
const pendingVoipPushJson = await VoicePnBridge.getPendingVoipPush();
|
|
262
|
+
log('Raw pending VoIP push response:', pendingVoipPushJson);
|
|
263
|
+
if (pendingVoipPushJson) {
|
|
264
|
+
try {
|
|
265
|
+
const pendingVoipPush = JSON.parse(pendingVoipPushJson);
|
|
266
|
+
const voipPayload = pendingVoipPush?.payload;
|
|
267
|
+
if (voipPayload && voipPayload.metadata) {
|
|
268
|
+
log('Found pending VoIP push data:', voipPayload);
|
|
269
|
+
// Create push data structure that matches what the VoIP client expects
|
|
270
|
+
pushData = {
|
|
271
|
+
action: 'incoming_call',
|
|
272
|
+
metadata: voipPayload.metadata,
|
|
273
|
+
from_notification: true,
|
|
274
|
+
};
|
|
275
|
+
// Clear the pending VoIP push data so it doesn't get processed again
|
|
276
|
+
await VoicePnBridge.clearPendingVoipPush();
|
|
277
|
+
log('Cleared pending VoIP push data after retrieval');
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
log('Invalid VoIP push data structure');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (parseError) {
|
|
284
|
+
log('Error parsing VoIP push JSON:', parseError);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
log('No pending VoIP push data found');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
log('VoicePnBridge not available on iOS');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch (bridgeError) {
|
|
296
|
+
log('Error accessing VoicePnBridge on iOS:', bridgeError);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
log('Push data check skipped - unsupported platform');
|
|
301
|
+
}
|
|
302
|
+
// Process the push notification if found
|
|
303
|
+
if (pushData) {
|
|
304
|
+
log('Processing initial push notification...');
|
|
305
|
+
// Check if we're already connected and handling a push - prevent duplicate processing
|
|
306
|
+
const isConnected = voipClient.currentConnectionState === connection_state_1.TelnyxConnectionState.CONNECTED;
|
|
307
|
+
if (isConnected) {
|
|
308
|
+
log('SKIPPING - Already connected, preventing duplicate processing');
|
|
309
|
+
// Clear the stored data since we're already handling it
|
|
310
|
+
// TODO: Implement clearPushMetaData
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// Set flags to prevent auto-reconnection during push call
|
|
314
|
+
setIsHandlingForegroundCall(true);
|
|
315
|
+
backgroundDetectorIgnore.current = true;
|
|
316
|
+
log(`Background detector ignore set to: true at ${new Date().toISOString()}`);
|
|
317
|
+
log(`Foreground call handling flag set to: true at ${new Date().toISOString()}`);
|
|
318
|
+
// Dispose any existing background client to prevent conflicts
|
|
319
|
+
disposeBackgroundClient();
|
|
320
|
+
// Handle the push notification using platform-specific approach
|
|
321
|
+
if (react_native_1.Platform.OS === 'ios') {
|
|
322
|
+
// On iOS, coordinate with CallKit by notifying the coordinator about the push
|
|
323
|
+
const { callKitCoordinator } = require('./callkit/callkit-coordinator');
|
|
324
|
+
// Extract call_id from nested metadata structure to use as CallKit UUID
|
|
325
|
+
const callId = pushData.metadata?.metadata?.call_id;
|
|
326
|
+
if (callId) {
|
|
327
|
+
log('Notifying CallKit coordinator about push notification:', callId);
|
|
328
|
+
await callKitCoordinator.handleCallKitPushReceived(callId, {
|
|
329
|
+
callData: { source: 'push_notification' },
|
|
330
|
+
pushData: pushData,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
log('No call_id found in push data, falling back to direct handling');
|
|
335
|
+
await voipClient.handlePushNotification(pushData);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
// On other platforms, handle push notification directly
|
|
340
|
+
await voipClient.handlePushNotification(pushData);
|
|
341
|
+
}
|
|
342
|
+
log('Initial push notification processed');
|
|
343
|
+
log('Cleared stored push data to prevent duplicate processing');
|
|
344
|
+
// Note: isHandlingForegroundCall will be reset when calls.length becomes 0
|
|
345
|
+
// This prevents premature disconnection during CallKit answer flow
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
log('No initial push data found');
|
|
349
|
+
}
|
|
278
350
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
351
|
+
catch (e) {
|
|
352
|
+
log('Error processing initial push notification:', e);
|
|
353
|
+
// Reset flags on error
|
|
354
|
+
setIsHandlingForegroundCall(false);
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
// Always reset the processing flag - it should not remain stuck
|
|
358
|
+
setProcessingPushOnLaunch(false);
|
|
359
|
+
onPushNotificationProcessingCompleted?.();
|
|
360
|
+
}
|
|
361
|
+
}, [
|
|
362
|
+
processingPushOnLaunch,
|
|
363
|
+
voipClient,
|
|
364
|
+
onPushNotificationProcessingStarted,
|
|
365
|
+
onPushNotificationProcessingCompleted,
|
|
366
|
+
log,
|
|
367
|
+
]);
|
|
368
|
+
// Dispose background client instance when no longer needed
|
|
369
|
+
const disposeBackgroundClient = (0, react_1.useCallback)(() => {
|
|
370
|
+
if (backgroundClientRef.current) {
|
|
371
|
+
log('Disposing background client instance');
|
|
372
|
+
backgroundClientRef.current.dispose();
|
|
373
|
+
backgroundClientRef.current = null;
|
|
374
|
+
}
|
|
375
|
+
}, [log]);
|
|
376
|
+
// Create background client for push notification handling
|
|
377
|
+
const createBackgroundClient = (0, react_1.useCallback)(() => {
|
|
378
|
+
log('Creating background client instance');
|
|
379
|
+
const backgroundClient = (0, telnyx_voip_client_1.createBackgroundTelnyxVoipClient)({
|
|
380
|
+
debug,
|
|
381
|
+
});
|
|
382
|
+
return backgroundClient;
|
|
383
|
+
}, [debug, log]);
|
|
384
|
+
// Setup effect
|
|
385
|
+
(0, react_1.useEffect)(() => {
|
|
386
|
+
// Listen to connection state changes
|
|
387
|
+
const connectionStateSubscription = voipClient.connectionState$.subscribe((state) => {
|
|
388
|
+
setCurrentConnectionState(state);
|
|
389
|
+
// Just log connection changes, let the app handle navigation
|
|
390
|
+
log(`Connection state changed to: ${state}`);
|
|
391
|
+
});
|
|
392
|
+
// Listen to call changes to reset flags when no active calls
|
|
393
|
+
const callsSubscription = voipClient.calls$.subscribe((calls) => {
|
|
394
|
+
// Check if we should reset flags - only reset if:
|
|
395
|
+
// 1. No active WebRTC calls AND
|
|
396
|
+
// 2. No CallKit operations in progress (to prevent disconnection during CallKit answer flow)
|
|
397
|
+
const hasActiveWebRTCCalls = calls.length > 0;
|
|
398
|
+
let hasCallKitProcessing = false;
|
|
399
|
+
// Check CallKit processing calls only on iOS
|
|
400
|
+
if (react_native_1.Platform.OS === 'ios') {
|
|
401
|
+
try {
|
|
402
|
+
const { callKitCoordinator } = require('./callkit/callkit-coordinator');
|
|
403
|
+
hasCallKitProcessing = callKitCoordinator.hasProcessingCalls();
|
|
404
|
+
log(`CallKit processing check: hasProcessingCalls=${hasCallKitProcessing}`);
|
|
405
|
+
}
|
|
406
|
+
catch (e) {
|
|
407
|
+
log('Error checking CallKit processing calls:', e);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
log(`Flag reset check: WebRTC calls=${calls.length}, CallKit processing=${hasCallKitProcessing}, isHandlingForegroundCall=${isHandlingForegroundCall}, backgroundDetectorIgnore=${backgroundDetectorIgnore.current}`);
|
|
411
|
+
if (!hasActiveWebRTCCalls &&
|
|
412
|
+
!hasCallKitProcessing &&
|
|
413
|
+
(isHandlingForegroundCall || backgroundDetectorIgnore.current)) {
|
|
414
|
+
log(`No active calls and no CallKit processing - resetting ignore flags at ${new Date().toISOString()}`);
|
|
415
|
+
setIsHandlingForegroundCall(false);
|
|
416
|
+
backgroundDetectorIgnore.current = false;
|
|
417
|
+
}
|
|
418
|
+
else if (!hasActiveWebRTCCalls && hasCallKitProcessing) {
|
|
419
|
+
log(`No WebRTC calls but CallKit operations in progress - keeping ignore flags active at ${new Date().toISOString()}`);
|
|
420
|
+
}
|
|
421
|
+
else if (hasActiveWebRTCCalls) {
|
|
422
|
+
log(`WebRTC calls active - keeping ignore flags active at ${new Date().toISOString()}`);
|
|
423
|
+
}
|
|
424
|
+
// Also reset processingPushOnLaunch if no calls are active
|
|
425
|
+
// This ensures the flag doesn't get stuck after call ends
|
|
426
|
+
if (calls.length === 0 && processingPushOnLaunch) {
|
|
427
|
+
log('No active calls - resetting processing push flag');
|
|
428
|
+
setProcessingPushOnLaunch(false);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
// Listen for immediate call action events from notification buttons (Android only)
|
|
432
|
+
let callActionSubscription = null;
|
|
433
|
+
if (react_native_1.Platform.OS === 'android') {
|
|
434
|
+
try {
|
|
435
|
+
const { VoicePnBridge } = require('./internal/voice-pn-bridge');
|
|
436
|
+
callActionSubscription = VoicePnBridge.addCallActionListener((event) => {
|
|
437
|
+
log(`Received immediate call action: ${event.action} for callId: ${event.callId}`);
|
|
438
|
+
// Handle immediate call actions (mainly for ending active calls from notification)
|
|
439
|
+
if (event.action === 'hangup' ||
|
|
440
|
+
event.action === 'endCall' ||
|
|
441
|
+
event.action === 'reject') {
|
|
442
|
+
log(`Processing immediate end call action for callId: ${event.callId}`);
|
|
443
|
+
// Find the call by ID and end it
|
|
444
|
+
const targetCall = voipClient.currentCalls.find((call) => call.callId === event.callId);
|
|
445
|
+
if (targetCall) {
|
|
446
|
+
log(`Found active call ${event.callId}, ending it immediately`);
|
|
447
|
+
targetCall.hangup().catch((error) => {
|
|
448
|
+
log(`Error ending call ${event.callId}:`, error);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
log(`No active call found with ID ${event.callId}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
log('Call action listener registered for immediate notification handling');
|
|
457
|
+
}
|
|
458
|
+
catch (e) {
|
|
459
|
+
log('Error setting up call action listener (VoicePnBridge not available):', e);
|
|
314
460
|
}
|
|
315
|
-
} else {
|
|
316
|
-
// On other platforms, handle push notification directly
|
|
317
|
-
await voipClient.handlePushNotification(pushData);
|
|
318
|
-
}
|
|
319
|
-
log('Initial push notification processed');
|
|
320
|
-
log('Cleared stored push data to prevent duplicate processing');
|
|
321
|
-
// Note: isHandlingForegroundCall will be reset when calls.length becomes 0
|
|
322
|
-
// This prevents premature disconnection during CallKit answer flow
|
|
323
|
-
} else {
|
|
324
|
-
log('No initial push data found');
|
|
325
461
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
setProcessingPushOnLaunch(false);
|
|
333
|
-
onPushNotificationProcessingCompleted?.();
|
|
334
|
-
}
|
|
335
|
-
},
|
|
336
|
-
[
|
|
337
|
-
processingPushOnLaunch,
|
|
338
|
-
voipClient,
|
|
339
|
-
onPushNotificationProcessingStarted,
|
|
340
|
-
onPushNotificationProcessingCompleted,
|
|
341
|
-
log,
|
|
342
|
-
]
|
|
343
|
-
);
|
|
344
|
-
// Dispose background client instance when no longer needed
|
|
345
|
-
const disposeBackgroundClient = (0, react_1.useCallback)(() => {
|
|
346
|
-
if (backgroundClientRef.current) {
|
|
347
|
-
log('Disposing background client instance');
|
|
348
|
-
backgroundClientRef.current.dispose();
|
|
349
|
-
backgroundClientRef.current = null;
|
|
350
|
-
}
|
|
351
|
-
}, [log]);
|
|
352
|
-
// Create background client for push notification handling
|
|
353
|
-
const createBackgroundClient = (0, react_1.useCallback)(() => {
|
|
354
|
-
log('Creating background client instance');
|
|
355
|
-
const backgroundClient = (0, telnyx_voip_client_1.createBackgroundTelnyxVoipClient)({
|
|
356
|
-
debug,
|
|
357
|
-
});
|
|
358
|
-
return backgroundClient;
|
|
359
|
-
}, [debug, log]);
|
|
360
|
-
// Setup effect
|
|
361
|
-
(0, react_1.useEffect)(() => {
|
|
362
|
-
// Listen to connection state changes
|
|
363
|
-
const connectionStateSubscription = voipClient.connectionState$.subscribe((state) => {
|
|
364
|
-
setCurrentConnectionState(state);
|
|
365
|
-
// Just log connection changes, let the app handle navigation
|
|
366
|
-
log(`Connection state changed to: ${state}`);
|
|
367
|
-
});
|
|
368
|
-
// Listen to call changes to reset flags when no active calls
|
|
369
|
-
const callsSubscription = voipClient.calls$.subscribe((calls) => {
|
|
370
|
-
// Check if we should reset flags - only reset if:
|
|
371
|
-
// 1. No active WebRTC calls AND
|
|
372
|
-
// 2. No CallKit operations in progress (to prevent disconnection during CallKit answer flow)
|
|
373
|
-
const hasActiveWebRTCCalls = calls.length > 0;
|
|
374
|
-
let hasCallKitProcessing = false;
|
|
375
|
-
// Check CallKit processing calls only on iOS
|
|
376
|
-
if (react_native_1.Platform.OS === 'ios') {
|
|
377
|
-
try {
|
|
378
|
-
const { callKitCoordinator } = require('./callkit/callkit-coordinator');
|
|
379
|
-
hasCallKitProcessing = callKitCoordinator.hasProcessingCalls();
|
|
380
|
-
log(`CallKit processing check: hasProcessingCalls=${hasCallKitProcessing}`);
|
|
381
|
-
} catch (e) {
|
|
382
|
-
log('Error checking CallKit processing calls:', e);
|
|
462
|
+
// Add app state listener if not skipping web background detection or not on web
|
|
463
|
+
// AND if app state management is enabled in the client options
|
|
464
|
+
let appStateSubscription = null;
|
|
465
|
+
if ((!skipWebBackgroundDetection || react_native_1.Platform.OS !== 'web') &&
|
|
466
|
+
voipClient.options.enableAppStateManagement) {
|
|
467
|
+
appStateSubscription = react_native_1.AppState.addEventListener('change', handleAppStateChange);
|
|
383
468
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
// Handle initial push notification if app was launched from terminated state
|
|
425
|
-
// Only check if we're not already processing to prevent infinite loops
|
|
426
|
-
const timeoutId = setTimeout(() => {
|
|
427
|
-
if (!processingPushOnLaunch) {
|
|
428
|
-
checkForInitialPushNotification();
|
|
429
|
-
}
|
|
430
|
-
}, 100);
|
|
431
|
-
// Cleanup function
|
|
432
|
-
return () => {
|
|
433
|
-
connectionStateSubscription.unsubscribe();
|
|
434
|
-
callsSubscription.unsubscribe();
|
|
435
|
-
if (appStateSubscription) {
|
|
436
|
-
appStateSubscription.remove();
|
|
437
|
-
}
|
|
438
|
-
clearTimeout(timeoutId);
|
|
439
|
-
// Clean up background client instance
|
|
440
|
-
disposeBackgroundClient();
|
|
441
|
-
};
|
|
442
|
-
}, [
|
|
443
|
-
voipClient,
|
|
444
|
-
handleAppStateChange,
|
|
445
|
-
disposeBackgroundClient,
|
|
446
|
-
skipWebBackgroundDetection,
|
|
447
|
-
isHandlingForegroundCall,
|
|
448
|
-
log,
|
|
449
|
-
]);
|
|
450
|
-
// Simply return the children wrapped in context provider - all lifecycle management is handled internally
|
|
451
|
-
return (0, jsx_runtime_1.jsx)(TelnyxVoiceContext_1.TelnyxVoiceProvider, {
|
|
452
|
-
voipClient: voipClient,
|
|
453
|
-
children: children,
|
|
454
|
-
});
|
|
469
|
+
// Handle initial push notification if app was launched from terminated state
|
|
470
|
+
// Only check if we're not already processing to prevent infinite loops
|
|
471
|
+
const timeoutId = setTimeout(() => {
|
|
472
|
+
if (!processingPushOnLaunch) {
|
|
473
|
+
checkForInitialPushNotification();
|
|
474
|
+
}
|
|
475
|
+
}, 100);
|
|
476
|
+
// Cleanup function
|
|
477
|
+
return () => {
|
|
478
|
+
connectionStateSubscription.unsubscribe();
|
|
479
|
+
callsSubscription.unsubscribe();
|
|
480
|
+
if (callActionSubscription) {
|
|
481
|
+
try {
|
|
482
|
+
const { VoicePnBridge } = require('./internal/voice-pn-bridge');
|
|
483
|
+
VoicePnBridge.removeCallActionListener(callActionSubscription);
|
|
484
|
+
log('Call action listener removed');
|
|
485
|
+
}
|
|
486
|
+
catch (e) {
|
|
487
|
+
log('Error removing call action listener:', e);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (appStateSubscription) {
|
|
491
|
+
appStateSubscription.remove();
|
|
492
|
+
}
|
|
493
|
+
clearTimeout(timeoutId);
|
|
494
|
+
// Clean up background client instance
|
|
495
|
+
disposeBackgroundClient();
|
|
496
|
+
};
|
|
497
|
+
}, [
|
|
498
|
+
voipClient,
|
|
499
|
+
handleAppStateChange,
|
|
500
|
+
disposeBackgroundClient,
|
|
501
|
+
skipWebBackgroundDetection,
|
|
502
|
+
isHandlingForegroundCall,
|
|
503
|
+
log,
|
|
504
|
+
]);
|
|
505
|
+
// Simply return the children wrapped in context provider - all lifecycle management is handled internally
|
|
506
|
+
return (0, jsx_runtime_1.jsx)(TelnyxVoiceContext_1.TelnyxVoiceProvider, { voipClient: voipClient, children: children });
|
|
455
507
|
};
|
|
456
508
|
/**
|
|
457
509
|
* Static factory method that handles all common SDK initialization boilerplate.
|
|
@@ -475,70 +527,51 @@ const TelnyxVoiceAppComponent = ({
|
|
|
475
527
|
* ```
|
|
476
528
|
*/
|
|
477
529
|
const initializeAndCreate = async (options) => {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (debug) {
|
|
493
|
-
console.log('[TelnyxVoiceApp] Android push notification initialization needed');
|
|
530
|
+
const { voipClient, children, backgroundMessageHandler, onPushNotificationProcessingStarted, onPushNotificationProcessingCompleted, onAppStateChanged, enableAutoReconnect = true, skipWebBackgroundDetection = true, debug = false, } = options;
|
|
531
|
+
// Initialize push notification handling for Android
|
|
532
|
+
if (react_native_1.Platform.OS === 'android') {
|
|
533
|
+
// TODO: Initialize Firebase or other push notification service
|
|
534
|
+
if (debug) {
|
|
535
|
+
console.log('[TelnyxVoiceApp] Android push notification initialization needed');
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
// Register background message handler if provided
|
|
539
|
+
if (backgroundMessageHandler) {
|
|
540
|
+
// TODO: Register the background message handler with the push notification service
|
|
541
|
+
if (debug) {
|
|
542
|
+
console.log('[TelnyxVoiceApp] Background message handler registration needed');
|
|
543
|
+
}
|
|
494
544
|
}
|
|
495
|
-
}
|
|
496
|
-
// Register background message handler if provided
|
|
497
|
-
if (backgroundMessageHandler) {
|
|
498
|
-
// TODO: Register the background message handler with the push notification service
|
|
499
545
|
if (debug) {
|
|
500
|
-
|
|
546
|
+
console.log('[TelnyxVoiceApp] SDK initialization complete');
|
|
501
547
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
console.log('[TelnyxVoiceApp] SDK initialization complete');
|
|
505
|
-
}
|
|
506
|
-
// Return a component that renders TelnyxVoiceApp with the provided options
|
|
507
|
-
return () =>
|
|
508
|
-
(0, jsx_runtime_1.jsx)(exports.TelnyxVoiceApp, {
|
|
509
|
-
voipClient: voipClient,
|
|
510
|
-
onPushNotificationProcessingStarted: onPushNotificationProcessingStarted,
|
|
511
|
-
onPushNotificationProcessingCompleted: onPushNotificationProcessingCompleted,
|
|
512
|
-
onAppStateChanged: onAppStateChanged,
|
|
513
|
-
enableAutoReconnect: enableAutoReconnect,
|
|
514
|
-
skipWebBackgroundDetection: skipWebBackgroundDetection,
|
|
515
|
-
debug: debug,
|
|
516
|
-
children: children,
|
|
517
|
-
});
|
|
548
|
+
// Return a component that renders TelnyxVoiceApp with the provided options
|
|
549
|
+
return () => ((0, jsx_runtime_1.jsx)(exports.TelnyxVoiceApp, { voipClient: voipClient, onPushNotificationProcessingStarted: onPushNotificationProcessingStarted, onPushNotificationProcessingCompleted: onPushNotificationProcessingCompleted, onAppStateChanged: onAppStateChanged, enableAutoReconnect: enableAutoReconnect, skipWebBackgroundDetection: skipWebBackgroundDetection, debug: debug, children: children }));
|
|
518
550
|
};
|
|
519
551
|
/**
|
|
520
552
|
* Handles background push notifications in the background isolate.
|
|
521
553
|
* This should be called from your background message handler.
|
|
522
554
|
*/
|
|
523
555
|
const handleBackgroundPush = async (message) => {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
556
|
+
console.log('[TelnyxVoiceApp] Background push received:', message);
|
|
557
|
+
try {
|
|
558
|
+
// TODO: Initialize push notification service in isolate if needed
|
|
559
|
+
// Use singleton pattern for background client to prevent multiple instances
|
|
560
|
+
let backgroundClient = (0, telnyx_voip_client_1.createBackgroundTelnyxVoipClient)({
|
|
561
|
+
debug: true,
|
|
562
|
+
});
|
|
563
|
+
await backgroundClient.handlePushNotification(message);
|
|
564
|
+
console.log('[TelnyxVoiceApp] Background push processed successfully');
|
|
565
|
+
// Clean up the background client
|
|
566
|
+
backgroundClient.dispose();
|
|
567
|
+
}
|
|
568
|
+
catch (e) {
|
|
569
|
+
console.log('[TelnyxVoiceApp] Error processing background push:', e);
|
|
570
|
+
}
|
|
538
571
|
};
|
|
539
572
|
// Create the component with static methods
|
|
540
573
|
exports.TelnyxVoiceApp = Object.assign(TelnyxVoiceAppComponent, {
|
|
541
|
-
|
|
542
|
-
|
|
574
|
+
initializeAndCreate,
|
|
575
|
+
handleBackgroundPush,
|
|
543
576
|
});
|
|
544
577
|
exports.default = exports.TelnyxVoiceApp;
|