Anonymous
Я пытаюсь получить токен voip от Apple в React Native, но, похоже, не могу этого сделать, я создаю облако xcode, так как
Сообщение
Anonymous » 24 ноя 2025, 15:44
Я пытаюсь реализовать push-уведомления VoIP в своем приложении React Native, используя response-native-voip-push-notification. Обычные push-уведомления (уведомления FCM и Apple) работают, но токен VoIP так и не приходит.
У меня:
Добавлено право на push-уведомления в профиле обеспечения и файле прав.
Включен VoIP в фоновых режимах в Xcode.
Обновлен Info.plist:
Код: Выделить всё
UIBackgroundModes
audio
remote-notification
voip
UIFileSharingEnabled
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
arm64
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UIViewControllerBasedStatusBarAppearance
Заголовок AppDelegate (AppDelegate.h):
Код: Выделить всё
#import
#import
#import
#import
@interface AppDelegate : RCTAppDelegate
@end
AppDelegate implementation (AppDelegate.mm):
#import "AppDelegate.h"
#import
#import
#import
@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 на 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 {
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 {
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 {
if (Platform.OS === 'ios') {
console.log('🧹 [VOIP] Cleaning up token listener...');
VoipPushNotification.removeEventListener('register');
console.log('✅ [VOIP] Cleanup complete');
}
}
}
export default VoIPService;
Экран отладки VoIP в 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(null);
const [loading, setLoading] = useState(true);
const [logs, setLogs] = useState([]);
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 (
VoIP is only available on iOS
);
}
return (
VoIP Debug Screen
Platform: {Platform.OS}
{/* Token Status */}
Token Status
{loading
? '⏳ Loading...'
: voipToken
? '✅ Token Available'
: '❌ No Token'}
{/* Token Display */}
{voipToken && (
VoIP Token
{voipToken}
📋 Copy Token
)}
{/* Actions */}
Actions
🔄 Refresh Token
🔧 Reinitialize VoIP
🗑️ Clear Token
{/* Logs */}
Logs (Last 50)
{logs.length === 0 ? (
No logs yet...
) : (
logs.map((log, index) => (
{log}
))
)}
{/* Instructions */}
📖 Instructions
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
);
};
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;
Проблема:
Я запускаю это на физическом устройстве через TestFlight.
Обычные push-уведомления работают.
Push-токен VoIP никогда не приходит.
Невозможно проверить журналы устройства, поскольку на моем iPhone 16 установлена iOS 26.1, которую Xcode 15.1 не поддерживает, и idevicesyslog завершается сбоем (ОШИБКА: Не удалось подключиться к lockdownd: -18).
Я читал, что начиная с iOS 15+ старый ключ разрешения PushKit удаляется и больше не требуется.
Мне не хватает каких-либо настроек для получения токена VoIP?
Известна ли проблема со сборками TestFlight, не получающими push-токены VoIP?
Существуют ли альтернативные способы проверить токен VoIP, когда доступ к журналам устройства недоступен?
Будем очень признательны за любые рекомендации.
Подробнее здесь:
https://stackoverflow.com/questions/798 ... seem-to-do
1763988282
Anonymous
Я пытаюсь реализовать push-уведомления VoIP в своем приложении React Native, используя response-native-voip-push-notification. Обычные push-уведомления (уведомления FCM и Apple) работают, но токен VoIP так и не приходит. У меня: Добавлено право на push-уведомления в профиле обеспечения и файле прав. Включен VoIP в фоновых режимах в Xcode. Обновлен Info.plist: [code]UIBackgroundModes audio remote-notification voip UIFileSharingEnabled UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance [/code] Заголовок AppDelegate (AppDelegate.h): [code] #import #import #import #import @interface AppDelegate : RCTAppDelegate @end AppDelegate implementation (AppDelegate.mm): #import "AppDelegate.h" #import #import #import @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 [/code] Служба VoIP на TypeScript: [code]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 { 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 { 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 { if (Platform.OS === 'ios') { console.log('🧹 [VOIP] Cleaning up token listener...'); VoipPushNotification.removeEventListener('register'); console.log('✅ [VOIP] Cleanup complete'); } } } export default VoIPService; [/code] Экран отладки VoIP в React Native: [code]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(null); const [loading, setLoading] = useState(true); const [logs, setLogs] = useState([]); 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 ( VoIP is only available on iOS ); } return ( VoIP Debug Screen Platform: {Platform.OS} {/* Token Status */} Token Status {loading ? '⏳ Loading...' : voipToken ? '✅ Token Available' : '❌ No Token'} {/* Token Display */} {voipToken && ( VoIP Token {voipToken} 📋 Copy Token )} {/* Actions */} Actions 🔄 Refresh Token 🔧 Reinitialize VoIP 🗑️ Clear Token {/* Logs */} Logs (Last 50) {logs.length === 0 ? ( No logs yet... ) : ( logs.map((log, index) => ( {log} )) )} {/* Instructions */} 📖 Instructions 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 ); }; 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; [/code] Проблема: Я запускаю это на физическом устройстве через TestFlight. Обычные push-уведомления работают. Push-токен VoIP никогда не приходит. Невозможно проверить журналы устройства, поскольку на моем iPhone 16 установлена iOS 26.1, которую Xcode 15.1 не поддерживает, и idevicesyslog завершается сбоем (ОШИБКА: Не удалось подключиться к lockdownd: -18). Я читал, что начиная с iOS 15+ старый ключ разрешения PushKit удаляется и больше не требуется. Мне не хватает каких-либо настроек для получения токена VoIP? Известна ли проблема со сборками TestFlight, не получающими push-токены VoIP? Существуют ли альтернативные способы проверить токен VoIP, когда доступ к журналам устройства недоступен? Будем очень признательны за любые рекомендации. Подробнее здесь: [url]https://stackoverflow.com/questions/79828651/i-am-trying-to-fetch-voip-token-from-apple-in-react-native-but-cannot-seem-to-do[/url]