@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.
- package/CHANGELOG.md +23 -0
- package/README.md +28 -7
- package/android/src/main/java/com/telnyx/react_voice_commons/CallForegroundService.kt +16 -2
- package/android/src/main/java/com/telnyx/react_voice_commons/TelnyxFirebaseMessagingService.kt +16 -2
- package/android/src/main/java/com/telnyx/react_voice_commons/TelnyxNotificationHelper.kt +345 -24
- package/android/src/main/java/com/telnyx/react_voice_commons/VoicePnBridgeModule.kt +74 -1
- package/android/src/main/java/com/telnyx/react_voice_commons/VoicePnManager.kt +36 -1
- package/ios/CallKitBridge.m +20 -1
- package/ios/CallKitBridge.swift +935 -60
- package/ios/README.md +2 -2
- package/ios/VoicePnBridge.m +21 -1
- package/ios/VoicePnBridge.swift +62 -1
- package/lib/callkit/callkit-coordinator.d.ts +39 -3
- package/lib/callkit/callkit-coordinator.js +561 -148
- package/lib/callkit/callkit.d.ts +7 -0
- package/lib/callkit/callkit.js +83 -11
- package/lib/hooks/useAppStateHandler.js +6 -2
- package/lib/index.d.ts +1 -0
- package/lib/index.js +9 -0
- package/lib/internal/CallKitHandler.d.ts +3 -0
- package/lib/internal/CallKitHandler.js +52 -46
- package/lib/internal/calls/call-state-controller.d.ts +39 -1
- package/lib/internal/calls/call-state-controller.js +152 -64
- package/lib/internal/session/session-manager.d.ts +16 -1
- package/lib/internal/session/session-manager.js +229 -58
- package/lib/internal/voice-pn-bridge.d.ts +44 -0
- package/lib/internal/voice-pn-bridge.js +94 -0
- package/lib/models/call.js +35 -0
- package/lib/models/config.d.ts +18 -0
- package/lib/models/config.js +2 -0
- package/lib/telnyx-voice-app.js +85 -43
- package/lib/telnyx-voip-client.d.ts +48 -5
- package/lib/telnyx-voip-client.js +276 -22
- package/package.json +7 -5
- package/src/callkit/callkit-coordinator.ts +642 -160
- package/src/callkit/callkit.ts +110 -12
- package/src/hooks/useAppStateHandler.ts +7 -2
- package/src/index.ts +3 -0
- package/src/internal/CallKitHandler.tsx +60 -55
- package/src/internal/calls/call-state-controller.ts +173 -74
- package/src/internal/session/session-manager.ts +277 -61
- package/src/internal/voice-pn-bridge.ts +111 -0
- package/src/models/call.ts +34 -0
- package/src/models/config.ts +18 -0
- package/src/telnyx-voice-app.tsx +96 -45
- package/src/telnyx-voip-client.ts +260 -24
- package/src/types/telnyx-sdk.d.ts +12 -1
package/src/models/call.ts
CHANGED
|
@@ -285,6 +285,17 @@ export class Call {
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
try {
|
|
288
|
+
if (Platform.OS === 'ios') {
|
|
289
|
+
const { callKitCoordinator } = await import('../callkit/callkit-coordinator');
|
|
290
|
+
if (callKitCoordinator.isAvailable()) {
|
|
291
|
+
const success = await callKitCoordinator.setHeldFromUI(this._telnyxCall, true);
|
|
292
|
+
if (!success) {
|
|
293
|
+
throw new Error('CallKit failed to hold call');
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
288
299
|
await this._telnyxCall.hold();
|
|
289
300
|
} catch (error) {
|
|
290
301
|
console.error('Failed to hold call:', error);
|
|
@@ -301,6 +312,17 @@ export class Call {
|
|
|
301
312
|
}
|
|
302
313
|
|
|
303
314
|
try {
|
|
315
|
+
if (Platform.OS === 'ios') {
|
|
316
|
+
const { callKitCoordinator } = await import('../callkit/callkit-coordinator');
|
|
317
|
+
if (callKitCoordinator.isAvailable()) {
|
|
318
|
+
const success = await callKitCoordinator.setHeldFromUI(this._telnyxCall, false);
|
|
319
|
+
if (!success) {
|
|
320
|
+
throw new Error('CallKit failed to resume call');
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
304
326
|
await this._telnyxCall.unhold();
|
|
305
327
|
} catch (error) {
|
|
306
328
|
console.error('Failed to resume call:', error);
|
|
@@ -406,6 +428,18 @@ export class Call {
|
|
|
406
428
|
const telnyxState = this._mapToTelnyxCallState(state);
|
|
407
429
|
this._callState.next(telnyxState);
|
|
408
430
|
|
|
431
|
+
// Keep the dedicated held observable derived from call state. Updating
|
|
432
|
+
// this only after the low-level SDK emits its successful transition
|
|
433
|
+
// preserves the previous value when hold/unhold signaling fails.
|
|
434
|
+
if (telnyxState === TelnyxCallState.HELD) {
|
|
435
|
+
this._isHeld.next(true);
|
|
436
|
+
} else if (
|
|
437
|
+
telnyxState === TelnyxCallState.ACTIVE ||
|
|
438
|
+
CallStateHelpers.isTerminated(telnyxState)
|
|
439
|
+
) {
|
|
440
|
+
this._isHeld.next(false);
|
|
441
|
+
}
|
|
442
|
+
|
|
409
443
|
// Start duration timer when call becomes active
|
|
410
444
|
if (telnyxState === TelnyxCallState.ACTIVE && !this._startTime) {
|
|
411
445
|
this._startDurationTimer();
|
package/src/models/config.ts
CHANGED
|
@@ -7,6 +7,14 @@ export interface CredentialConfig {
|
|
|
7
7
|
sipPassword: string;
|
|
8
8
|
debug?: boolean;
|
|
9
9
|
pushNotificationDeviceToken?: string;
|
|
10
|
+
/** Receive PushKit calls while the WebSocket session is active. Default: false */
|
|
11
|
+
pushWhenActive?: boolean;
|
|
12
|
+
/** Enable native missed call push notifications. Default: false */
|
|
13
|
+
enableMissedCallNotifications?: boolean;
|
|
14
|
+
/** Android only: name of a bundled `res/raw` ringtone (without its file extension). Falls back to the device ringtone. */
|
|
15
|
+
incomingCallRingtone?: string;
|
|
16
|
+
/** Enable Trickle ICE. Default: false */
|
|
17
|
+
useTrickleIce?: boolean;
|
|
10
18
|
/** Enable automatic call quality reporting. Default: true */
|
|
11
19
|
enableCallReports?: boolean;
|
|
12
20
|
/** Stats collection interval in seconds. Default: 5 */
|
|
@@ -25,6 +33,14 @@ export interface TokenConfig {
|
|
|
25
33
|
token: string;
|
|
26
34
|
debug?: boolean;
|
|
27
35
|
pushNotificationDeviceToken?: string;
|
|
36
|
+
/** Receive PushKit calls while the WebSocket session is active. Default: false */
|
|
37
|
+
pushWhenActive?: boolean;
|
|
38
|
+
/** Enable native missed call push notifications. Default: false */
|
|
39
|
+
enableMissedCallNotifications?: boolean;
|
|
40
|
+
/** Android only: name of a bundled `res/raw` ringtone (without its file extension). Falls back to the device ringtone. */
|
|
41
|
+
incomingCallRingtone?: string;
|
|
42
|
+
/** Enable Trickle ICE. Default: false */
|
|
43
|
+
useTrickleIce?: boolean;
|
|
28
44
|
/** Enable automatic call quality reporting. Default: true */
|
|
29
45
|
enableCallReports?: boolean;
|
|
30
46
|
/** Stats collection interval in seconds. Default: 5 */
|
|
@@ -105,6 +121,7 @@ export function validateConfig(config: Config): string[] {
|
|
|
105
121
|
* @param options - Optional configuration settings
|
|
106
122
|
* @param options.debug - Enable debug logging (sets SDK logLevel to 'debug')
|
|
107
123
|
* @param options.pushNotificationDeviceToken - Device token for push notifications
|
|
124
|
+
* @param options.pushWhenActive - Keep active PushKit devices eligible for incoming calls
|
|
108
125
|
* @returns Complete credential configuration object
|
|
109
126
|
*/
|
|
110
127
|
export function createCredentialConfig(
|
|
@@ -127,6 +144,7 @@ export function createCredentialConfig(
|
|
|
127
144
|
* @param options - Optional configuration settings
|
|
128
145
|
* @param options.debug - Enable debug logging (sets SDK logLevel to 'debug')
|
|
129
146
|
* @param options.pushNotificationDeviceToken - Device token for push notifications
|
|
147
|
+
* @param options.pushWhenActive - Keep active PushKit devices eligible for incoming calls
|
|
130
148
|
* @returns Complete token configuration object
|
|
131
149
|
*/
|
|
132
150
|
export function createTokenConfig(
|
package/src/telnyx-voice-app.tsx
CHANGED
|
@@ -3,8 +3,40 @@ import { AppState, AppStateStatus, Platform } from 'react-native';
|
|
|
3
3
|
import { TelnyxVoipClient, createBackgroundTelnyxVoipClient } from './telnyx-voip-client';
|
|
4
4
|
import { TelnyxConnectionState } from './models/connection-state';
|
|
5
5
|
import { Call } from './models/call';
|
|
6
|
+
import { TelnyxCallState } from './models/call-state';
|
|
6
7
|
import { TelnyxVoiceProvider } from './context/TelnyxVoiceContext';
|
|
7
8
|
|
|
9
|
+
let sharedBackgroundClient: TelnyxVoipClient | null = null;
|
|
10
|
+
let sharedBackgroundDisposePromise: Promise<void> | null = null;
|
|
11
|
+
|
|
12
|
+
const disposeSharedBackgroundClient = async (
|
|
13
|
+
log: (message: string, ...args: any[]) => void,
|
|
14
|
+
expectedClient?: TelnyxVoipClient
|
|
15
|
+
): Promise<void> => {
|
|
16
|
+
if (sharedBackgroundDisposePromise) {
|
|
17
|
+
await sharedBackgroundDisposePromise;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const backgroundClient = expectedClient ?? sharedBackgroundClient;
|
|
21
|
+
if (!backgroundClient || (expectedClient && sharedBackgroundClient !== expectedClient)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
sharedBackgroundClient = null;
|
|
26
|
+
log('Disposing background client instance');
|
|
27
|
+
|
|
28
|
+
sharedBackgroundDisposePromise = backgroundClient
|
|
29
|
+
.dispose()
|
|
30
|
+
.catch((e) => {
|
|
31
|
+
log('Error disposing background client instance:', e);
|
|
32
|
+
})
|
|
33
|
+
.finally(() => {
|
|
34
|
+
sharedBackgroundDisposePromise = null;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await sharedBackgroundDisposePromise;
|
|
38
|
+
};
|
|
39
|
+
|
|
8
40
|
/**
|
|
9
41
|
* Configuration options for TelnyxVoiceApp
|
|
10
42
|
*/
|
|
@@ -91,13 +123,19 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
91
123
|
voipClient.currentConnectionState
|
|
92
124
|
);
|
|
93
125
|
|
|
126
|
+
// Refs that mirror mutable state for use inside long-lived effects and
|
|
127
|
+
// callbacks. Reading these flags through refs (instead of the state
|
|
128
|
+
// variables) keeps the setup effect's dependency array stable so
|
|
129
|
+
// connection/call subscriptions are not torn down and recreated every
|
|
130
|
+
// time the flags change — fixing the subscription gap described in
|
|
131
|
+
// VSDK-344.
|
|
132
|
+
const isHandlingForegroundCallRef = useRef(false);
|
|
133
|
+
const processingPushOnLaunchRef = useRef(false);
|
|
134
|
+
|
|
94
135
|
// Refs for tracking state
|
|
95
136
|
const appStateRef = useRef<AppStateStatus>(AppState.currentState);
|
|
96
137
|
const backgroundDetectorIgnore = useRef(false);
|
|
97
138
|
|
|
98
|
-
// Static background client instance for singleton pattern
|
|
99
|
-
const backgroundClientRef = useRef<TelnyxVoipClient | null>(null);
|
|
100
|
-
|
|
101
139
|
const log = useCallback(
|
|
102
140
|
(message: string, ...args: any[]) => {
|
|
103
141
|
if (debug) {
|
|
@@ -115,7 +153,7 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
115
153
|
|
|
116
154
|
log(`App state changed from ${previousAppState} to ${nextAppState}`);
|
|
117
155
|
log(`Background detector ignore flag: ${backgroundDetectorIgnore.current}`);
|
|
118
|
-
log(`Handling foreground call: ${
|
|
156
|
+
log(`Handling foreground call: ${isHandlingForegroundCallRef.current}`);
|
|
119
157
|
|
|
120
158
|
// Call optional user callback first
|
|
121
159
|
onAppStateChanged?.(nextAppState);
|
|
@@ -147,13 +185,13 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
147
185
|
await handleAppResumed();
|
|
148
186
|
}
|
|
149
187
|
},
|
|
150
|
-
[enableAutoReconnect, onAppStateChanged,
|
|
188
|
+
[enableAutoReconnect, onAppStateChanged, log]
|
|
151
189
|
);
|
|
152
190
|
|
|
153
191
|
// Handle app going to background - disconnect like the old implementation
|
|
154
192
|
const handleAppBackgrounded = useCallback(async () => {
|
|
155
193
|
// Check if we should ignore background detection (e.g., during active calls)
|
|
156
|
-
if (backgroundDetectorIgnore.current ||
|
|
194
|
+
if (backgroundDetectorIgnore.current || isHandlingForegroundCallRef.current) {
|
|
157
195
|
log(
|
|
158
196
|
'Background detector ignore flag set or handling foreground call - skipping disconnection'
|
|
159
197
|
);
|
|
@@ -206,7 +244,7 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
206
244
|
} catch (e) {
|
|
207
245
|
log('Error disconnecting on background:', e);
|
|
208
246
|
}
|
|
209
|
-
}, [voipClient,
|
|
247
|
+
}, [voipClient, log]);
|
|
210
248
|
|
|
211
249
|
// Handle app resuming from background
|
|
212
250
|
const handleAppResumed = useCallback(async () => {
|
|
@@ -217,7 +255,7 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
217
255
|
await checkForInitialPushNotification(true); // Pass true for fromAppResume
|
|
218
256
|
|
|
219
257
|
// If we're ignoring (e.g., from push call) or handling foreground call, don't auto-reconnect
|
|
220
|
-
if (backgroundDetectorIgnore.current ||
|
|
258
|
+
if (backgroundDetectorIgnore.current || isHandlingForegroundCallRef.current) {
|
|
221
259
|
log(
|
|
222
260
|
'Background detector ignore flag set or handling foreground call - skipping reconnection'
|
|
223
261
|
);
|
|
@@ -248,7 +286,7 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
248
286
|
if (currentState !== TelnyxConnectionState.CONNECTED) {
|
|
249
287
|
await attemptAutoReconnection();
|
|
250
288
|
}
|
|
251
|
-
}, [voipClient,
|
|
289
|
+
}, [voipClient, log]);
|
|
252
290
|
|
|
253
291
|
// Attempt to reconnect using stored credentials
|
|
254
292
|
const attemptAutoReconnection = useCallback(async () => {
|
|
@@ -399,13 +437,14 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
399
437
|
async (fromAppResume: boolean = false) => {
|
|
400
438
|
log(`checkForInitialPushNotification called${fromAppResume ? ' (from app resume)' : ''}`);
|
|
401
439
|
|
|
402
|
-
if (
|
|
440
|
+
if (processingPushOnLaunchRef.current && !fromAppResume) {
|
|
403
441
|
log('Already processing push, returning early');
|
|
404
442
|
return;
|
|
405
443
|
}
|
|
406
444
|
|
|
407
445
|
if (!fromAppResume) {
|
|
408
446
|
setProcessingPushOnLaunch(true);
|
|
447
|
+
processingPushOnLaunchRef.current = true;
|
|
409
448
|
}
|
|
410
449
|
onPushNotificationProcessingStarted?.();
|
|
411
450
|
|
|
@@ -433,10 +472,19 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
433
472
|
// Prevent duplicate processing if already connected or connecting.
|
|
434
473
|
// Since push data is no longer cleared on read, this guard prevents
|
|
435
474
|
// re-processing when checkForInitialPushNotification fires again on app resume.
|
|
436
|
-
|
|
475
|
+
const isConnectedOrConnecting =
|
|
437
476
|
voipClient.currentConnectionState === TelnyxConnectionState.CONNECTED ||
|
|
438
|
-
voipClient.currentConnectionState === TelnyxConnectionState.CONNECTING
|
|
439
|
-
|
|
477
|
+
voipClient.currentConnectionState === TelnyxConnectionState.CONNECTING;
|
|
478
|
+
const isActiveIOSCallWaitingPush =
|
|
479
|
+
Platform.OS === 'ios' &&
|
|
480
|
+
voipClient.currentConnectionState === TelnyxConnectionState.CONNECTED &&
|
|
481
|
+
voipClient.currentCalls.some(
|
|
482
|
+
(call) =>
|
|
483
|
+
call.currentState === TelnyxCallState.ACTIVE ||
|
|
484
|
+
call.currentState === TelnyxCallState.HELD
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
if (isConnectedOrConnecting && !isActiveIOSCallWaitingPush) {
|
|
440
488
|
log(
|
|
441
489
|
`SKIPPING - Already ${voipClient.currentConnectionState}, preventing duplicate processing`
|
|
442
490
|
);
|
|
@@ -445,11 +493,12 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
445
493
|
|
|
446
494
|
// Set flags to prevent auto-reconnection during push call
|
|
447
495
|
setIsHandlingForegroundCall(true);
|
|
496
|
+
isHandlingForegroundCallRef.current = true;
|
|
448
497
|
backgroundDetectorIgnore.current = true;
|
|
449
498
|
log(`Background detector ignore set to: true at ${new Date().toISOString()}`);
|
|
450
499
|
log(`Foreground call handling flag set to: true at ${new Date().toISOString()}`);
|
|
451
500
|
|
|
452
|
-
disposeBackgroundClient();
|
|
501
|
+
await disposeBackgroundClient();
|
|
453
502
|
|
|
454
503
|
// On iOS, coordinate with CallKit using the call_id from push metadata
|
|
455
504
|
if (Platform.OS === 'ios') {
|
|
@@ -457,10 +506,20 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
457
506
|
if (callId) {
|
|
458
507
|
const { callKitCoordinator } = require('./callkit/callkit-coordinator');
|
|
459
508
|
log('Notifying CallKit coordinator about push notification:', callId);
|
|
460
|
-
await callKitCoordinator.handleCallKitPushReceived(callId, {
|
|
509
|
+
const processed = await callKitCoordinator.handleCallKitPushReceived(callId, {
|
|
461
510
|
callData: { source: 'push_notification' },
|
|
462
511
|
pushData: pushData,
|
|
463
512
|
});
|
|
513
|
+
if (!processed) {
|
|
514
|
+
// The push was filtered, rejected, or otherwise ignored by
|
|
515
|
+
// CallKit. Reset the foreground-push lifecycle flags so the
|
|
516
|
+
// app does not stay in a state that skips background
|
|
517
|
+
// disconnect/reconnect handling.
|
|
518
|
+
log('CallKit push was not processed - resetting foreground flags');
|
|
519
|
+
setIsHandlingForegroundCall(false);
|
|
520
|
+
isHandlingForegroundCallRef.current = false;
|
|
521
|
+
backgroundDetectorIgnore.current = false;
|
|
522
|
+
}
|
|
464
523
|
} else {
|
|
465
524
|
log('No call_id found in push data, falling back to direct handling');
|
|
466
525
|
await voipClient.handlePushNotification(pushData);
|
|
@@ -473,27 +532,19 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
473
532
|
} catch (e) {
|
|
474
533
|
log('Error processing initial push notification:', e);
|
|
475
534
|
setIsHandlingForegroundCall(false);
|
|
535
|
+
isHandlingForegroundCallRef.current = false;
|
|
476
536
|
} finally {
|
|
477
537
|
setProcessingPushOnLaunch(false);
|
|
538
|
+
processingPushOnLaunchRef.current = false;
|
|
478
539
|
onPushNotificationProcessingCompleted?.();
|
|
479
540
|
}
|
|
480
541
|
},
|
|
481
|
-
[
|
|
482
|
-
processingPushOnLaunch,
|
|
483
|
-
voipClient,
|
|
484
|
-
onPushNotificationProcessingStarted,
|
|
485
|
-
onPushNotificationProcessingCompleted,
|
|
486
|
-
log,
|
|
487
|
-
]
|
|
542
|
+
[voipClient, onPushNotificationProcessingStarted, onPushNotificationProcessingCompleted, log]
|
|
488
543
|
);
|
|
489
544
|
|
|
490
545
|
// Dispose background client instance when no longer needed
|
|
491
|
-
const disposeBackgroundClient = useCallback(() => {
|
|
492
|
-
|
|
493
|
-
log('Disposing background client instance');
|
|
494
|
-
backgroundClientRef.current.dispose();
|
|
495
|
-
backgroundClientRef.current = null;
|
|
496
|
-
}
|
|
546
|
+
const disposeBackgroundClient = useCallback(async (): Promise<void> => {
|
|
547
|
+
await disposeSharedBackgroundClient(log);
|
|
497
548
|
}, [log]);
|
|
498
549
|
|
|
499
550
|
// Create background client for push notification handling
|
|
@@ -551,18 +602,19 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
551
602
|
}
|
|
552
603
|
|
|
553
604
|
log(
|
|
554
|
-
`Flag reset check: WebRTC calls=${calls.length}, CallKit processing=${hasCallKitProcessing}, isHandlingForegroundCall=${
|
|
605
|
+
`Flag reset check: WebRTC calls=${calls.length}, CallKit processing=${hasCallKitProcessing}, isHandlingForegroundCall=${isHandlingForegroundCallRef.current}, backgroundDetectorIgnore=${backgroundDetectorIgnore.current}`
|
|
555
606
|
);
|
|
556
607
|
|
|
557
608
|
if (
|
|
558
609
|
!hasActiveWebRTCCalls &&
|
|
559
610
|
!hasCallKitProcessing &&
|
|
560
|
-
(
|
|
611
|
+
(isHandlingForegroundCallRef.current || backgroundDetectorIgnore.current)
|
|
561
612
|
) {
|
|
562
613
|
log(
|
|
563
614
|
`No active calls and no CallKit processing - resetting ignore flags at ${new Date().toISOString()}`
|
|
564
615
|
);
|
|
565
616
|
setIsHandlingForegroundCall(false);
|
|
617
|
+
isHandlingForegroundCallRef.current = false;
|
|
566
618
|
backgroundDetectorIgnore.current = false;
|
|
567
619
|
} else if (!hasActiveWebRTCCalls && hasCallKitProcessing) {
|
|
568
620
|
log(
|
|
@@ -574,9 +626,10 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
574
626
|
|
|
575
627
|
// Also reset processingPushOnLaunch if no calls are active
|
|
576
628
|
// This ensures the flag doesn't get stuck after call ends
|
|
577
|
-
if (calls.length === 0 &&
|
|
629
|
+
if (calls.length === 0 && processingPushOnLaunchRef.current) {
|
|
578
630
|
log('No active calls - resetting processing push flag');
|
|
579
631
|
setProcessingPushOnLaunch(false);
|
|
632
|
+
processingPushOnLaunchRef.current = false;
|
|
580
633
|
}
|
|
581
634
|
});
|
|
582
635
|
|
|
@@ -628,7 +681,7 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
628
681
|
// Handle initial push notification if app was launched from terminated state
|
|
629
682
|
// Only check if we're not already processing to prevent infinite loops
|
|
630
683
|
const timeoutId = setTimeout(() => {
|
|
631
|
-
if (!
|
|
684
|
+
if (!processingPushOnLaunchRef.current) {
|
|
632
685
|
checkForInitialPushNotification();
|
|
633
686
|
}
|
|
634
687
|
}, 100);
|
|
@@ -651,16 +704,9 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
651
704
|
}
|
|
652
705
|
clearTimeout(timeoutId);
|
|
653
706
|
// Clean up background client instance
|
|
654
|
-
disposeBackgroundClient();
|
|
707
|
+
void disposeBackgroundClient();
|
|
655
708
|
};
|
|
656
|
-
}, [
|
|
657
|
-
voipClient,
|
|
658
|
-
handleAppStateChange,
|
|
659
|
-
disposeBackgroundClient,
|
|
660
|
-
skipWebBackgroundDetection,
|
|
661
|
-
isHandlingForegroundCall,
|
|
662
|
-
log,
|
|
663
|
-
]);
|
|
709
|
+
}, [voipClient, handleAppStateChange, disposeBackgroundClient, skipWebBackgroundDetection, log]);
|
|
664
710
|
|
|
665
711
|
// Simply return the children wrapped in context provider - all lifecycle management is handled internally
|
|
666
712
|
return <TelnyxVoiceProvider voipClient={voipClient}>{children}</TelnyxVoiceProvider>;
|
|
@@ -769,23 +815,28 @@ const initializeAndCreate = async (options: {
|
|
|
769
815
|
*/
|
|
770
816
|
const handleBackgroundPush = async (message: any): Promise<void> => {
|
|
771
817
|
console.log('[TelnyxVoiceApp] Background push received:', message);
|
|
818
|
+
let backgroundClient: TelnyxVoipClient | null = null;
|
|
772
819
|
|
|
773
820
|
try {
|
|
774
821
|
// TODO: Initialize push notification service in isolate if needed
|
|
775
822
|
|
|
823
|
+
await disposeSharedBackgroundClient(console.log);
|
|
824
|
+
|
|
776
825
|
// Use singleton pattern for background client to prevent multiple instances
|
|
777
|
-
|
|
826
|
+
backgroundClient = createBackgroundTelnyxVoipClient({
|
|
778
827
|
debug: false,
|
|
779
828
|
});
|
|
829
|
+
sharedBackgroundClient = backgroundClient;
|
|
780
830
|
|
|
781
831
|
await backgroundClient.handlePushNotification(message);
|
|
782
832
|
|
|
783
833
|
console.log('[TelnyxVoiceApp] Background push processed successfully');
|
|
784
|
-
|
|
785
|
-
// Clean up the background client
|
|
786
|
-
backgroundClient.dispose();
|
|
787
834
|
} catch (e) {
|
|
788
835
|
console.log('[TelnyxVoiceApp] Error processing background push:', e);
|
|
836
|
+
} finally {
|
|
837
|
+
if (backgroundClient) {
|
|
838
|
+
await disposeSharedBackgroundClient(console.log, backgroundClient);
|
|
839
|
+
}
|
|
789
840
|
}
|
|
790
841
|
};
|
|
791
842
|
|