@trustchex/react-native-sdk 1.253.0 → 1.266.1
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.
- package/README.md +43 -2
- package/android/src/main/java/com/trustchex/reactnativesdk/DeviceBrightnessModule.kt +66 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +12 -0
- package/ios/DeviceBrightnessModule.h +4 -0
- package/ios/DeviceBrightnessModule.m +27 -0
- package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +25 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +19 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +19 -0
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +18 -5
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +10 -2
- package/lib/module/Screens/Static/ResultScreen.js +52 -3
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +41 -2
- package/lib/module/Shared/Components/EIDScanner.js +63 -3
- package/lib/module/Shared/Components/FaceCamera.js +69 -4
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +4 -1
- package/lib/module/Shared/Components/NavigationManager.js +2 -0
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +2 -0
- package/lib/module/Shared/Contexts/AppContext.js +3 -1
- package/lib/module/Shared/Libs/analytics.utils.js +430 -0
- package/lib/module/Shared/Libs/camera.utils.js +58 -2
- package/lib/module/Shared/Libs/deeplink.utils.js +8 -0
- package/lib/module/Shared/Libs/http-client.js +89 -28
- package/lib/module/Shared/Services/AnalyticsService.js +404 -0
- package/lib/module/Shared/Types/analytics.types.js +111 -0
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +1 -0
- package/lib/module/Translation/index.js +5 -0
- package/lib/module/Trustchex.js +47 -4
- package/lib/module/index.js +3 -0
- package/lib/module/version.js +5 -0
- package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +7 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts +98 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +19 -1
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts +86 -0
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts +146 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -0
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Translation/index.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts +1 -0
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +2 -0
- package/lib/typescript/src/version.d.ts.map +1 -0
- package/package.json +6 -2
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +35 -1
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +30 -0
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +30 -0
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +30 -4
- package/src/Screens/Static/QrCodeScanningScreen.tsx +12 -2
- package/src/Screens/Static/ResultScreen.tsx +79 -4
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +65 -10
- package/src/Shared/Components/EIDScanner.tsx +132 -3
- package/src/Shared/Components/FaceCamera.tsx +77 -2
- package/src/Shared/Components/IdentityDocumentCamera.tsx +4 -4
- package/src/Shared/Components/NavigationManager.tsx +2 -0
- package/src/Shared/Components/QrCodeScannerCamera.tsx +2 -0
- package/src/Shared/Contexts/AppContext.ts +4 -0
- package/src/Shared/Libs/analytics.utils.ts +644 -0
- package/src/Shared/Libs/camera.utils.ts +74 -2
- package/src/Shared/Libs/deeplink.utils.ts +5 -0
- package/src/Shared/Libs/http-client.ts +105 -31
- package/src/Shared/Services/AnalyticsService.ts +470 -0
- package/src/Shared/Types/analytics.types.ts +179 -0
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +1 -0
- package/src/Translation/Resources/tr.ts +2 -1
- package/src/Translation/index.ts +9 -0
- package/src/Trustchex.tsx +54 -2
- package/src/index.tsx +33 -0
- package/src/version.ts +3 -0
|
@@ -4,17 +4,19 @@ import React, { useCallback, useContext, useEffect, useMemo, useRef, useState }
|
|
|
4
4
|
import { SafeAreaView, Text, StyleSheet, Alert, View, Image, Dimensions, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
|
5
5
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
6
|
import AppContext from "../../Shared/Contexts/AppContext.js";
|
|
7
|
-
import httpClient, { NotFoundError } from "../../Shared/Libs/http-client.js";
|
|
7
|
+
import httpClient, { BadRequestError, NotFoundError } from "../../Shared/Libs/http-client.js";
|
|
8
8
|
import { useTranslation } from 'react-i18next';
|
|
9
9
|
import LanguageSelector from "../../Shared/Components/LanguageSelector.js";
|
|
10
10
|
import NavigationManager from "../../Shared/Components/NavigationManager.js";
|
|
11
11
|
import StyledButton from "../../Shared/Components/StyledButton.js";
|
|
12
12
|
import StyledTextInput from "../../Shared/Components/StyledTextInput.js";
|
|
13
|
+
import { analyticsService } from "../../Shared/Services/AnalyticsService.js";
|
|
13
14
|
import { useTheme } from "../../Shared/Contexts/ThemeContext.js";
|
|
14
15
|
import LottieView from 'lottie-react-native';
|
|
15
16
|
import { getSimulatedDemoData, isDemoSession } from "../../Shared/Libs/demo.utils.js";
|
|
16
17
|
import { useNavigation } from '@react-navigation/native';
|
|
17
18
|
import { SESSION_CODE_CHARSET_PATTERN, SESSION_CODE_VALIDATION_PATTERN } from "../../Shared/Constants/validation.constants.js";
|
|
19
|
+
import { trackError, trackFunnelStep, useScreenTracking } from "../../Shared/Libs/analytics.utils.js";
|
|
18
20
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
21
|
const VerificationSessionCheckScreen = () => {
|
|
20
22
|
const [sessionCode, setSessionCode] = useState('');
|
|
@@ -33,6 +35,9 @@ const VerificationSessionCheckScreen = () => {
|
|
|
33
35
|
const insets = useSafeAreaInsets();
|
|
34
36
|
const theme = useTheme();
|
|
35
37
|
const primaryColor = theme.colors.primary;
|
|
38
|
+
|
|
39
|
+
// Track screen view and exit
|
|
40
|
+
useScreenTracking('verification_session_check');
|
|
36
41
|
const apiUrl = useMemo(() => `${appContext.baseUrl}/api/app/mobile`, [appContext.baseUrl]);
|
|
37
42
|
const validateSessionCode = useCallback(value => {
|
|
38
43
|
return SESSION_CODE_VALIDATION_PATTERN.test(value.toUpperCase());
|
|
@@ -43,21 +48,37 @@ const VerificationSessionCheckScreen = () => {
|
|
|
43
48
|
return response;
|
|
44
49
|
} catch (error) {
|
|
45
50
|
if (error instanceof NotFoundError) {
|
|
51
|
+
// User entered invalid session ID - expected user behavior, not actionable
|
|
46
52
|
Alert.alert(t('general.error'), t('verificationSessionCheckScreen.noVerificationSessionFound'));
|
|
53
|
+
} else {
|
|
54
|
+
trackError('SESSION_CHECK_ERROR', error instanceof Error ? error.message : 'Unknown error', 'verification_session_check', 'high', {
|
|
55
|
+
recoverable: false,
|
|
56
|
+
userAction: 'check_session'
|
|
57
|
+
});
|
|
47
58
|
}
|
|
48
59
|
}
|
|
49
60
|
}, [apiUrl, appContext.isDemoSession, t]);
|
|
50
61
|
const getSessionByCode = useCallback(async inputCode => {
|
|
51
62
|
try {
|
|
52
63
|
appContext.isDemoSession = isDemoSession(inputCode);
|
|
64
|
+
analyticsService.setDemoSession(appContext.isDemoSession);
|
|
53
65
|
const response = await httpClient.get(`${apiUrl}/verification-sessions?sessionCode=${inputCode.toUpperCase()}`, appContext.isDemoSession ? getSimulatedDemoData('GET_SESSION_BY_CODE') : undefined);
|
|
54
66
|
if (!response || !response.id) {
|
|
55
67
|
throw new NotFoundError();
|
|
56
68
|
}
|
|
69
|
+
|
|
70
|
+
// Track successful session start as funnel step
|
|
71
|
+
trackFunnelStep('Session Started', 1, 5, undefined, true);
|
|
57
72
|
return response;
|
|
58
73
|
} catch (error) {
|
|
59
74
|
if (error instanceof NotFoundError) {
|
|
75
|
+
// User entered invalid session code - expected user behavior, not actionable
|
|
60
76
|
Alert.alert(t('general.error'), t('verificationSessionCheckScreen.noVerificationSessionFound'));
|
|
77
|
+
} else {
|
|
78
|
+
trackError('SESSION_CODE_CHECK_ERROR', error instanceof Error ? error.message : 'Unknown error', 'verification_session_check', 'high', {
|
|
79
|
+
recoverable: false,
|
|
80
|
+
userAction: 'check_session_code'
|
|
81
|
+
});
|
|
61
82
|
}
|
|
62
83
|
}
|
|
63
84
|
}, [apiUrl, appContext, t]);
|
|
@@ -67,8 +88,13 @@ const VerificationSessionCheckScreen = () => {
|
|
|
67
88
|
return true;
|
|
68
89
|
} catch (error) {
|
|
69
90
|
if (error instanceof NotFoundError) {
|
|
91
|
+
// Session expired or invalid - expected behavior, not actionable
|
|
70
92
|
Alert.alert(t('general.error'), t('verificationSessionCheckScreen.noVerificationSessionFound'));
|
|
71
93
|
} else {
|
|
94
|
+
trackError('VERIFICATION_CODE_SEND_FAILED', 'Failed to send verification code', 'verification_session_check', 'high', {
|
|
95
|
+
recoverable: true,
|
|
96
|
+
userAction: 'send_verification_code'
|
|
97
|
+
});
|
|
72
98
|
Alert.alert(t('general.error'), t('verificationSessionCheckScreen.cannotSendVerificationCode'));
|
|
73
99
|
}
|
|
74
100
|
}
|
|
@@ -84,7 +110,15 @@ const VerificationSessionCheckScreen = () => {
|
|
|
84
110
|
return response;
|
|
85
111
|
} catch (error) {
|
|
86
112
|
if (error instanceof NotFoundError) {
|
|
113
|
+
// Session expired or invalid - expected behavior, not actionable
|
|
87
114
|
Alert.alert(t('general.error'), t('verificationSessionCheckScreen.noVerificationSessionFound'));
|
|
115
|
+
} else if (error instanceof BadRequestError) {
|
|
116
|
+
// Wrong OTP code - expected user behavior, not actionable
|
|
117
|
+
} else {
|
|
118
|
+
trackError('VERIFIED_SESSION_CHECK_ERROR', error instanceof Error ? error.message : 'Unknown error', 'verification_session_check', 'high', {
|
|
119
|
+
recoverable: false,
|
|
120
|
+
userAction: 'check_verified_session'
|
|
121
|
+
});
|
|
88
122
|
}
|
|
89
123
|
}
|
|
90
124
|
}, [apiUrl, appContext.isDemoSession, t]);
|
|
@@ -176,7 +210,11 @@ const VerificationSessionCheckScreen = () => {
|
|
|
176
210
|
setIsCheckingSession(true);
|
|
177
211
|
const session = await getSessionByCode(alphanumericText);
|
|
178
212
|
if (session?.id) {
|
|
179
|
-
appContext.
|
|
213
|
+
if (appContext.setSessionId) {
|
|
214
|
+
appContext.setSessionId(session.id);
|
|
215
|
+
} else {
|
|
216
|
+
appContext.identificationInfo.sessionId = session.id;
|
|
217
|
+
}
|
|
180
218
|
} else {
|
|
181
219
|
setSessionCode('');
|
|
182
220
|
}
|
|
@@ -240,6 +278,7 @@ const VerificationSessionCheckScreen = () => {
|
|
|
240
278
|
setIsCodeSent(false);
|
|
241
279
|
navigationManagerRef.current?.navigateToNextStep();
|
|
242
280
|
} else {
|
|
281
|
+
// User entered wrong OTP code - expected behavior, not actionable
|
|
243
282
|
appContext.onError?.('Invalid OTP code');
|
|
244
283
|
Alert.alert(t('general.error'), t('verificationSessionCheckScreen.codeError'));
|
|
245
284
|
setCode('');
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
4
4
|
import { Alert, View, Text, Image, StyleSheet } from 'react-native';
|
|
5
5
|
import NFCManager from 'react-native-nfc-manager';
|
|
6
|
+
import DeviceInfo from 'react-native-device-info';
|
|
6
7
|
import { eidReader } from "../EIDReader/eidReader.js";
|
|
7
8
|
import NativeProgressBar from "./NativeProgressBar.js";
|
|
8
9
|
import { useTranslation } from 'react-i18next';
|
|
@@ -11,6 +12,7 @@ import StyledButton from "./StyledButton.js";
|
|
|
11
12
|
import LottieView from 'lottie-react-native';
|
|
12
13
|
import { useKeepAwake } from "../Libs/native-keep-awake.utils.js";
|
|
13
14
|
import { speakWithDebounce } from "../Libs/tts.utils.js";
|
|
15
|
+
import { trackEIDScanStart, trackEIDScanComplete, trackEIDScanFailed } from "../Libs/analytics.utils.js";
|
|
14
16
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
15
17
|
const EIDScanner = ({
|
|
16
18
|
documentNumber,
|
|
@@ -35,33 +37,85 @@ const EIDScanner = ({
|
|
|
35
37
|
} = useTranslation();
|
|
36
38
|
const appContext = React.useContext(AppContext);
|
|
37
39
|
const [voiceGuidanceMessage, setVoiceGuidanceMessage] = useState();
|
|
40
|
+
const [attemptNumber, setAttemptNumber] = useState(0);
|
|
38
41
|
const readNFC = useCallback(async () => {
|
|
42
|
+
const startTime = Date.now();
|
|
43
|
+
const currentAttempt = attemptNumber + 1;
|
|
44
|
+
setAttemptNumber(currentAttempt);
|
|
39
45
|
setIsScanning(true);
|
|
40
46
|
setIsScanned(false);
|
|
41
47
|
setProgress(0);
|
|
48
|
+
|
|
49
|
+
// Track EID scan start using analytics helper
|
|
50
|
+
const docType = documentType || 'UNKNOWN';
|
|
51
|
+
|
|
52
|
+
// If device doesn't support NFC or NFC is disabled, track and abort early
|
|
53
|
+
if (!hasNfc) {
|
|
54
|
+
await trackEIDScanFailed(docType, 'device_unsupported', 'Device does not support NFC', 0, currentAttempt, DeviceInfo.getModel(), DeviceInfo.getSystemVersion()).catch(() => {});
|
|
55
|
+
|
|
56
|
+
// Stop scanning - user will see the not-supported UI
|
|
57
|
+
setIsScanning(false);
|
|
58
|
+
setProgress(0);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!isEnabled) {
|
|
62
|
+
await trackEIDScanFailed(docType, 'not_enabled', 'NFC is disabled on device', 0, currentAttempt, DeviceInfo.getModel(), DeviceInfo.getSystemVersion()).catch(() => {});
|
|
63
|
+
|
|
64
|
+
// Stop scanning - allow the UI to prompt user to enable NFC
|
|
65
|
+
setIsScanning(false);
|
|
66
|
+
setProgress(0);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
await trackEIDScanStart(docType, hasNfc, isEnabled, currentAttempt).catch(() => {});
|
|
42
70
|
try {
|
|
43
71
|
if (documentNumber && dateOfBirth && dateOfExpiry) {
|
|
44
72
|
const passportData = await eidReader(documentNumber, dateOfBirth, dateOfExpiry, progressValue => {
|
|
45
73
|
setProgress(progressValue);
|
|
46
74
|
});
|
|
47
75
|
if (passportData) {
|
|
76
|
+
const scanDuration = Date.now() - startTime;
|
|
48
77
|
setDocumentFaceImage(passportData.faceImage);
|
|
49
78
|
setDocumentMRZInfo(passportData.mrz);
|
|
50
79
|
setDocumentFaceImageMimeType(passportData.mimeType);
|
|
51
80
|
setIsScanned(true);
|
|
81
|
+
|
|
82
|
+
// Track successful EID scan
|
|
83
|
+
await trackEIDScanComplete(docType, scanDuration, currentAttempt).catch(() => {});
|
|
52
84
|
}
|
|
53
85
|
} else {
|
|
86
|
+
const scanDuration = Date.now() - startTime;
|
|
87
|
+
// MRZ input issue — track as NFC scan failure (unknown error type for NFC-specific stream)
|
|
88
|
+
await trackEIDScanFailed(docType, 'unknown', 'Invalid MRZ fields', scanDuration, currentAttempt, DeviceInfo.getModel(), DeviceInfo.getSystemVersion()).catch(() => {});
|
|
54
89
|
Alert.alert(t('general.error'), t('eidScannerScreen.invalidMRZFields'));
|
|
55
90
|
}
|
|
56
91
|
} catch (error) {
|
|
57
|
-
|
|
58
|
-
|
|
92
|
+
const scanDuration = Date.now() - startTime;
|
|
93
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
94
|
+
|
|
95
|
+
// Determine error type
|
|
96
|
+
let errorType = 'unknown';
|
|
97
|
+
if (errorMessage.includes('cancelled') || errorMessage.includes('cancel')) {
|
|
98
|
+
errorType = 'user_cancelled';
|
|
99
|
+
} else if (errorMessage.includes('timeout') || errorMessage.includes('time out')) {
|
|
100
|
+
errorType = 'timeout';
|
|
101
|
+
} else if (errorMessage.includes('not supported')) {
|
|
102
|
+
errorType = 'device_unsupported';
|
|
103
|
+
} else if (errorMessage.includes('not enabled') || errorMessage.includes('disabled')) {
|
|
104
|
+
errorType = 'not_enabled';
|
|
105
|
+
} else if (errorMessage.includes('read') || errorMessage.includes('communication')) {
|
|
106
|
+
errorType = 'reading_error';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Track EID scan failure with detailed metadata
|
|
110
|
+
await trackEIDScanFailed(docType, errorType, errorMessage, scanDuration, currentAttempt, DeviceInfo.getModel(), DeviceInfo.getSystemVersion()).catch(() => {});
|
|
111
|
+
console.debug('NFC scan error:', error);
|
|
112
|
+
// Ignore error - let user retry
|
|
59
113
|
} finally {
|
|
60
114
|
setIsScanning(false);
|
|
61
115
|
setProgress(0);
|
|
62
116
|
setHasGuideShown(false);
|
|
63
117
|
}
|
|
64
|
-
}, [documentNumber, dateOfBirth, dateOfExpiry, t]);
|
|
118
|
+
}, [documentNumber, dateOfBirth, dateOfExpiry, documentType, hasNfc, isEnabled, attemptNumber, t]);
|
|
65
119
|
const getFieldsFromMRZ = useCallback(mrz => {
|
|
66
120
|
return {
|
|
67
121
|
documentCode: mrz.getDocumentCode(),
|
|
@@ -81,8 +135,14 @@ const EIDScanner = ({
|
|
|
81
135
|
const deviceIsSupported = await NFCManager.isSupported();
|
|
82
136
|
setHasNFC(deviceIsSupported);
|
|
83
137
|
isNFCSupported && isNFCSupported(deviceIsSupported);
|
|
138
|
+
|
|
139
|
+
// NFC not supported is a device limitation - not actionable by developers
|
|
140
|
+
|
|
84
141
|
const deviceIsEnabled = await NFCManager.isEnabled();
|
|
85
142
|
setIsEnabled(deviceIsEnabled);
|
|
143
|
+
|
|
144
|
+
// NFC disabled is a user setting - they can enable it in settings
|
|
145
|
+
// Not tracking as it's user-controllable
|
|
86
146
|
};
|
|
87
147
|
checkIsSupported();
|
|
88
148
|
}, [isNFCSupported, isScanning]);
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
import { useIsFocused } from '@react-navigation/native';
|
|
4
4
|
import { useKeepAwake } from "../Libs/native-keep-awake.utils.js";
|
|
5
5
|
import React, { useEffect, useState } from 'react';
|
|
6
|
-
import { StyleSheet, Text, View, Platform, Linking, Dimensions, ActivityIndicator } from 'react-native';
|
|
6
|
+
import { StyleSheet, Text, View, Platform, Linking, Dimensions, ActivityIndicator, NativeModules } from 'react-native';
|
|
7
7
|
import { useCameraDevice, useCameraPermission, Camera, useFrameProcessor, useCameraFormat, runAtTargetFps, useMicrophonePermission } from 'react-native-vision-camera';
|
|
8
8
|
import { runAsync } from "../Libs/worklet.utils.js";
|
|
9
9
|
import { useFaceDetector } from "../VisionCameraPlugins/FaceDetector/index.js";
|
|
10
10
|
import { Worklets, useSharedValue } from 'react-native-worklets-core';
|
|
11
11
|
import { crop } from "../VisionCameraPlugins/Cropper/index.js";
|
|
12
|
-
import {
|
|
12
|
+
import { isCircularRegionBright } from "../Libs/camera.utils.js";
|
|
13
13
|
import { useTranslation } from 'react-i18next';
|
|
14
14
|
import StyledButton from "./StyledButton.js";
|
|
15
15
|
import { useTheme } from "../Contexts/ThemeContext.js";
|
|
@@ -20,7 +20,8 @@ const windowWidth = Dimensions.get('window').width;
|
|
|
20
20
|
const windowHeight = Dimensions.get('window').height;
|
|
21
21
|
const FaceCamera = ({
|
|
22
22
|
onFacesDetected,
|
|
23
|
-
onCameraInitialized
|
|
23
|
+
onCameraInitialized,
|
|
24
|
+
previewRect
|
|
24
25
|
}) => {
|
|
25
26
|
useKeepAwake();
|
|
26
27
|
const theme = useTheme();
|
|
@@ -84,11 +85,72 @@ const FaceCamera = ({
|
|
|
84
85
|
setIsActive(false);
|
|
85
86
|
};
|
|
86
87
|
}, [device, format, isFocused]);
|
|
88
|
+
|
|
89
|
+
// Set screen brightness to maximum when camera is active
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const {
|
|
92
|
+
DeviceBrightness
|
|
93
|
+
} = NativeModules;
|
|
94
|
+
if (!DeviceBrightness) return;
|
|
95
|
+
let originalBrightness = -1;
|
|
96
|
+
const setBrightness = async () => {
|
|
97
|
+
if (isActive) {
|
|
98
|
+
try {
|
|
99
|
+
originalBrightness = await DeviceBrightness.getBrightness();
|
|
100
|
+
await DeviceBrightness.setBrightness(1.0);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.log('Failed to set brightness:', error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
setBrightness();
|
|
107
|
+
return () => {
|
|
108
|
+
if (originalBrightness >= 0) {
|
|
109
|
+
DeviceBrightness.setBrightness(originalBrightness).catch(() => {});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}, [isActive]);
|
|
87
113
|
const handleWorklet = frame => {
|
|
88
114
|
'worklet';
|
|
89
115
|
|
|
90
116
|
try {
|
|
91
|
-
|
|
117
|
+
// Calculate brightness based on the circular preview area if provided
|
|
118
|
+
// Otherwise fall back to entire frame brightness
|
|
119
|
+
let isBright = false;
|
|
120
|
+
if (previewRect) {
|
|
121
|
+
// Convert preview rect from screen coordinates to frame coordinates
|
|
122
|
+
const scaleX = frame.width / windowWidth;
|
|
123
|
+
const scaleY = frame.height / windowHeight;
|
|
124
|
+
const frameCircleRect = {
|
|
125
|
+
minX: Math.floor(previewRect.minX * scaleX),
|
|
126
|
+
minY: Math.floor(previewRect.minY * scaleY),
|
|
127
|
+
width: Math.floor(previewRect.width * scaleX),
|
|
128
|
+
height: Math.floor(previewRect.height * scaleY)
|
|
129
|
+
};
|
|
130
|
+
isBright = isCircularRegionBright(frame, frameCircleRect, 60);
|
|
131
|
+
} else {
|
|
132
|
+
// Fallback: check entire frame brightness (use legacy method)
|
|
133
|
+
const buffer = frame.toArrayBuffer();
|
|
134
|
+
const data = new Uint8Array(buffer);
|
|
135
|
+
const width = frame.width;
|
|
136
|
+
const height = frame.height;
|
|
137
|
+
let luminanceSum = 0;
|
|
138
|
+
let pixelCount = 0;
|
|
139
|
+
const centerX = Math.floor(width / 2);
|
|
140
|
+
const centerY = Math.floor(height / 2);
|
|
141
|
+
const halfSizeX = Math.floor(width / 2);
|
|
142
|
+
const halfSizeY = Math.floor(height / 2);
|
|
143
|
+
for (let y = centerY - halfSizeY; y < centerY + halfSizeY; y++) {
|
|
144
|
+
for (let x = centerX - halfSizeX; x < centerX + halfSizeX; x++) {
|
|
145
|
+
const index = y * width + x;
|
|
146
|
+
if (data[index] !== undefined) {
|
|
147
|
+
luminanceSum += data[index];
|
|
148
|
+
pixelCount++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
isBright = luminanceSum / pixelCount > 60;
|
|
153
|
+
}
|
|
92
154
|
const image = crop(frame, {
|
|
93
155
|
cropRegion: {
|
|
94
156
|
top: 0,
|
|
@@ -139,6 +201,7 @@ const FaceCamera = ({
|
|
|
139
201
|
});
|
|
140
202
|
}
|
|
141
203
|
if (!cameraPermission.hasPermission) {
|
|
204
|
+
// Camera permission denied by user - their choice, not actionable
|
|
142
205
|
return /*#__PURE__*/_jsxs(View, {
|
|
143
206
|
style: styles.permissionContainer,
|
|
144
207
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
@@ -154,6 +217,7 @@ const FaceCamera = ({
|
|
|
154
217
|
});
|
|
155
218
|
}
|
|
156
219
|
if (!microphonePermission.hasPermission) {
|
|
220
|
+
// Microphone permission denied by user - their choice, not actionable
|
|
157
221
|
return /*#__PURE__*/_jsxs(View, {
|
|
158
222
|
style: styles.permissionContainer,
|
|
159
223
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
@@ -169,6 +233,7 @@ const FaceCamera = ({
|
|
|
169
233
|
});
|
|
170
234
|
}
|
|
171
235
|
if (device == null) {
|
|
236
|
+
// No camera device - device limitation, not actionable
|
|
172
237
|
return /*#__PURE__*/_jsx(View, {
|
|
173
238
|
style: styles.permissionContainer,
|
|
174
239
|
children: /*#__PURE__*/_jsx(Text, {
|
|
@@ -299,7 +299,10 @@ const IdentityDocumentCamera = ({
|
|
|
299
299
|
|
|
300
300
|
// Add validation for face bounds to prevent invalid crop operations
|
|
301
301
|
if (face.bounds.x < 0 || face.bounds.y < 0 || face.bounds.width <= 0 || face.bounds.height <= 0 || face.bounds.x >= width || face.bounds.y >= height) {
|
|
302
|
-
console.warn(
|
|
302
|
+
// console.warn(
|
|
303
|
+
// 'Invalid face bounds detected, skipping face:',
|
|
304
|
+
// face.bounds
|
|
305
|
+
// );
|
|
303
306
|
continue;
|
|
304
307
|
}
|
|
305
308
|
|
|
@@ -7,6 +7,7 @@ import { View, StyleSheet, Alert } from 'react-native';
|
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
import i18n from "../../Translation/index.js";
|
|
9
9
|
import StyledButton from "./StyledButton.js";
|
|
10
|
+
import { analyticsService } from "../Services/AnalyticsService.js";
|
|
10
11
|
|
|
11
12
|
// Simple global navigation lock
|
|
12
13
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
@@ -107,6 +108,7 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
107
108
|
appContext.currentWorkflowStep = undefined;
|
|
108
109
|
appContext.workflowSteps = undefined;
|
|
109
110
|
appContext.isDemoSession = false;
|
|
111
|
+
analyticsService.setDemoSession(false);
|
|
110
112
|
appContext.identificationInfo = {
|
|
111
113
|
sessionId: '',
|
|
112
114
|
identificationId: '',
|
|
@@ -131,6 +131,7 @@ const QrCodeScannerCamera = ({
|
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
if (!cameraPermission.hasPermission) {
|
|
134
|
+
// Camera permission denied by user - their choice, not actionable
|
|
134
135
|
return /*#__PURE__*/_jsxs(View, {
|
|
135
136
|
style: styles.permissionContainer,
|
|
136
137
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
@@ -146,6 +147,7 @@ const QrCodeScannerCamera = ({
|
|
|
146
147
|
});
|
|
147
148
|
}
|
|
148
149
|
if (device == null) {
|
|
150
|
+
// No camera device - device limitation, not actionable
|
|
149
151
|
return /*#__PURE__*/_jsx(View, {
|
|
150
152
|
style: styles.permissionContainer,
|
|
151
153
|
children: /*#__PURE__*/_jsx(TextView, {
|