I am trying to fetch voip token from apple in react native but cannot seem to do so, I am building on xcode cloud as my mac is quite old. xcode is 15 [closed]

1 week ago 11
ARTICLE AD BOX

I’m trying to implement VoIP push notifications in my React Native app using react-native-voip-push-notification. Regular push notifications (FCM & Apple notifications) are working, but the VoIP token never arrives.

I have:

Added Push Notifications entitlement in the provisioning profile and entitlements file.

Enabled VoIP in background modes in Xcode.

Updated Info.plist:

<key>UIBackgroundModes</key> <array> <string>audio</string> <string>remote-notification</string> <string>voip</string> </array> <key>UIFileSharingEnabled</key> <true/> <key>UILaunchStoryboardName</key> <string>LaunchScreen</string> <key>UIRequiredDeviceCapabilities</key> <array> <string>arm64</string> </array> <key>UISupportedInterfaceOrientations</key> <array> <string>UIInterfaceOrientationPortrait</string> </array> <key>UISupportedInterfaceOrientations~ipad</key> <array> <string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> <key>UIViewControllerBasedStatusBarAppearance</key> <false/>

AppDelegate header (AppDelegate.h):

#import <RCTAppDelegate.h> #import <UIKit/UIKit.h> #import <Firebase.h> #import <PushKit/PushKit.h> @interface AppDelegate : RCTAppDelegate <PKPushRegistryDelegate> @end AppDelegate implementation (AppDelegate.mm): #import "AppDelegate.h" #import <React/RCTBundleURLProvider.h> #import <PushKit/PushKit.h> #import <RNVoipPushNotificationManager.h> @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSLog(@" [NATIVE] ========================================"); NSLog(@" [NATIVE] App Launching - didFinishLaunchingWithOptions"); NSLog(@" [NATIVE] ========================================"); self.moduleName = @"sihspatient"; self.initialProps = @{}; if ([FIRApp defaultApp] == nil) { [FIRApp configure]; NSLog(@" [NATIVE] Firebase configured"); } BOOL result = [super application:application didFinishLaunchingWithOptions:launchOptions]; NSLog(@" [NATIVE] React Native bridge initialized"); [self voipRegistration]; return result; } - (void)voipRegistration { NSLog(@" [NATIVE-VOIP] ========================================"); NSLog(@" [NATIVE-VOIP] Starting VoIP Registration"); NSLog(@" [NATIVE-VOIP] ========================================"); PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; NSLog(@" [NATIVE-VOIP] PKPushRegistry instance created"); pushRegistry.delegate = self; NSLog(@" [NATIVE-VOIP] Delegate set to AppDelegate"); pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; NSLog(@" [NATIVE-VOIP] Desired push types set: PKPushTypeVoIP"); NSLog(@" [NATIVE-VOIP] Waiting for Apple to call didUpdatePushCredentials..."); NSLog(@" [NATIVE-VOIP] This may take 2-10 seconds on first launch"); NSLog(@" [NATIVE-VOIP] Must be running on physical device (not simulator)"); NSLog(@" [NATIVE-VOIP] Must have Push Notifications capability enabled"); } - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type { NSLog(@" [NATIVE-VOIP] ========================================"); NSLog(@" [NATIVE-VOIP] didUpdatePushCredentials CALLED!"); NSLog(@" [NATIVE-VOIP] ========================================"); NSLog(@" [NATIVE-VOIP] Push Type: %@", type); NSLog(@" [NATIVE-VOIP] Credentials: %@", credentials); NSData *tokenData = credentials.token; NSString *tokenString = [[tokenData description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]; tokenString = [tokenString stringByReplacingOccurrencesOfString:@" " withString:@""]; NSLog(@" [NATIVE-VOIP] Token (hex): %@", tokenString); NSLog(@" [NATIVE-VOIP] Token length: %lu bytes", (unsigned long)tokenData.length); NSLog(@" [NATIVE-VOIP] Forwarding token to React Native bridge..."); [RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type]; NSLog(@" [NATIVE-VOIP] Token forwarded to RNVoipPushNotificationManager"); } - (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type { NSLog(@" [NATIVE-VOIP] Push token invalidated for type: %@", type); } - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion { NSLog(@" [NATIVE-VOIP] Incoming VoIP push received"); NSLog(@" [NATIVE-VOIP] Payload: %@", payload.dictionaryPayload); [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type]; if (completion) { completion(); } } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { return [self bundleURL]; } - (NSURL *)bundleURL { #if DEBUG return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; #else return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif } @end

VoIP service in TypeScript:

import {Platform} from 'react-native'; import VoipPushNotification from 'react-native-voip-push-notification'; import AsyncStorage from '@react-native-async-storage/async-storage'; class VoIPService { private static instance: VoIPService; static getInstance(): VoIPService { if (!VoIPService.instance) { VoIPService.instance = new VoIPService(); } return VoIPService.instance; } /** * Initialize VoIP - ONLY for token retrieval */ async initialize(): Promise<void> { if (Platform.OS !== 'ios') { console.log( '⚠️ [VOIP] VoIP only available on iOS, current platform:', Platform.OS, ); return; } console.log('🚀 [VOIP] ========================================'); console.log('🚀 [VOIP] Starting VoIP Token Retrieval'); console.log('🚀 [VOIP] ========================================'); try { // ✅ Step 1: Check if token already exists in storage console.log( '📱 [VOIP] Step 1: Checking AsyncStorage for existing token...', ); const existingToken = await AsyncStorage.getItem('VOIP_TOKEN'); if (existingToken) { console.log('✅ [VOIP] Found existing token in storage!'); console.log( '📱 [VOIP] Token (first 30 chars):', existingToken.substring(0, 30) + '...', ); console.log('📱 [VOIP] Token length:', existingToken.length); } else { console.log('⚠️ [VOIP] No existing token found in storage'); } // ✅ Step 2: Setup listener for new token console.log('📱 [VOIP] Step 2: Setting up token listener...'); VoipPushNotification.addEventListener( 'register', async (token: string) => { console.log('🎉 [VOIP] ========================================'); console.log('🎉 [VOIP] TOKEN RECEIVED FROM APPLE!'); console.log('🎉 [VOIP] ========================================'); console.log( '📱 [VOIP] Token (first 30 chars):', token.substring(0, 30) + '...', ); console.log('📱 [VOIP] Token length:', token.length); console.log('📱 [VOIP] Full token:', token); try { await AsyncStorage.setItem('VOIP_TOKEN', token); console.log('✅ [VOIP] Token saved to AsyncStorage successfully!'); // Verify it was saved const savedToken = await AsyncStorage.getItem('VOIP_TOKEN'); if (savedToken === token) { console.log( '✅ [VOIP] Token verification successful - token matches!', ); } else { console.log( '❌ [VOIP] Token verification FAILED - tokens do not match!', ); } } catch (saveError) { console.error( '❌ [VOIP] Error saving token to AsyncStorage:', saveError, ); } }, ); console.log('✅ [VOIP] Token listener registered'); console.log('⏳ [VOIP] Waiting for token from Apple PushKit...'); console.log( '📝 [VOIP] Note: Token comes from AppDelegate.mm native code', ); } catch (error) { console.error('❌ [VOIP] Error during initialization:', error); console.error('❌ [VOIP] Error details:', JSON.stringify(error, null, 2)); } } /** * Get stored VoIP token */ async getVoIPToken(): Promise<string | null> { console.log('🔍 [VOIP] Attempting to retrieve token from storage...'); try { const token = await AsyncStorage.getItem('VOIP_TOKEN'); if (token) { console.log('✅ [VOIP] Token found in storage!'); console.log( '📱 [VOIP] Token (first 30 chars):', token.substring(0, 30) + '...', ); console.log('📱 [VOIP] Token length:', token.length); return token; } else { console.log('⚠️ [VOIP] No token found in storage'); console.log( '💡 [VOIP] Token should be received from AppDelegate.mm after app launch', ); return null; } } catch (error) { console.error('❌ [VOIP] Error retrieving token from storage:', error); return null; } } /** * Clean up listener */ async cleanup(): Promise<void> { if (Platform.OS === 'ios') { console.log('🧹 [VOIP] Cleaning up token listener...'); VoipPushNotification.removeEventListener('register'); console.log('✅ [VOIP] Cleanup complete'); } } } export default VoIPService;

VoIP Debug Screen in React Native:

import React, {useEffect, useState} from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Platform, Alert, Clipboard, } from 'react-native'; import VoIPService from './services/voip.service'; import AsyncStorage from '@react-native-async-storage/async-storage'; const VoIPDebugScreen = () => { const [voipToken, setVoipToken] = useState<string | null>(null); const [loading, setLoading] = useState(true); const [logs, setLogs] = useState<string[]>([]); const addLog = (message: string) => { const timestamp = new Date().toLocaleTimeString(); setLogs(prev => [`[${timestamp}] ${message}`, ...prev].slice(0, 50)); }; useEffect(() => { checkVoIPToken(); // Check token every 2 seconds const interval = setInterval(checkVoIPToken, 2000); return () => clearInterval(interval); }, []); const checkVoIPToken = async () => { try { addLog('Checking VoIP token...'); const token = await VoIPService.getInstance().getVoIPToken(); setVoipToken(token); setLoading(false); if (token) { addLog(`✅ Token found: ${token.substring(0, 20)}...`); } else { addLog('⚠️ No token found yet'); } } catch (error) { addLog(`❌ Error: ${error}`); setLoading(false); } }; const copyToClipboard = () => { if (voipToken) { Clipboard.setString(voipToken); Alert.alert('Copied!', 'VoIP token copied to clipboard'); addLog('📋 Token copied to clipboard'); } }; const reinitializeVoIP = async () => { try { addLog('🔄 Reinitializing VoIP service...'); await VoIPService.getInstance().initialize(); addLog('✅ VoIP service reinitialized'); setTimeout(checkVoIPToken, 1000); } catch (error) { addLog(`❌ Reinitialization error: ${error}`); } }; const clearToken = async () => { try { await AsyncStorage.removeItem('VOIP_TOKEN'); setVoipToken(null); addLog('🗑️ Token cleared from storage'); Alert.alert('Success', 'VoIP token cleared'); } catch (error) { addLog(`❌ Clear error: ${error}`); } }; if (Platform.OS !== 'ios') { return ( <View style={styles.container}> <Text style={styles.errorText}>VoIP is only available on iOS</Text> </View> ); } return ( <ScrollView style={styles.container}> <View style={styles.header}> <Text style={styles.title}>VoIP Debug Screen</Text> <Text style={styles.subtitle}>Platform: {Platform.OS}</Text> </View> {/* Token Status */} <View style={styles.section}> <Text style={styles.sectionTitle}>Token Status</Text> <View style={[ styles.statusCard, {backgroundColor: voipToken ? '#d4edda' : '#f8d7da'}, ]}> <Text style={styles.statusText}> {loading ? '⏳ Loading...' : voipToken ? '✅ Token Available' : '❌ No Token'} </Text> </View> </View> {/* Token Display */} {voipToken && ( <View style={styles.section}> <Text style={styles.sectionTitle}>VoIP Token</Text> <View style={styles.tokenCard}> <ScrollView horizontal showsHorizontalScrollIndicator={false}> <Text style={styles.tokenText}>{voipToken}</Text> </ScrollView> </View> <TouchableOpacity style={styles.button} onPress={copyToClipboard}> <Text style={styles.buttonText}>📋 Copy Token</Text> </TouchableOpacity> </View> )} {/* Actions */} <View style={styles.section}> <Text style={styles.sectionTitle}>Actions</Text> <TouchableOpacity style={[styles.button, styles.primaryButton]} onPress={checkVoIPToken}> <Text style={styles.buttonText}>🔄 Refresh Token</Text> </TouchableOpacity> <TouchableOpacity style={[styles.button, styles.secondaryButton]} onPress={reinitializeVoIP}> <Text style={styles.buttonText}>🔧 Reinitialize VoIP</Text> </TouchableOpacity> <TouchableOpacity style={[styles.button, styles.dangerButton]} onPress={clearToken}> <Text style={styles.buttonText}>🗑️ Clear Token</Text> </TouchableOpacity> </View> {/* Logs */} <View style={styles.section}> <Text style={styles.sectionTitle}>Logs (Last 50)</Text> <View style={styles.logsContainer}> {logs.length === 0 ? ( <Text style={styles.noLogsText}>No logs yet...</Text> ) : ( logs.map((log, index) => ( <Text key={index} style={styles.logText}> {log} </Text> )) )} </View> </View> {/* Instructions */} <View style={styles.section}> <Text style={styles.sectionTitle}>📖 Instructions</Text> <Text style={styles.instructionText}> 1. Install this build on a physical iOS device{'\n'} 2. Launch the app{'\n'} 3. Wait 5-10 seconds for token registration{'\n'} 4. Token should appear above{'\n'} 5. Copy and save the token for backend testing{'\n'} 6. Remove this screen before production release </Text> </View> </ScrollView> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, header: { backgroundColor: '#007AFF', padding: 20, paddingTop: 60, }, title: { fontSize: 24, fontWeight: 'bold', color: '#fff', marginBottom: 5, }, subtitle: { fontSize: 14, color: '#fff', opacity: 0.8, }, section: { backgroundColor: '#fff', margin: 10, padding: 15, borderRadius: 10, shadowColor: '#000', shadowOffset: {width: 0, height: 2}, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, sectionTitle: { fontSize: 18, fontWeight: '600', marginBottom: 10, color: '#333', }, statusCard: { padding: 15, borderRadius: 8, alignItems: 'center', }, statusText: { fontSize: 16, fontWeight: '600', }, tokenCard: { backgroundColor: '#f8f9fa', padding: 15, borderRadius: 8, borderWidth: 1, borderColor: '#dee2e6', marginBottom: 10, }, tokenText: { fontSize: 12, fontFamily: 'Courier', color: '#495057', }, button: { padding: 15, borderRadius: 8, alignItems: 'center', marginTop: 10, }, primaryButton: { backgroundColor: '#007AFF', }, secondaryButton: { backgroundColor: '#6c757d', }, dangerButton: { backgroundColor: '#dc3545', }, buttonText: { color: '#fff', fontSize: 16, fontWeight: '600', }, logsContainer: { backgroundColor: '#000', padding: 10, borderRadius: 8, maxHeight: 300, }, logText: { color: '#0f0', fontSize: 11, fontFamily: 'Courier', marginBottom: 3, }, noLogsText: { color: '#888', textAlign: 'center', fontStyle: 'italic', }, instructionText: { fontSize: 14, color: '#666', lineHeight: 22, }, errorText: { fontSize: 18, color: '#dc3545', textAlign: 'center', marginTop: 100, }, }); export default VoIPDebugScreen;

Problem:

I’m running this on a physical device via TestFlight.

Regular push notifications work.

VoIP push token never arrives.

Cannot check device logs because my iPhone 16 is iOS 26.1, which Xcode 15.1 does not support, and idevicesyslog fails (ERROR: Could not connect to lockdownd: -18).

I read that starting iOS 15+, the old PushKit entitlement key is removed and no longer required.

Am I missing any setup to get the VoIP token?

Is there a known issue with TestFlight builds not receiving VoIP push tokens?

Are there alternative ways to verify the VoIP token when device logs cannot be accessed?

Any guidance would be really appreciated.

Read Entire Article