Я пытаюсь получить токен voip от Apple в React Native, но, похоже, не могу этого сделать, я создаю облако xcode, так какIOS

Программируем под IOS
Ответить
Anonymous
 Я пытаюсь получить токен voip от Apple в React Native, но, похоже, не могу этого сделать, я создаю облако xcode, так как

Сообщение Anonymous »

Я пытаюсь реализовать 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
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»