@telnyx/react-voice-commons-sdk 0.1.5 → 0.1.7-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.
Files changed (26) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +24 -24
  3. package/android/build.gradle +45 -0
  4. package/android/src/main/AndroidManifest.xml +38 -0
  5. package/android/src/main/java/com/telnyx/react_voice_commons/CallForegroundService.kt +83 -0
  6. package/android/src/main/java/com/telnyx/react_voice_commons/TelnyxFirebaseMessagingService.kt +179 -0
  7. package/android/src/main/java/com/telnyx/react_voice_commons/TelnyxMainActivity.kt +216 -0
  8. package/android/src/main/java/com/telnyx/react_voice_commons/TelnyxNotificationActionReceiver.kt +177 -0
  9. package/android/src/main/java/com/telnyx/react_voice_commons/TelnyxNotificationHelper.kt +277 -0
  10. package/android/src/main/java/com/telnyx/react_voice_commons/VoicePnBridgeModule.kt +247 -0
  11. package/android/src/main/java/com/telnyx/react_voice_commons/VoicePnBridgePackage.kt +17 -0
  12. package/android/src/main/java/com/telnyx/react_voice_commons/VoicePnManager.kt +154 -0
  13. package/ios/CallKitBridge.m +1 -1
  14. package/ios/CallKitBridge.swift +1 -11
  15. package/ios/README.md +2 -2
  16. package/lib/callkit/callkit-coordinator.js +6 -2
  17. package/lib/internal/calls/call-state-controller.d.ts +9 -0
  18. package/lib/internal/calls/call-state-controller.js +51 -24
  19. package/lib/telnyx-voice-app.js +127 -151
  20. package/lib/telnyx-voip-client.d.ts +21 -0
  21. package/lib/telnyx-voip-client.js +30 -0
  22. package/package.json +4 -1
  23. package/src/callkit/callkit-coordinator.ts +8 -2
  24. package/src/internal/calls/call-state-controller.ts +56 -24
  25. package/src/telnyx-voice-app.tsx +154 -170
  26. package/src/telnyx-voip-client.ts +31 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # CHANGELOG.md
2
2
 
3
+ ## [0.1.6](https://github.com/team-telnyx/react-native-voice-commons/releases/tag/0.1.6) (2025-12-09)
4
+
5
+ ### Enhancement
6
+
7
+ • Added Android native components to npm package for proper distribution
8
+ • Complete Android native integration support for Firebase messaging and call management
9
+
10
+ ### Bug Fixing
11
+
12
+ • Fixed missing Android directory in npm package files array
13
+ • Resolved native component availability issues for Android integrations
14
+
3
15
  ## [0.1.5](https://github.com/team-telnyx/react-native-voice-commons/releases/tag/0.1.5) (2025-12-08)
4
16
 
5
17
  ### Enhancement
package/README.md CHANGED
@@ -51,7 +51,7 @@ export default function App() {
51
51
 
52
52
  ### Core Components
53
53
 
54
- #### 1. VoIP Client Configuration
54
+ ### 1. VoIP Client Configuration
55
55
 
56
56
  ```tsx
57
57
  const voipClient = createTelnyxVoipClient({
@@ -65,7 +65,7 @@ const voipClient = createTelnyxVoipClient({
65
65
  - **`enableAppStateManagement: true`** - **Optional (default: true)**: Enables automatic background/foreground app state management. When enabled, the library automatically disconnects when the app goes to background (unless there's an active call) and handles reconnection logic. Set to `false` if you want to handle app lifecycle manually.
66
66
  - **`debug: true`** - **Optional**: Enables detailed logging for connection states, call transitions, and push notification processing. Useful for development and troubleshooting.
67
67
 
68
- #### 2. TelnyxVoiceApp Wrapper
68
+ ### 2. TelnyxVoiceApp Wrapper
69
69
 
70
70
  The `TelnyxVoiceApp` component handles:
71
71
 
@@ -74,7 +74,7 @@ The `TelnyxVoiceApp` component handles:
74
74
  - Login state management with automatic reconnection
75
75
  - Background client management for push notifications
76
76
 
77
- #### 3. Reactive State Management
77
+ ### 3. Reactive State Management
78
78
 
79
79
  ```tsx
80
80
  // Listen to connection state
@@ -93,7 +93,7 @@ call.callState$.subscribe((state) => {
93
93
  });
94
94
  ```
95
95
 
96
- #### 4. Call Management
96
+ ### 4. Call Management
97
97
 
98
98
  ```tsx
99
99
  // Make a call
@@ -112,9 +112,9 @@ await call.hangup();
112
112
 
113
113
  The library supports both credential-based and token-based authentication with automatic persistence for seamless reconnection.
114
114
 
115
- #### Authentication Methods
115
+ ### Authentication Methods
116
116
 
117
- ##### 1. Credential-Based Authentication
117
+ ### 1. Credential-Based Authentication
118
118
 
119
119
  ```tsx
120
120
  import { createCredentialConfig } from '@telnyx/react-voice-commons-sdk';
@@ -127,7 +127,7 @@ const config = createCredentialConfig('your_sip_username', 'your_sip_password',
127
127
  await voipClient.login(config);
128
128
  ```
129
129
 
130
- ##### 2. Token-Based Authentication
130
+ ### 2. Token-Based Authentication
131
131
 
132
132
  ```tsx
133
133
  import { createTokenConfig } from '@telnyx/react-voice-commons-sdk';
@@ -140,11 +140,11 @@ const config = createTokenConfig('your_jwt_token', {
140
140
  await voipClient.loginWithToken(config);
141
141
  ```
142
142
 
143
- #### Automatic Storage & Reconnection
143
+ ### Automatic Storage & Reconnection
144
144
 
145
145
  The library automatically stores authentication data securely for seamless reconnection. **You don't need to manually manage these storage keys** - the library handles everything internally.
146
146
 
147
- ##### Internal Storage (Managed Automatically)
147
+ ### Internal Storage (Managed Automatically)
148
148
 
149
149
  The library uses these AsyncStorage keys internally:
150
150
 
@@ -155,7 +155,7 @@ The library uses these AsyncStorage keys internally:
155
155
 
156
156
  **Note**: These are managed automatically by the library. You only need to call `login()` once, and the library will handle storage and future reconnections.
157
157
 
158
- ##### Auto-Reconnection
158
+ ### Auto-Reconnection
159
159
 
160
160
  ```tsx
161
161
  // Automatically reconnects using internally stored credentials or token
@@ -176,7 +176,7 @@ if (!success) {
176
176
 
177
177
  **Demo App Note**: When using the library in a demo application, the `TelnyxLoginForm` component may do additional storage for UI convenience (pre-filling login forms). This is separate from the library's internal authentication storage and is not required for production apps.
178
178
 
179
- ##### Manual Storage Management (Advanced Use Only)
179
+ ### Manual Storage Management (Advanced Use Only)
180
180
 
181
181
  If you need to clear stored authentication data manually:
182
182
 
@@ -198,9 +198,9 @@ await AsyncStorage.multiRemove([
198
198
 
199
199
  The library provides complete native integration for both platforms. These integrations are required for production apps using the library.
200
200
 
201
- #### Android Integration
201
+ ### Android Integration
202
202
 
203
- ##### 1. MainActivity Setup
203
+ ### 1. MainActivity Setup
204
204
 
205
205
  Your app's MainActivity should extend `TelnyxMainActivity` for automatic push notification handling:
206
206
 
@@ -225,7 +225,7 @@ The `TelnyxMainActivity` provides:
225
225
  - Proper lifecycle management for VoIP functionality
226
226
  - Integration with `VoicePnManager` for push notification state
227
227
 
228
- ##### 2. Push Notification Setup
228
+ ### 2. Push Notification Setup
229
229
 
230
230
  1. Place `google-services.json` in the project root
231
231
  2. Register background message handler:
@@ -239,9 +239,9 @@ messaging().setBackgroundMessageHandler(async (remoteMessage) => {
239
239
  });
240
240
  ```
241
241
 
242
- #### iOS Integration
242
+ ### iOS Integration
243
243
 
244
- ##### 1. AppDelegate Setup
244
+ ### 1. AppDelegate Setup
245
245
 
246
246
  Your AppDelegate only needs to implement `PKPushRegistryDelegate` for VoIP push notifications. CallKit integration is automatically handled by CallBridge:
247
247
 
@@ -277,13 +277,13 @@ public class AppDelegate: ExpoAppDelegate, PKPushRegistryDelegate {
277
277
 
278
278
  **Note**: CallKit integration (CXProvider, CXProviderDelegate, audio session management) is automatically handled by the internal CallBridge component. You don't need to implement any CallKit delegate methods manually.
279
279
 
280
- ##### 2. VoIP Push Certificate Setup
280
+ ### 2. VoIP Push Certificate Setup
281
281
 
282
282
  - Configure VoIP push certificates in your Apple Developer account
283
283
  - The `TelnyxVoipPushHandler` automatically handles token registration and push processing
284
284
  - CallKit integration is automatically managed by CallBridge - no manual setup required
285
285
 
286
- #### Key Native Features Integrated
286
+ ### Key Native Features Integrated
287
287
 
288
288
  1. **Push Notification Handling**: Both platforms handle background push notifications properly
289
289
  2. **Native Call UI**: CallKit (iOS, managed by CallBridge) and ConnectionService (Android) integration
@@ -416,15 +416,15 @@ npx expo run:ios
416
416
 
417
417
  ### Common Integration Issues
418
418
 
419
- #### Double Login
419
+ ### Double Login
420
420
 
421
421
  Ensure you're not calling login methods manually when using `TelnyxVoiceApp` with auto-reconnection enabled.
422
422
 
423
- #### Background Disconnection
423
+ ### Background Disconnection
424
424
 
425
425
  Check if `enableAutoReconnect` is set appropriately for your use case in the `TelnyxVoiceApp` configuration.
426
426
 
427
- #### Push Notifications Not Working
427
+ ### Push Notifications Not Working
428
428
 
429
429
  - **Android**:
430
430
  - Verify `google-services.json` is in the correct location and Firebase is properly configured
@@ -436,19 +436,19 @@ Check if `enableAutoReconnect` is set appropriately for your use case in the `Te
436
436
  - Check that `TelnyxVoipPushHandler.initializeVoipRegistration()` is called in `didFinishLaunchingWithOptions`
437
437
  - **Both**: Check that background message handlers are properly registered
438
438
 
439
- #### Native Integration Issues
439
+ ### Native Integration Issues
440
440
 
441
441
  - **Android**: Ensure MainActivity extends `TelnyxMainActivity` for proper intent handling
442
442
  - **iOS**: Verify AppDelegate implements `PKPushRegistryDelegate` and delegates to `TelnyxVoipPushHandler`
443
443
  - **CallKit**: On iOS, CallKit integration is automatically handled by CallBridge - no manual setup required
444
444
 
445
- #### Audio Issues
445
+ ### Audio Issues
446
446
 
447
447
  - **iOS**: Audio session management is automatically handled by CallBridge
448
448
  - **Android**: Verify ConnectionService is properly configured for audio routing
449
449
  - **Both**: Ensure proper audio permissions are granted
450
450
 
451
- #### Memory Leaks
451
+ ### Memory Leaks
452
452
 
453
453
  Ensure you're unsubscribing from RxJS observables in your React components:
454
454
 
@@ -0,0 +1,45 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'kotlin-android'
3
+
4
+ android {
5
+ namespace 'com.telnyx.react_voice_commons'
6
+ compileSdk 34
7
+
8
+ defaultConfig {
9
+ minSdk 21
10
+ targetSdk 34
11
+ versionCode 1
12
+ versionName "1.0"
13
+ }
14
+
15
+ compileOptions {
16
+ sourceCompatibility JavaVersion.VERSION_17
17
+ targetCompatibility JavaVersion.VERSION_17
18
+ }
19
+
20
+ kotlinOptions {
21
+ jvmTarget = "17"
22
+ }
23
+
24
+ sourceSets {
25
+ main {
26
+ java.srcDirs += 'voice-pn-module/src/main/java'
27
+ }
28
+ }
29
+
30
+ buildTypes {
31
+ release {
32
+ minifyEnabled false
33
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
34
+ }
35
+ }
36
+ }
37
+
38
+ dependencies {
39
+ implementation "com.google.firebase:firebase-messaging:23.1.2"
40
+ implementation "androidx.core:core-ktx:1.8.0"
41
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.21"
42
+
43
+ // React Native dependencies
44
+ implementation "com.facebook.react:react-native:+"
45
+ }
@@ -0,0 +1,38 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+
4
+ <!-- Basic permissions that this module needs -->
5
+ <uses-permission android:name="android.permission.INTERNET" />
6
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
7
+ <uses-permission android:name="android.permission.VIBRATE" />
8
+
9
+ <!-- WebRTC and voice call permissions -->
10
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
11
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
12
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
13
+
14
+ <!-- Background execution permissions for call handling -->
15
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
16
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
17
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
18
+
19
+ <!-- Call management permissions -->
20
+ <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
21
+ <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
22
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
23
+
24
+ <!-- Hardware features -->
25
+ <uses-feature
26
+ android:name="android.hardware.microphone"
27
+ android:required="true" />
28
+
29
+ <application>
30
+ <!-- Foreground service for keeping WebRTC connections alive during calls -->
31
+ <service
32
+ android:name=".CallForegroundService"
33
+ android:enabled="true"
34
+ android:exported="false"
35
+ android:foregroundServiceType="phoneCall|microphone" />
36
+ </application>
37
+
38
+ </manifest>
@@ -0,0 +1,83 @@
1
+ package com.telnyx.react_voice_commons
2
+
3
+ import android.app.Service
4
+ import android.content.Intent
5
+ import android.os.IBinder
6
+ import android.util.Log
7
+
8
+ /**
9
+ * Foreground service to keep WebRTC connections alive during calls
10
+ * This service prevents the system from killing the app during active calls
11
+ */
12
+ class CallForegroundService : Service() {
13
+ companion object {
14
+ private const val TAG = "CallForegroundService"
15
+ const val ACTION_START_FOREGROUND_SERVICE = "START_FOREGROUND_SERVICE"
16
+ const val ACTION_STOP_FOREGROUND_SERVICE = "STOP_FOREGROUND_SERVICE"
17
+
18
+ const val EXTRA_CALLER_NAME = "caller_name"
19
+ const val EXTRA_CALLER_NUMBER = "caller_number"
20
+ const val EXTRA_CALL_ID = "call_id"
21
+ }
22
+
23
+ override fun onBind(intent: Intent?): IBinder? {
24
+ return null
25
+ }
26
+
27
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
28
+ when (intent?.action) {
29
+ ACTION_START_FOREGROUND_SERVICE -> {
30
+ Log.d(TAG, "Starting foreground service for call")
31
+ startAsForegroundService(intent)
32
+ }
33
+ ACTION_STOP_FOREGROUND_SERVICE -> {
34
+ Log.d(TAG, "Stopping foreground service")
35
+ stopAsForegroundService()
36
+ }
37
+ }
38
+
39
+ // Restart the service if it's killed by the system
40
+ return START_STICKY
41
+ }
42
+
43
+ private fun startAsForegroundService(intent: Intent) {
44
+ val callerName = intent.getStringExtra(EXTRA_CALLER_NAME) ?: "Unknown Caller"
45
+ val callerNumber = intent.getStringExtra(EXTRA_CALLER_NUMBER) ?: ""
46
+ val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "unknown"
47
+
48
+ try {
49
+ // Create ongoing call notification
50
+ val notificationHelper = TelnyxNotificationHelper(this)
51
+ val notification = notificationHelper.createOngoingCallNotification(callerName, callerNumber, callId)
52
+
53
+ // Start foreground service with the notification
54
+ startForeground(TelnyxNotificationHelper.ONGOING_CALL_NOTIFICATION_ID, notification)
55
+
56
+ Log.d(TAG, "Foreground service started with ongoing call notification")
57
+ } catch (e: Exception) {
58
+ Log.e(TAG, "Error starting foreground service", e)
59
+ // Stop the service if we can't create the notification
60
+ stopSelf()
61
+ }
62
+ }
63
+
64
+ private fun stopAsForegroundService() {
65
+ try {
66
+ // Hide the ongoing call notification
67
+ TelnyxNotificationHelper.hideOngoingCallNotificationFromContext(this)
68
+
69
+ // Stop foreground service
70
+ stopForeground(true)
71
+ stopSelf()
72
+
73
+ Log.d(TAG, "Foreground service stopped")
74
+ } catch (e: Exception) {
75
+ Log.e(TAG, "Error stopping foreground service", e)
76
+ }
77
+ }
78
+
79
+ override fun onDestroy() {
80
+ super.onDestroy()
81
+ Log.d(TAG, "CallForegroundService destroyed")
82
+ }
83
+ }
@@ -0,0 +1,179 @@
1
+ package com.telnyx.react_voice_commons
2
+
3
+ import android.util.Log
4
+ import com.google.firebase.messaging.FirebaseMessagingService
5
+ import com.google.firebase.messaging.RemoteMessage
6
+
7
+ /**
8
+ * FCM Service for handling Telnyx voice push notifications
9
+ * This service should be extended by the main app's FCM service
10
+ */
11
+ open class TelnyxFirebaseMessagingService : FirebaseMessagingService() {
12
+ private val tag = "TelnyxFCMService"
13
+
14
+ override fun onMessageReceived(remoteMessage: RemoteMessage) {
15
+ super.onMessageReceived(remoteMessage)
16
+
17
+ Log.d(tag, "FCM message received from: ${remoteMessage.from}")
18
+ Log.d(tag, "FCM message data: ${remoteMessage.data}")
19
+
20
+ // Check if this is a Telnyx voice push notification
21
+ val isTelnyxVoiceMessage = remoteMessage.data.containsKey("metadata") ||
22
+ remoteMessage.data.containsKey("caller_id_name") ||
23
+ remoteMessage.data.containsKey("caller_name") ||
24
+ remoteMessage.data.containsKey("call_id") ||
25
+ remoteMessage.data.containsKey("telnyx_call_id") ||
26
+ remoteMessage.data.get("message_type") == "voice" ||
27
+ remoteMessage.data.get("message") == "Incoming call!" ||
28
+ remoteMessage.data.get("message") == "Missed call!"
29
+
30
+ if (isTelnyxVoiceMessage) {
31
+ Log.d(tag, "Handling as Telnyx voice push notification")
32
+
33
+ // Check if this is a missed call notification
34
+ val isMissedCall = remoteMessage.data.get("message") == "Missed call!"
35
+ if (isMissedCall) {
36
+ handleTelnyxMissedCall(remoteMessage)
37
+ } else {
38
+ handleTelnyxVoicePush(remoteMessage)
39
+ }
40
+ } else {
41
+ Log.d(tag, "Non-Telnyx message, delegating to handleNonTelnyxMessage")
42
+ handleNonTelnyxMessage(remoteMessage)
43
+ }
44
+ }
45
+
46
+ override fun onNewToken(token: String) {
47
+ super.onNewToken(token)
48
+ Log.d(tag, "FCM token refreshed: $token")
49
+ handleTokenRefresh(token)
50
+ }
51
+
52
+ /**
53
+ * Handle Telnyx missed call notifications
54
+ */
55
+ protected open fun handleTelnyxMissedCall(remoteMessage: RemoteMessage) {
56
+ try {
57
+ Log.d(tag, "Processing Telnyx missed call notification")
58
+
59
+ // Extract caller information from metadata or top-level data
60
+ var callId = "unknown"
61
+ var callerName = "Unknown Caller"
62
+ var callerNumber = ""
63
+
64
+ // First try to parse metadata JSON if it exists
65
+ val metadataJson = remoteMessage.data["metadata"]
66
+ if (metadataJson != null) {
67
+ try {
68
+ Log.d(tag, "Parsing missed call metadata JSON: $metadataJson")
69
+ val metadata = org.json.JSONObject(metadataJson)
70
+ callId = metadata.optString("call_id", "unknown")
71
+ callerName = metadata.optString("caller_name", "Unknown Caller")
72
+ callerNumber = metadata.optString("caller_number", "")
73
+ Log.d(tag, "Extracted from metadata - callId: $callId, callerName: $callerName, callerNumber: $callerNumber")
74
+ } catch (e: Exception) {
75
+ Log.e(tag, "Error parsing missed call metadata JSON", e)
76
+ }
77
+ }
78
+
79
+ // Fallback to top-level data if metadata parsing failed or doesn't exist
80
+ if (callId == "unknown") {
81
+ callId = remoteMessage.data["call_id"] ?: remoteMessage.data["telnyx_call_id"] ?: "unknown"
82
+ callerName = remoteMessage.data["caller_id_name"] ?: remoteMessage.data["caller_name"] ?: "Unknown Caller"
83
+ callerNumber = remoteMessage.data["caller_id_number"] ?: remoteMessage.data["caller_number"] ?: ""
84
+ Log.d(tag, "Using top-level data for missed call - callId: $callId, callerName: $callerName, callerNumber: $callerNumber")
85
+ }
86
+
87
+ // Show missed call notification (this will hide any existing incoming call notification)
88
+ val notificationHelper = TelnyxNotificationHelper(this)
89
+ notificationHelper.showMissedCallNotification(callerName, callerNumber, callId)
90
+
91
+ Log.d(tag, "Telnyx missed call notification processed successfully")
92
+ } catch (e: Exception) {
93
+ Log.e(tag, "Error handling Telnyx missed call notification", e)
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Handle Telnyx voice push notifications
99
+ */
100
+ protected open fun handleTelnyxVoicePush(remoteMessage: RemoteMessage) {
101
+ try {
102
+ Log.d(tag, "Processing Telnyx voice push using VoicePnManager")
103
+
104
+ // Extract caller information from metadata or top-level data
105
+ var callId = "unknown"
106
+ var callerName = "Unknown Caller"
107
+ var callerNumber = ""
108
+
109
+ // First try to parse metadata JSON if it exists
110
+ val metadataJson = remoteMessage.data["metadata"]
111
+ if (metadataJson != null) {
112
+ try {
113
+ Log.d(tag, "Parsing metadata JSON: $metadataJson")
114
+ val metadata = org.json.JSONObject(metadataJson)
115
+ callId = metadata.optString("call_id", "unknown")
116
+ callerName = metadata.optString("caller_name", "Unknown Caller")
117
+ callerNumber = metadata.optString("caller_number", "")
118
+ Log.d(tag, "Extracted from metadata - callId: $callId, callerName: $callerName, callerNumber: $callerNumber")
119
+ } catch (e: Exception) {
120
+ Log.e(tag, "Error parsing metadata JSON", e)
121
+ // Fall back to top-level data if metadata parsing fails
122
+ }
123
+ }
124
+
125
+ // Fallback to top-level data if metadata parsing failed or doesn't exist
126
+ if (callId == "unknown") {
127
+ callId = remoteMessage.data["call_id"] ?: remoteMessage.data["telnyx_call_id"] ?: "unknown"
128
+ callerName = remoteMessage.data["caller_id_name"] ?: remoteMessage.data["caller_name"] ?: "Unknown Caller"
129
+ callerNumber = remoteMessage.data["caller_id_number"] ?: remoteMessage.data["caller_number"] ?: ""
130
+ Log.d(tag, "Using top-level data - callId: $callId, callerName: $callerName, callerNumber: $callerNumber")
131
+ }
132
+
133
+
134
+ var metadata = ""
135
+
136
+ // Extract metadata using the same approach as official Telnyx service
137
+ try {
138
+ val params = remoteMessage.data
139
+ val objects = org.json.JSONObject(params as Map<*, *>)
140
+ metadata = objects.getString( "metadata")
141
+
142
+ Log.d(tag, "Extracted FCM metadata string: $metadata")
143
+ } catch (e: Exception) {
144
+ Log.e(tag, "Error extracting metadata from FCM data: ${e.message}")
145
+
146
+ // Fallback to simple JSON if metadata extraction fails
147
+ metadata = org.json.JSONObject().apply {
148
+ put("call_id", callId)
149
+ put("caller_name", callerName)
150
+ put("caller_number", callerNumber)
151
+ }.toString()
152
+
153
+ Log.d(tag, "Using fallback metadata: $metadata")
154
+ }
155
+ //VoicePnManager.setPendingPushAction(this, "incoming_call", metadata)
156
+
157
+ // Show notification using dynamic class resolution
158
+ val notificationHelper = TelnyxNotificationHelper(this)
159
+ notificationHelper.showIncomingCallNotification(callerName, callerNumber, callId, metadata)
160
+ Log.d(tag, "Telnyx voice push processed successfully")
161
+ } catch (e: Exception) {
162
+ Log.e(tag, "Error handling Telnyx voice push notification", e)
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Handle non-Telnyx messages - override this in your app's FCM service if needed
168
+ */
169
+ protected open fun handleNonTelnyxMessage(remoteMessage: RemoteMessage) {
170
+ Log.d(tag, "Default non-Telnyx message handler - override this method if needed")
171
+ }
172
+
173
+ /**
174
+ * Handle FCM token refresh - override this in your app's FCM service if needed
175
+ */
176
+ protected open fun handleTokenRefresh(token: String) {
177
+ Log.d(tag, "Default token refresh handler - override this method if needed")
178
+ }
179
+ }