На Android все работает нормально. В iOS порядок действий следующий:
- HyperVerge успешно фиксирует лицо пользователя
- Когда SDK запрашивает захват документа и я выбираю «Снимок с камеры», приложение немедленно аварийно завершает работу
- Я не вижу ошибок JS — только родной сбой:
Код: Выделить всё
Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
Встроенная настройка iOS (AppDelegate.swift)
Это мой AppDelegate, созданный Expo с помощью React Native + Expo Factory:
Код: Выделить всё
import Expo
import React
import ReactAppDependencyProvider
@UIApplicationMain
public class AppDelegate: ExpoAppDelegate {
var window: UIWindow?
var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
var reactNativeFactory: RCTReactNativeFactory?
public override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
let delegate = ReactNativeDelegate()
let factory = ExpoReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
reactNativeFactory = factory
bindReactNativeFactory(factory)
#if os(iOS) || os(tvOS)
window = UIWindow(frame: UIScreen.main.bounds)
factory.startReactNative(
withModuleName: "main",
in: window,
launchOptions: launchOptions)
#endif
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// Linking API
public override func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
}
// Universal Links
public override func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
}
}
class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
// Extension point for config-plugins
override func sourceURL(for bridge: RCTBridge) -> URL? {
// needed to return the correct URL for expo-dev-client.
bridge.bundleURL ?? bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
#else
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}
Я вызываю HyperVerge SDK с экрана KYC:
Код: Выделить всё
import { useNavigation } from '@react-navigation/native';
import * as ImagePicker from 'expo-image-picker';
import React, { useRef, useState } from 'react';
import { NativeModules, Platform, StyleSheet, View } from 'react-native';
import {
AuthButton,
AuthFormCard,
AuthHeader,
} from '../../components/auth';
import {
BenefitItem,
InfoSection,
KYCScreenLayout,
SkipButton,
useHideBottomNav,
} from '../../components/kyc';
import { HYPERKYC_CONFIG, generateTransactionId } from '../../constants/hyperkyc';
import { Spacing } from '../../constants/theme';
import { useAlert } from '../../contexts/AlertContext';
const { Hyperkyc } = NativeModules;
export default function KYCScreen() {
const navigation = useNavigation();
const { showError } = useAlert();
const [loading, setLoading] = useState(false);
const transactionIdRef = useRef(generateTransactionId());
useHideBottomNav();
const handleStartKYC = async () => {
setLoading(true);
try {
// iOS camera permission
if (Platform.OS === 'ios') {
const { status } = await ImagePicker.requestCameraPermissionsAsync();
if (status !== 'granted') {
showError(
'Camera Permission Required',
'Camera access is required for KYC verification. Please enable camera permissions in Settings.'
);
setLoading(false);
return;
}
}
if (!Hyperkyc) {
showError('SDK Error', 'HyperKYC SDK module not found. Please ensure the native module is properly linked.');
setLoading(false);
return;
}
if (
!HYPERKYC_CONFIG.appId ||
!HYPERKYC_CONFIG.workflowId ||
HYPERKYC_CONFIG.appId === 'YOUR_APP_ID' ||
HYPERKYC_CONFIG.workflowId === 'YOUR_WORKFLOW_ID'
) {
showError('Configuration Error', 'HyperKYC credentials not configured.');
setLoading(false);
return;
}
// new transaction ID
transactionIdRef.current = generateTransactionId();
const configDictionary: any = {
appId: HYPERKYC_CONFIG.appId,
appKey: HYPERKYC_CONFIG.appKey,
transactionId: transactionIdRef.current,
workflowId: HYPERKYC_CONFIG.workflowId,
inputs: {},
};
// Unique ID
if (Hyperkyc.createUniqueId) {
await new Promise((resolve, reject) => {
Hyperkyc.createUniqueId((uniqueId: string) => {
if (uniqueId) {
configDictionary.uniqueId = uniqueId;
resolve();
} else {
reject(new Error('Failed to generate unique ID'));
}
});
});
}
console.log('HyperKYC Config:', configDictionary);
console.log('Launching HyperKYC...');
Hyperkyc.launch(configDictionary, (response: any) => {
setLoading(false);
console.log('HyperKYC Response:', response);
if (Hyperkyc && Hyperkyc.removeAllEventListeners) {
Hyperkyc.removeAllEventListeners();
}
const sdkStatus = response?.status || 'needs_review';
if (sdkStatus === 'auto_approved') {
handleVerificationComplete();
} else if (sdkStatus === 'auto_declined') {
handleVerificationError('KYC verification was declined.');
} else if (sdkStatus === 'user_cancelled') {
console.log('User cancelled KYC verification');
} else if (
sdkStatus === 'needs_review' ||
sdkStatus === 'success' ||
sdkStatus === 'completed'
) {
handleVerificationComplete();
} else if (sdkStatus === 'error' || sdkStatus === 'failed') {
handleVerificationError(response?.error || response?.message || 'Verification failed');
} else {
console.log('Unknown KYC status:', sdkStatus);
handleVerificationComplete();
}
});
} catch (error: any) {
setLoading(false);
showError('Verification Failed', error?.message || 'Failed to start verification');
console.error('HyperKYC launch error:', error);
}
};
const handleVerificationComplete = () => {
navigation.navigate('KYCComplete' as never);
};
const handleVerificationError = (error: any) => {
const errorMessage = typeof error === 'string' ? error : error?.message || 'An error occurred during verification';
showError('Verification Error', errorMessage);
};
const handleSkipForNow = () => {
navigation.navigate('RiskProfile' as never);
};
// ... JSX omitted for brevity
}
- Этап захвата лица работает
- Когда HyperVerge запрашивает документ и я нажимаю Захватить с камеры, приложение вылетает с ошибкой:
Код: Выделить всё
Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
- Нет полезных журналов в консоли Metro / JS
- Реализация Android с тем же кодом RN работает нормально
- Expo SDK: (например, 51/52 — пожалуйста, заполните)
- React Native: (версия, используемая текущим Expo SDK)
- HyperVerge RN SDK: (точная версия)
- iOS: (устройство или симулятор, например iOS) 18 на iPhone 15)
- Xcode: (версия)
- Проверенные разрешения камеры на iOS (с помощью expo-image-picker)
- Проверено, что собственный модуль Hyperkyc определен
- Проверено, что appId, appKey и workflowId установлены правильно
- Записан словарь конфигурации перед вызовом Hyperkyc.launch
- Очищено папка сборки, переустановка модулей, пересборка приложения
- Проверено как на симуляторе, так и на физическом устройстве
- Есть ли что-то явно неправильное или отсутствующее в моей интеграции с iOS/Expo, что может привести к EXC_BAD_ACCESSкогда HyperVerge открывает камеру для шага документа?
- Известна ли какая-либо несовместимость между RN SDK HyperVerge и клиентом разработки Expo / Expo на iOS?
- Как я могу получить более подробную информацию о сбое для такого рода EXC_BAD_ACCESS (любые конкретные настройки Xcode/символики, которые мне следует включить)?
Подробнее здесь: https://stackoverflow.com/questions/798 ... ive-expo-a