@telnyx/react-voice-commons-sdk 0.1.7-beta.1 → 0.1.8-beta.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 +27 -0
- package/README.md +35 -5
- package/lib/callkit/callkit-coordinator.js +31 -1
- package/lib/internal/calls/call-state-controller.js +2 -2
- package/lib/internal/session/session-manager.d.ts +3 -1
- package/lib/internal/session/session-manager.js +7 -4
- package/lib/models/call.d.ts +10 -0
- package/lib/models/call.js +14 -0
- package/lib/telnyx-voice-app.js +17 -5
- package/package.json +1 -1
- package/src/callkit/callkit-coordinator.ts +34 -1
- package/src/internal/calls/call-state-controller.ts +1 -1
- package/src/internal/session/session-manager.ts +7 -4
- package/src/models/call.ts +16 -0
- package/src/telnyx-voice-app.tsx +16 -5
- package/src/types/telnyx-sdk.d.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# CHANGELOG.md
|
|
2
2
|
|
|
3
|
+
## [0.1.7](https://github.com/team-telnyx/react-native-voice-commons/releases/tag/0.1.7) (2026-02-20)
|
|
4
|
+
|
|
5
|
+
### Enhancement
|
|
6
|
+
|
|
7
|
+
• Added `TelnyxVoipClient.isLaunchedFromPushNotification()` static method to check if the app was cold-started from a push notification, allowing consumers to skip auto-login and avoid double-login races
|
|
8
|
+
• `createTelnyxVoipClient()` now returns a singleton — safe to call inside React component bodies without creating a new instance on every render
|
|
9
|
+
• Added `destroyTelnyxVoipClient()` to tear down the singleton when a fresh instance is needed
|
|
10
|
+
• `TelnyxVoiceApp` now automatically wires the `voipClient` on the CallKit coordinator on mount — consumers no longer need to manually call `setVoipClient()` at the correct component level
|
|
11
|
+
|
|
12
|
+
### Bug Fixing
|
|
13
|
+
|
|
14
|
+
• Fixed cold-start push notification failures caused by double-login race between user auto-login and SDK internal push login
|
|
15
|
+
• Fixed CallKit coordinator having no `voipClient` reference when user answered a call via CallKit before navigating to the correct screen
|
|
16
|
+
• Fixed `call_id` extraction in `checkForInitialPushNotification` — the double-nested path `pushData.metadata?.metadata?.call_id` never resolved, so the CallKit coordinator was bypassed on iOS
|
|
17
|
+
• Refactored `checkForInitialPushNotification` into `getAndroidPushData` and `getIOSPushData` helpers to reduce nesting and improve readability
|
|
18
|
+
|
|
19
|
+
### Deprecation
|
|
20
|
+
|
|
21
|
+
• `setVoipClient()` on `CallKitCoordinator` and `useCallKitCoordinator()` hook is now deprecated — `TelnyxVoiceApp` handles this automatically
|
|
22
|
+
|
|
23
|
+
## [0.1.7-beta.0](https://github.com/team-telnyx/react-native-voice-commons/releases/tag/0.1.7-beta.0) (2026-02-18)
|
|
24
|
+
|
|
25
|
+
### Bug Fixing
|
|
26
|
+
|
|
27
|
+
• Fixed `call_id` extraction in `checkForInitialPushNotification` — the double-nested path `pushData.metadata?.metadata?.call_id` never resolved, so the CallKit coordinator was bypassed and all iOS push calls fell through to direct handling
|
|
28
|
+
• Refactored `checkForInitialPushNotification` into `getAndroidPushData` and `getIOSPushData` helpers to reduce nesting and improve readability
|
|
29
|
+
|
|
3
30
|
## [0.1.6](https://github.com/team-telnyx/react-native-voice-commons/releases/tag/0.1.6) (2025-12-09)
|
|
4
31
|
|
|
5
32
|
### Enhancement
|
package/README.md
CHANGED
|
@@ -32,15 +32,30 @@ The `@telnyx/react-voice-commons-sdk` library provides:
|
|
|
32
32
|
Integrate the library using the `TelnyxVoiceApp` component for automatic lifecycle management:
|
|
33
33
|
|
|
34
34
|
```tsx
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
TelnyxVoiceApp,
|
|
37
|
+
TelnyxVoipClient,
|
|
38
|
+
createTelnyxVoipClient,
|
|
39
|
+
} from '@telnyx/react-voice-commons-sdk';
|
|
36
40
|
|
|
37
|
-
// Create the VoIP client instance
|
|
41
|
+
// Create the VoIP client instance (singleton — safe to call inside a component body)
|
|
38
42
|
const voipClient = createTelnyxVoipClient({
|
|
39
43
|
enableAppStateManagement: true, // Optional: Enable automatic app state management (default: true)
|
|
40
44
|
debug: true, // Optional: Enable debug logging
|
|
41
45
|
});
|
|
42
46
|
|
|
43
47
|
export default function App() {
|
|
48
|
+
// Skip auto-login if the app was launched from a push notification —
|
|
49
|
+
// the SDK handles login internally via the push notification flow.
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
TelnyxVoipClient.isLaunchedFromPushNotification().then((isFromPush) => {
|
|
52
|
+
if (!isFromPush) {
|
|
53
|
+
// Safe to auto-login
|
|
54
|
+
voipClient.loginFromStoredConfig();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
44
59
|
return (
|
|
45
60
|
<TelnyxVoiceApp voipClient={voipClient} enableAutoReconnect={false} debug={true}>
|
|
46
61
|
<YourAppContent />
|
|
@@ -54,10 +69,16 @@ export default function App() {
|
|
|
54
69
|
### 1. VoIP Client Configuration
|
|
55
70
|
|
|
56
71
|
```tsx
|
|
72
|
+
// createTelnyxVoipClient is a singleton — repeated calls return the same instance.
|
|
73
|
+
// This makes it safe to call inside a React component body without re-creating on every render.
|
|
57
74
|
const voipClient = createTelnyxVoipClient({
|
|
58
75
|
enableAppStateManagement: true, // Optional: Enable automatic app state management (default: true)
|
|
59
76
|
debug: true, // Optional: Enable debug logging
|
|
60
77
|
});
|
|
78
|
+
|
|
79
|
+
// If you need to tear down and recreate the client (e.g., on logout):
|
|
80
|
+
import { destroyTelnyxVoipClient } from '@telnyx/react-voice-commons-sdk';
|
|
81
|
+
destroyTelnyxVoipClient(); // Disposes the singleton; next createTelnyxVoipClient() call creates a fresh instance
|
|
61
82
|
```
|
|
62
83
|
|
|
63
84
|
**Configuration Options Explained:**
|
|
@@ -73,6 +94,7 @@ The `TelnyxVoiceApp` component handles:
|
|
|
73
94
|
- Push notification processing from terminated state
|
|
74
95
|
- Login state management with automatic reconnection
|
|
75
96
|
- Background client management for push notifications
|
|
97
|
+
- **Automatic CallKit coordinator wiring** — the `voipClient` is set on the CallKit coordinator on mount, so you don't need to call `setVoipClient()` manually
|
|
76
98
|
|
|
77
99
|
### 3. Reactive State Management
|
|
78
100
|
|
|
@@ -416,9 +438,18 @@ npx expo run:ios
|
|
|
416
438
|
|
|
417
439
|
### Common Integration Issues
|
|
418
440
|
|
|
419
|
-
### Double Login
|
|
441
|
+
### Double Login on Cold-Start
|
|
442
|
+
|
|
443
|
+
When the app is launched from a push notification, the SDK handles login internally. If your app also auto-logs in on mount, both will race and the push flow breaks. Use `isLaunchedFromPushNotification()` to guard your auto-login:
|
|
444
|
+
|
|
445
|
+
```tsx
|
|
446
|
+
const isFromPush = await TelnyxVoipClient.isLaunchedFromPushNotification();
|
|
447
|
+
if (!isFromPush) {
|
|
448
|
+
voipClient.loginFromStoredConfig();
|
|
449
|
+
}
|
|
450
|
+
```
|
|
420
451
|
|
|
421
|
-
|
|
452
|
+
Also ensure you're not calling login methods manually when using `TelnyxVoiceApp` with auto-reconnection enabled.
|
|
422
453
|
|
|
423
454
|
### Background Disconnection
|
|
424
455
|
|
|
@@ -457,7 +488,6 @@ useEffect(() => {
|
|
|
457
488
|
const subscription = voipClient.connectionState$.subscribe(handleStateChange);
|
|
458
489
|
return () => subscription.unsubscribe();
|
|
459
490
|
}, []);
|
|
460
|
-
}, []);
|
|
461
491
|
```
|
|
462
492
|
|
|
463
493
|
## Documentation
|
|
@@ -303,10 +303,21 @@ class CallKitCoordinator {
|
|
|
303
303
|
} else {
|
|
304
304
|
console.log('CallKitCoordinator: Outgoing call, skipping answer and CONNECTING state');
|
|
305
305
|
}
|
|
306
|
+
// Clear push data now that answer action is fulfilled
|
|
307
|
+
try {
|
|
308
|
+
await voice_pn_bridge_1.VoicePnBridge.clearPendingVoipPush();
|
|
309
|
+
console.log('CallKitCoordinator: Cleared pending VoIP push after answer fulfilled');
|
|
310
|
+
} catch (clearErr) {
|
|
311
|
+
console.error('CallKitCoordinator: Error clearing push data after answer:', clearErr);
|
|
312
|
+
}
|
|
306
313
|
} catch (error) {
|
|
307
314
|
console.error('CallKitCoordinator: Error processing CallKit answer', error);
|
|
308
315
|
await callkit_1.default.reportCallEnded(callKitUUID, callkit_1.CallEndReason.Failed);
|
|
309
316
|
this.cleanupCall(callKitUUID);
|
|
317
|
+
// Clear push data even on error to prevent stale state
|
|
318
|
+
try {
|
|
319
|
+
await voice_pn_bridge_1.VoicePnBridge.clearPendingVoipPush();
|
|
320
|
+
} catch (_) {}
|
|
310
321
|
} finally {
|
|
311
322
|
this.processingCalls.delete(callKitUUID);
|
|
312
323
|
}
|
|
@@ -352,6 +363,11 @@ class CallKitCoordinator {
|
|
|
352
363
|
} finally {
|
|
353
364
|
this.processingCalls.delete(callKitUUID);
|
|
354
365
|
this.cleanupCall(callKitUUID);
|
|
366
|
+
// Clear push data now that end action is fulfilled
|
|
367
|
+
try {
|
|
368
|
+
await voice_pn_bridge_1.VoicePnBridge.clearPendingVoipPush();
|
|
369
|
+
console.log('CallKitCoordinator: Cleared pending VoIP push after end fulfilled');
|
|
370
|
+
} catch (_) {}
|
|
355
371
|
// Check if app is in background and no more calls - disconnect client
|
|
356
372
|
await this.checkBackgroundDisconnection();
|
|
357
373
|
}
|
|
@@ -506,6 +522,11 @@ class CallKitCoordinator {
|
|
|
506
522
|
// Set the pending push action to be handled when app comes to foreground
|
|
507
523
|
await voice_pn_bridge_1.VoicePnBridge.setPendingPushAction(pushAction, pushMetadata);
|
|
508
524
|
console.log('CallKitCoordinator: ✅ Set pending push action');
|
|
525
|
+
// Clear push data now that push notification answer is handled
|
|
526
|
+
try {
|
|
527
|
+
await voice_pn_bridge_1.VoicePnBridge.clearPendingVoipPush();
|
|
528
|
+
console.log('CallKitCoordinator: Cleared pending VoIP push after push answer handled');
|
|
529
|
+
} catch (_) {}
|
|
509
530
|
return;
|
|
510
531
|
}
|
|
511
532
|
// For other platforms (shouldn't happen on iOS)
|
|
@@ -533,6 +554,11 @@ class CallKitCoordinator {
|
|
|
533
554
|
this.voipClient.queueEndFromCallKit();
|
|
534
555
|
// Clean up push notification state
|
|
535
556
|
await this.cleanupPushNotificationState();
|
|
557
|
+
// Clear push data now that rejection is handled
|
|
558
|
+
try {
|
|
559
|
+
await voice_pn_bridge_1.VoicePnBridge.clearPendingVoipPush();
|
|
560
|
+
console.log('CallKitCoordinator: Cleared pending VoIP push after rejection handled');
|
|
561
|
+
} catch (_) {}
|
|
536
562
|
console.log('CallKitCoordinator: 🎯 Push notification rejection handling complete');
|
|
537
563
|
return;
|
|
538
564
|
}
|
|
@@ -738,7 +764,11 @@ class CallKitCoordinator {
|
|
|
738
764
|
* This helps prevent premature flag resets during CallKit operations
|
|
739
765
|
*/
|
|
740
766
|
hasProcessingCalls() {
|
|
741
|
-
return this
|
|
767
|
+
// Also return true when isCallFromPush is set — this prevents the
|
|
768
|
+
// calls$ subscription in TelnyxVoiceApp from resetting protection flags
|
|
769
|
+
// (isHandlingForegroundCall, backgroundDetectorIgnore) before the WebRTC
|
|
770
|
+
// call arrives during push notification handling.
|
|
771
|
+
return this.processingCalls.size > 0 || this.isCallFromPush;
|
|
742
772
|
}
|
|
743
773
|
/**
|
|
744
774
|
* Check if there's currently a call from push notification being processed
|
|
@@ -326,8 +326,8 @@ class CallStateController {
|
|
|
326
326
|
console.log('CallStateController: Reporting incoming call to CallKitCoordinator');
|
|
327
327
|
callkit_coordinator_1.callKitCoordinator.reportIncomingCall(
|
|
328
328
|
telnyxCall,
|
|
329
|
-
call.
|
|
330
|
-
call.
|
|
329
|
+
call.callerName,
|
|
330
|
+
call.callerNumber
|
|
331
331
|
);
|
|
332
332
|
} else {
|
|
333
333
|
// Handle outgoing call with CallKit
|
|
@@ -49,7 +49,9 @@ export declare class SessionManager {
|
|
|
49
49
|
*/
|
|
50
50
|
disconnect(): Promise<void>;
|
|
51
51
|
/**
|
|
52
|
-
* Disable push notifications for the current session
|
|
52
|
+
* Disable push notifications for the current session.
|
|
53
|
+
* Delegates to the TelnyxRTC client's disablePushNotification() method
|
|
54
|
+
* which sends a 'telnyx_rtc.disable_push_notification' message via the socket.
|
|
53
55
|
*/
|
|
54
56
|
disablePushNotifications(): void;
|
|
55
57
|
/**
|
|
@@ -140,16 +140,19 @@ class SessionManager {
|
|
|
140
140
|
this._connectionState.next(connection_state_1.TelnyxConnectionState.DISCONNECTED);
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
143
|
-
* Disable push notifications for the current session
|
|
143
|
+
* Disable push notifications for the current session.
|
|
144
|
+
* Delegates to the TelnyxRTC client's disablePushNotification() method
|
|
145
|
+
* which sends a 'telnyx_rtc.disable_push_notification' message via the socket.
|
|
144
146
|
*/
|
|
145
147
|
disablePushNotifications() {
|
|
146
148
|
if (
|
|
147
149
|
this._telnyxClient &&
|
|
148
150
|
this.currentState === connection_state_1.TelnyxConnectionState.CONNECTED
|
|
149
151
|
) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
console.log('SessionManager: Disabling push notifications for session:', this._sessionId);
|
|
153
|
+
this._telnyxClient.disablePushNotification();
|
|
154
|
+
} else {
|
|
155
|
+
console.warn('SessionManager: Cannot disable push - client not connected');
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
/**
|
package/lib/models/call.d.ts
CHANGED
|
@@ -46,6 +46,16 @@ export declare class Call {
|
|
|
46
46
|
* Whether this is an outgoing call
|
|
47
47
|
*/
|
|
48
48
|
get isOutgoing(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The original caller name (from_display_name) received in the INVITE message.
|
|
51
|
+
* Falls back to destination if not available.
|
|
52
|
+
*/
|
|
53
|
+
get callerName(): string;
|
|
54
|
+
/**
|
|
55
|
+
* The original caller number received in the INVITE message.
|
|
56
|
+
* Falls back to destination if not available.
|
|
57
|
+
*/
|
|
58
|
+
get callerNumber(): string;
|
|
49
59
|
/**
|
|
50
60
|
* Current call state (synchronous access)
|
|
51
61
|
*/
|
package/lib/models/call.js
CHANGED
|
@@ -115,6 +115,20 @@ class Call {
|
|
|
115
115
|
get isOutgoing() {
|
|
116
116
|
return !this._isIncoming;
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* The original caller name (from_display_name) received in the INVITE message.
|
|
120
|
+
* Falls back to destination if not available.
|
|
121
|
+
*/
|
|
122
|
+
get callerName() {
|
|
123
|
+
return this._originalCallerName || this._destination;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The original caller number received in the INVITE message.
|
|
127
|
+
* Falls back to destination if not available.
|
|
128
|
+
*/
|
|
129
|
+
get callerNumber() {
|
|
130
|
+
return this._originalCallerNumber || this._destination;
|
|
131
|
+
}
|
|
118
132
|
/**
|
|
119
133
|
* Current call state (synchronous access)
|
|
120
134
|
*/
|
package/lib/telnyx-voice-app.js
CHANGED
|
@@ -274,8 +274,14 @@ const TelnyxVoiceAppComponent = ({
|
|
|
274
274
|
return null;
|
|
275
275
|
}
|
|
276
276
|
log('Found pending VoIP push data:', voipPayload);
|
|
277
|
-
|
|
278
|
-
|
|
277
|
+
// Do NOT clear push data here. Let it persist until the answer/end action
|
|
278
|
+
// is fulfilled in the CallKit coordinator. This prevents a race condition in
|
|
279
|
+
// Expo apps where the RN bridge mounts immediately on push notification —
|
|
280
|
+
// the push data would be consumed and cleared before the user answers,
|
|
281
|
+
// leaving the coordinator with nothing to work with.
|
|
282
|
+
// For non-Expo apps (RN mounts after answer), the coordinator's
|
|
283
|
+
// handlePushNotificationAnswer/Reject clears the data before
|
|
284
|
+
// checkForInitialPushNotification ever runs, so no loop occurs.
|
|
279
285
|
return { action: 'incoming_call', metadata: voipPayload.metadata, from_notification: true };
|
|
280
286
|
} catch (parseError) {
|
|
281
287
|
log('Error parsing VoIP push JSON:', parseError);
|
|
@@ -315,11 +321,17 @@ const TelnyxVoiceAppComponent = ({
|
|
|
315
321
|
return;
|
|
316
322
|
}
|
|
317
323
|
log('Processing initial push notification...');
|
|
318
|
-
// Prevent duplicate processing if already connected
|
|
324
|
+
// Prevent duplicate processing if already connected or connecting.
|
|
325
|
+
// Since push data is no longer cleared on read, this guard prevents
|
|
326
|
+
// re-processing when checkForInitialPushNotification fires again on app resume.
|
|
319
327
|
if (
|
|
320
|
-
voipClient.currentConnectionState ===
|
|
328
|
+
voipClient.currentConnectionState ===
|
|
329
|
+
connection_state_1.TelnyxConnectionState.CONNECTED ||
|
|
330
|
+
voipClient.currentConnectionState === connection_state_1.TelnyxConnectionState.CONNECTING
|
|
321
331
|
) {
|
|
322
|
-
log(
|
|
332
|
+
log(
|
|
333
|
+
`SKIPPING - Already ${voipClient.currentConnectionState}, preventing duplicate processing`
|
|
334
|
+
);
|
|
323
335
|
return;
|
|
324
336
|
}
|
|
325
337
|
// Set flags to prevent auto-reconnection during push call
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telnyx/react-voice-commons-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8-beta.0",
|
|
4
4
|
"description": "A high-level, state-agnostic, drop-in module for the Telnyx React Native SDK that simplifies WebRTC voice calling integration",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/index.js",
|
|
@@ -306,10 +306,21 @@ class CallKitCoordinator {
|
|
|
306
306
|
} else {
|
|
307
307
|
console.log('CallKitCoordinator: Outgoing call, skipping answer and CONNECTING state');
|
|
308
308
|
}
|
|
309
|
+
// Clear push data now that answer action is fulfilled
|
|
310
|
+
try {
|
|
311
|
+
await VoicePnBridge.clearPendingVoipPush();
|
|
312
|
+
console.log('CallKitCoordinator: Cleared pending VoIP push after answer fulfilled');
|
|
313
|
+
} catch (clearErr) {
|
|
314
|
+
console.error('CallKitCoordinator: Error clearing push data after answer:', clearErr);
|
|
315
|
+
}
|
|
309
316
|
} catch (error) {
|
|
310
317
|
console.error('CallKitCoordinator: Error processing CallKit answer', error);
|
|
311
318
|
await CallKit.reportCallEnded(callKitUUID, CallEndReason.Failed);
|
|
312
319
|
this.cleanupCall(callKitUUID);
|
|
320
|
+
// Clear push data even on error to prevent stale state
|
|
321
|
+
try {
|
|
322
|
+
await VoicePnBridge.clearPendingVoipPush();
|
|
323
|
+
} catch (_) {}
|
|
313
324
|
} finally {
|
|
314
325
|
this.processingCalls.delete(callKitUUID);
|
|
315
326
|
}
|
|
@@ -366,6 +377,12 @@ class CallKitCoordinator {
|
|
|
366
377
|
this.processingCalls.delete(callKitUUID);
|
|
367
378
|
this.cleanupCall(callKitUUID);
|
|
368
379
|
|
|
380
|
+
// Clear push data now that end action is fulfilled
|
|
381
|
+
try {
|
|
382
|
+
await VoicePnBridge.clearPendingVoipPush();
|
|
383
|
+
console.log('CallKitCoordinator: Cleared pending VoIP push after end fulfilled');
|
|
384
|
+
} catch (_) {}
|
|
385
|
+
|
|
369
386
|
// Check if app is in background and no more calls - disconnect client
|
|
370
387
|
await this.checkBackgroundDisconnection();
|
|
371
388
|
}
|
|
@@ -544,6 +561,12 @@ class CallKitCoordinator {
|
|
|
544
561
|
await VoicePnBridge.setPendingPushAction(pushAction, pushMetadata);
|
|
545
562
|
console.log('CallKitCoordinator: ✅ Set pending push action');
|
|
546
563
|
|
|
564
|
+
// Clear push data now that push notification answer is handled
|
|
565
|
+
try {
|
|
566
|
+
await VoicePnBridge.clearPendingVoipPush();
|
|
567
|
+
console.log('CallKitCoordinator: Cleared pending VoIP push after push answer handled');
|
|
568
|
+
} catch (_) {}
|
|
569
|
+
|
|
547
570
|
return;
|
|
548
571
|
}
|
|
549
572
|
|
|
@@ -577,6 +600,12 @@ class CallKitCoordinator {
|
|
|
577
600
|
// Clean up push notification state
|
|
578
601
|
await this.cleanupPushNotificationState();
|
|
579
602
|
|
|
603
|
+
// Clear push data now that rejection is handled
|
|
604
|
+
try {
|
|
605
|
+
await VoicePnBridge.clearPendingVoipPush();
|
|
606
|
+
console.log('CallKitCoordinator: Cleared pending VoIP push after rejection handled');
|
|
607
|
+
} catch (_) {}
|
|
608
|
+
|
|
580
609
|
console.log('CallKitCoordinator: 🎯 Push notification rejection handling complete');
|
|
581
610
|
return;
|
|
582
611
|
}
|
|
@@ -815,7 +844,11 @@ class CallKitCoordinator {
|
|
|
815
844
|
* This helps prevent premature flag resets during CallKit operations
|
|
816
845
|
*/
|
|
817
846
|
hasProcessingCalls(): boolean {
|
|
818
|
-
return this
|
|
847
|
+
// Also return true when isCallFromPush is set — this prevents the
|
|
848
|
+
// calls$ subscription in TelnyxVoiceApp from resetting protection flags
|
|
849
|
+
// (isHandlingForegroundCall, backgroundDetectorIgnore) before the WebRTC
|
|
850
|
+
// call arrives during push notification handling.
|
|
851
|
+
return this.processingCalls.size > 0 || this.isCallFromPush;
|
|
819
852
|
}
|
|
820
853
|
|
|
821
854
|
/**
|
|
@@ -392,7 +392,7 @@ export class CallStateController {
|
|
|
392
392
|
} else if (call.isIncoming) {
|
|
393
393
|
// Handle incoming call with CallKit (only if not already integrated)
|
|
394
394
|
console.log('CallStateController: Reporting incoming call to CallKitCoordinator');
|
|
395
|
-
callKitCoordinator.reportIncomingCall(telnyxCall, call.
|
|
395
|
+
callKitCoordinator.reportIncomingCall(telnyxCall, call.callerName, call.callerNumber);
|
|
396
396
|
} else {
|
|
397
397
|
// Handle outgoing call with CallKit
|
|
398
398
|
console.log('CallStateController: Starting outgoing call with CallKitCoordinator');
|
|
@@ -111,13 +111,16 @@ export class SessionManager {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
* Disable push notifications for the current session
|
|
114
|
+
* Disable push notifications for the current session.
|
|
115
|
+
* Delegates to the TelnyxRTC client's disablePushNotification() method
|
|
116
|
+
* which sends a 'telnyx_rtc.disable_push_notification' message via the socket.
|
|
115
117
|
*/
|
|
116
118
|
disablePushNotifications(): void {
|
|
117
119
|
if (this._telnyxClient && this.currentState === TelnyxConnectionState.CONNECTED) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
console.log('SessionManager: Disabling push notifications for session:', this._sessionId);
|
|
121
|
+
this._telnyxClient.disablePushNotification();
|
|
122
|
+
} else {
|
|
123
|
+
console.warn('SessionManager: Cannot disable push - client not connected');
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
|
package/src/models/call.ts
CHANGED
|
@@ -66,6 +66,22 @@ export class Call {
|
|
|
66
66
|
return !this._isIncoming;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* The original caller name (from_display_name) received in the INVITE message.
|
|
71
|
+
* Falls back to destination if not available.
|
|
72
|
+
*/
|
|
73
|
+
get callerName(): string {
|
|
74
|
+
return this._originalCallerName || this._destination;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The original caller number received in the INVITE message.
|
|
79
|
+
* Falls back to destination if not available.
|
|
80
|
+
*/
|
|
81
|
+
get callerNumber(): string {
|
|
82
|
+
return this._originalCallerNumber || this._destination;
|
|
83
|
+
}
|
|
84
|
+
|
|
69
85
|
/**
|
|
70
86
|
* Current call state (synchronous access)
|
|
71
87
|
*/
|
package/src/telnyx-voice-app.tsx
CHANGED
|
@@ -374,8 +374,14 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
374
374
|
|
|
375
375
|
log('Found pending VoIP push data:', voipPayload);
|
|
376
376
|
|
|
377
|
-
|
|
378
|
-
|
|
377
|
+
// Do NOT clear push data here. Let it persist until the answer/end action
|
|
378
|
+
// is fulfilled in the CallKit coordinator. This prevents a race condition in
|
|
379
|
+
// Expo apps where the RN bridge mounts immediately on push notification —
|
|
380
|
+
// the push data would be consumed and cleared before the user answers,
|
|
381
|
+
// leaving the coordinator with nothing to work with.
|
|
382
|
+
// For non-Expo apps (RN mounts after answer), the coordinator's
|
|
383
|
+
// handlePushNotificationAnswer/Reject clears the data before
|
|
384
|
+
// checkForInitialPushNotification ever runs, so no loop occurs.
|
|
379
385
|
|
|
380
386
|
return { action: 'incoming_call', metadata: voipPayload.metadata, from_notification: true };
|
|
381
387
|
} catch (parseError) {
|
|
@@ -424,9 +430,14 @@ const TelnyxVoiceAppComponent: React.FC<TelnyxVoiceAppProps> = ({
|
|
|
424
430
|
|
|
425
431
|
log('Processing initial push notification...');
|
|
426
432
|
|
|
427
|
-
// Prevent duplicate processing if already connected
|
|
428
|
-
|
|
429
|
-
|
|
433
|
+
// Prevent duplicate processing if already connected or connecting.
|
|
434
|
+
// Since push data is no longer cleared on read, this guard prevents
|
|
435
|
+
// re-processing when checkForInitialPushNotification fires again on app resume.
|
|
436
|
+
if (
|
|
437
|
+
voipClient.currentConnectionState === TelnyxConnectionState.CONNECTED ||
|
|
438
|
+
voipClient.currentConnectionState === TelnyxConnectionState.CONNECTING
|
|
439
|
+
) {
|
|
440
|
+
log(`SKIPPING - Already ${voipClient.currentConnectionState}, preventing duplicate processing`);
|
|
430
441
|
return;
|
|
431
442
|
}
|
|
432
443
|
|
|
@@ -83,6 +83,7 @@ declare module '@telnyx/react-native-voice-sdk' {
|
|
|
83
83
|
connect(): Promise<void>;
|
|
84
84
|
disconnect(): void;
|
|
85
85
|
newCall(options: CallOptions): Promise<Call>;
|
|
86
|
+
disablePushNotification(): void;
|
|
86
87
|
|
|
87
88
|
on(event: string, listener: (...args: any[]) => void): this;
|
|
88
89
|
off(event: string, listener: (...args: any[]) => void): this;
|