@trustchex/react-native-sdk 1.381.0 → 1.464.0
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/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKModule.kt +2 -8
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +60 -13
- package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -1
- package/ios/Camera/TrustchexCameraView.swift +10 -13
- package/ios/MLKit/MLKitModule.swift +1 -1
- package/lib/module/Screens/Debug/BarcodeTestScreen.js +308 -0
- package/lib/module/Screens/Debug/MRZTestScreen.js +105 -13
- package/lib/module/Screens/Debug/NFCScanTestScreen.js +635 -0
- package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +49 -32
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +22 -4
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -0
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +126 -27
- package/lib/module/Screens/Dynamic/VerbalConsentScreen.js +1079 -0
- package/lib/module/Screens/Dynamic/VideoCallScreen.js +678 -0
- package/lib/module/Screens/Static/OTPVerificationScreen.js +6 -0
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +7 -1
- package/lib/module/Screens/Static/ResultScreen.js +154 -34
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +59 -51
- package/lib/module/Shared/Animations/recording.json +1 -0
- package/lib/module/Shared/Animations/video-call.json +1 -0
- package/lib/module/Shared/Components/DebugNavigationPanel.js +231 -67
- package/lib/module/Shared/Components/EIDScanner.js +213 -112
- package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +5 -3
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +77 -39
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +13 -4
- package/lib/module/Shared/Components/NavigationManager.js +39 -19
- package/lib/module/Shared/Contexts/AppContext.js +1 -0
- package/lib/module/Shared/EIDReader/aesSecureMessagingWrapper.js +51 -0
- package/lib/module/Shared/EIDReader/apduLevelPACECapable.js +3 -0
- package/lib/module/Shared/EIDReader/bacKey.js +16 -2
- package/lib/module/Shared/EIDReader/eidReader.js +354 -13
- package/lib/module/Shared/EIDReader/eidService.js +25 -1
- package/lib/module/Shared/EIDReader/nfcManagerCardService.js +4 -7
- package/lib/module/Shared/EIDReader/paceInfo.js +85 -0
- package/lib/module/Shared/EIDReader/paceKeySpec.js +51 -0
- package/lib/module/Shared/EIDReader/protocol/paceAPDUSender.js +100 -0
- package/lib/module/Shared/EIDReader/protocol/paceProtocol.js +655 -0
- package/lib/module/Shared/EIDReader/protocol/paceResult.js +37 -0
- package/lib/module/Shared/EIDReader/secureMessagingWrapper.js +27 -4
- package/lib/module/Shared/EIDReader/smartcards/commandAPDU.js +2 -1
- package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +1 -1
- package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +6 -3
- package/lib/module/Shared/EIDReader/utils/aesCrypto.utils.js +189 -0
- package/lib/module/Shared/Libs/SignalingClient.js +128 -0
- package/lib/module/Shared/Libs/analytics.utils.js +8 -0
- package/lib/module/Shared/Libs/contains.js +1 -40
- package/lib/module/Shared/Libs/country-display.utils.js +34 -0
- package/lib/module/Shared/Libs/deeplink.utils.js +9 -1
- package/lib/module/Shared/Libs/demo.utils.js +8 -0
- package/lib/module/Shared/Libs/http-client.js +9 -0
- package/lib/module/Shared/Libs/mrz.utils.js +3 -2
- package/lib/module/Shared/Libs/promise.utils.js +16 -2
- package/lib/module/Shared/Libs/status-bar.utils.js +23 -0
- package/lib/module/Shared/Services/DataUploadService.js +294 -0
- package/lib/module/Shared/Services/VideoSessionService.js +156 -0
- package/lib/module/Shared/Services/WebRTCService.js +510 -0
- package/lib/module/Shared/Types/analytics.types.js +4 -0
- package/lib/module/Translation/Resources/en.js +61 -2
- package/lib/module/Translation/Resources/tr.js +61 -2
- package/lib/module/Trustchex.js +64 -20
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts.map +1 -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/Dynamic/VerbalConsentScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Static/OTPVerificationScreen.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/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +5 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +1 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts +18 -0
- package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts +23 -0
- package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts +6 -0
- package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/eidService.d.ts +9 -0
- package/lib/typescript/src/Shared/EIDReader/eidService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/nfcManagerCardService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts +50 -0
- package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts +30 -0
- package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts +17 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts +105 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts +24 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts +15 -0
- package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/smartcards/commandAPDU.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts +39 -0
- package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts +24 -0
- package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/contains.d.ts +0 -7
- package/lib/typescript/src/Shared/Libs/contains.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts +2 -0
- package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/promise.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts +9 -0
- package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +25 -0
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts +33 -0
- package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/WebRTCService.d.ts +58 -0
- package/lib/typescript/src/Shared/Services/WebRTCService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts +4 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +13 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +60 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +60 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +35 -5
- package/src/Screens/Debug/BarcodeTestScreen.tsx +317 -0
- package/src/Screens/Debug/MRZTestScreen.tsx +107 -13
- package/src/Screens/Debug/NFCScanTestScreen.tsx +692 -0
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +58 -35
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +27 -4
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +6 -0
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +156 -27
- package/src/Screens/Dynamic/VerbalConsentScreen.tsx +1401 -0
- package/src/Screens/Dynamic/VideoCallScreen.tsx +766 -0
- package/src/Screens/Static/OTPVerificationScreen.tsx +6 -0
- package/src/Screens/Static/QrCodeScanningScreen.tsx +7 -1
- package/src/Screens/Static/ResultScreen.tsx +235 -48
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +67 -72
- package/src/Shared/Animations/recording.json +1 -0
- package/src/Shared/Animations/video-call.json +1 -0
- package/src/Shared/Components/DebugNavigationPanel.tsx +252 -51
- package/src/Shared/Components/EIDScanner.tsx +223 -116
- package/src/Shared/Components/IdentityDocumentCamera.flows.ts +7 -4
- package/src/Shared/Components/IdentityDocumentCamera.tsx +224 -188
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +13 -4
- package/src/Shared/Components/NavigationManager.tsx +41 -19
- package/src/Shared/Contexts/AppContext.ts +2 -0
- package/src/Shared/EIDReader/aesSecureMessagingWrapper.ts +69 -0
- package/src/Shared/EIDReader/apduLevelPACECapable.ts +34 -0
- package/src/Shared/EIDReader/bacKey.ts +24 -8
- package/src/Shared/EIDReader/eidReader.ts +398 -12
- package/src/Shared/EIDReader/eidService.ts +49 -1
- package/src/Shared/EIDReader/nfcManagerCardService.ts +4 -6
- package/src/Shared/EIDReader/paceInfo.ts +159 -0
- package/src/Shared/EIDReader/paceKeySpec.ts +56 -0
- package/src/Shared/EIDReader/protocol/paceAPDUSender.ts +163 -0
- package/src/Shared/EIDReader/protocol/paceProtocol.ts +946 -0
- package/src/Shared/EIDReader/protocol/paceResult.ts +62 -0
- package/src/Shared/EIDReader/secureMessagingWrapper.ts +28 -10
- package/src/Shared/EIDReader/smartcards/commandAPDU.ts +2 -1
- package/src/Shared/EIDReader/tlv/tlv.helpers.ts +1 -1
- package/src/Shared/EIDReader/tlv/tlv.utils.ts +8 -5
- package/src/Shared/EIDReader/utils/aesCrypto.utils.ts +217 -0
- package/src/Shared/Libs/SignalingClient.ts +189 -0
- package/src/Shared/Libs/analytics.utils.ts +8 -0
- package/src/Shared/Libs/contains.ts +0 -53
- package/src/Shared/Libs/country-display.utils.ts +55 -0
- package/src/Shared/Libs/crypto.utils.ts +2 -2
- package/src/Shared/Libs/deeplink.utils.ts +12 -1
- package/src/Shared/Libs/demo.utils.ts +10 -0
- package/src/Shared/Libs/http-client.ts +19 -1
- package/src/Shared/Libs/mrz.utils.ts +3 -2
- package/src/Shared/Libs/promise.utils.ts +16 -2
- package/src/Shared/Libs/status-bar.utils.ts +21 -0
- package/src/Shared/Services/DataUploadService.ts +395 -0
- package/src/Shared/Services/VideoSessionService.ts +190 -0
- package/src/Shared/Services/WebRTCService.ts +636 -0
- package/src/Shared/Types/analytics.types.ts +4 -0
- package/src/Shared/Types/identificationInfo.ts +16 -1
- package/src/Translation/Resources/en.ts +88 -3
- package/src/Translation/Resources/tr.ts +89 -3
- package/src/Trustchex.tsx +65 -19
- package/src/version.ts +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ISOCountry } from '../EIDReader/data/isoCountry';
|
|
2
|
+
import { UnicodeCountry } from '../EIDReader/data/unicodeCountry';
|
|
3
|
+
|
|
4
|
+
const COUNTRY_NAME_OVERRIDES: Record<string, string> = {
|
|
5
|
+
TUR: 'Türkiye',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const findCountryByAlpha3 = (alpha3Code: string) => {
|
|
9
|
+
return [...UnicodeCountry.VALUES, ...ISOCountry.VALUES].find(
|
|
10
|
+
(country) => country.toAlpha3Code() === alpha3Code
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getLocalizedCountryName = (
|
|
15
|
+
alpha3Code?: string | null,
|
|
16
|
+
_language?: string | null
|
|
17
|
+
): string => {
|
|
18
|
+
if (!alpha3Code) return '';
|
|
19
|
+
|
|
20
|
+
const normalizedCode = alpha3Code.trim().toUpperCase();
|
|
21
|
+
const overrideName = COUNTRY_NAME_OVERRIDES[normalizedCode];
|
|
22
|
+
|
|
23
|
+
if (overrideName) {
|
|
24
|
+
return overrideName;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const country = findCountryByAlpha3(normalizedCode);
|
|
28
|
+
if (!country) {
|
|
29
|
+
return normalizedCode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
typeof Intl !== 'undefined' &&
|
|
34
|
+
'Locale' in Intl &&
|
|
35
|
+
typeof Intl.Locale === 'function' &&
|
|
36
|
+
'DisplayNames' in Intl &&
|
|
37
|
+
typeof Intl.DisplayNames === 'function'
|
|
38
|
+
) {
|
|
39
|
+
const nativeLanguage = new Intl.Locale(
|
|
40
|
+
`und-${country.toAlpha2Code()}`
|
|
41
|
+
).maximize().language;
|
|
42
|
+
|
|
43
|
+
if (nativeLanguage) {
|
|
44
|
+
const nativeName = new Intl.DisplayNames([nativeLanguage], {
|
|
45
|
+
type: 'region',
|
|
46
|
+
}).of(country.toAlpha2Code());
|
|
47
|
+
|
|
48
|
+
if (nativeName) {
|
|
49
|
+
return nativeName;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return country.getName();
|
|
55
|
+
};
|
|
@@ -9,8 +9,8 @@ const getSessionKey = async (apiUrl: string, sessionId: string) => {
|
|
|
9
9
|
const clientPrivateKey = secp256k1.utils.randomPrivateKey();
|
|
10
10
|
const clientPublicKey = secp256k1.getPublicKey(clientPrivateKey);
|
|
11
11
|
const serverResponse = await httpClient.post<
|
|
12
|
-
{ serverPublicKey:
|
|
13
|
-
{ clientPublicKey:
|
|
12
|
+
{ serverPublicKey: string },
|
|
13
|
+
{ clientPublicKey: string }
|
|
14
14
|
>(`${apiUrl}/verification-sessions/${sessionId}/auth`, {
|
|
15
15
|
clientPublicKey: Buffer.from(clientPublicKey).toString('hex'),
|
|
16
16
|
});
|
|
@@ -10,7 +10,10 @@ const handleDeepLink = ({ url }: { url: string }) => {
|
|
|
10
10
|
let sessionId = '';
|
|
11
11
|
|
|
12
12
|
for (let i = 0; i < segments.length; i++) {
|
|
13
|
-
if (
|
|
13
|
+
if (
|
|
14
|
+
segments[i] === 'verification-session' ||
|
|
15
|
+
segments[i] === 'verification-sessions'
|
|
16
|
+
) {
|
|
14
17
|
sessionId = segments[i + 1] ?? '';
|
|
15
18
|
debugLog('handleDeepLink', 'Found sessionId:', sessionId);
|
|
16
19
|
} else if (segments[i] === 'app-url') {
|
|
@@ -19,6 +22,14 @@ const handleDeepLink = ({ url }: { url: string }) => {
|
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
24
|
|
|
25
|
+
// If no app-url segment found, derive baseUrl from the URL itself
|
|
26
|
+
if (!baseUrl && sessionId) {
|
|
27
|
+
const match = url.match(/^(https?:\/\/[^/]+)/);
|
|
28
|
+
if (match) {
|
|
29
|
+
baseUrl = match[1];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
debugLog('handleDeepLink', 'Returning:', { baseUrl, sessionId });
|
|
23
34
|
return [baseUrl, sessionId];
|
|
24
35
|
};
|
|
@@ -36,6 +36,16 @@ const demoSession = {
|
|
|
36
36
|
},
|
|
37
37
|
},
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
type: 'VERBAL_CONSENT',
|
|
41
|
+
required: false,
|
|
42
|
+
data: {
|
|
43
|
+
voiceGuidanceActive: true,
|
|
44
|
+
verbalConsentTitle: 'Verbal Consent',
|
|
45
|
+
verbalConsentText:
|
|
46
|
+
'I hereby provide my verbal consent for identity verification purposes. I confirm that all information provided is accurate and complete.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
39
49
|
{
|
|
40
50
|
type: 'IDENTITY_DOCUMENT_SCAN',
|
|
41
51
|
required: false,
|
|
@@ -64,6 +64,14 @@ const request = async <TResponse, TBody>(
|
|
|
64
64
|
let statusCode = 0;
|
|
65
65
|
let success = false;
|
|
66
66
|
|
|
67
|
+
console.log(`[HTTP] ${httpMethod} ${url}`);
|
|
68
|
+
if (body) {
|
|
69
|
+
console.log(
|
|
70
|
+
'[HTTP] Request body:',
|
|
71
|
+
JSON.stringify(body).substring(0, 200) + '...'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
try {
|
|
68
76
|
const response = await fetch(url, {
|
|
69
77
|
method: httpMethod,
|
|
@@ -75,13 +83,23 @@ const request = async <TResponse, TBody>(
|
|
|
75
83
|
|
|
76
84
|
statusCode = response.status;
|
|
77
85
|
success = response.ok;
|
|
86
|
+
console.log(
|
|
87
|
+
`[HTTP] Response status: ${statusCode} ${response.ok ? '✓' : '✗'}`
|
|
88
|
+
);
|
|
78
89
|
|
|
79
90
|
let responseJson = null;
|
|
80
91
|
|
|
81
92
|
try {
|
|
82
93
|
responseJson = await response.json();
|
|
94
|
+
if (responseJson) {
|
|
95
|
+
console.log(
|
|
96
|
+
'[HTTP] Response body:',
|
|
97
|
+
JSON.stringify(responseJson).substring(0, 200) + '...'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
83
100
|
} catch (error) {
|
|
84
101
|
// Invalid JSON response
|
|
102
|
+
console.log('[HTTP] Response body: (non-JSON)');
|
|
85
103
|
}
|
|
86
104
|
|
|
87
105
|
// Track API call performance (non-blocking)
|
|
@@ -204,7 +222,7 @@ const post = <TResponse, TBody>(
|
|
|
204
222
|
|
|
205
223
|
const put = <TResponse, TBody>(
|
|
206
224
|
url: string,
|
|
207
|
-
body?:
|
|
225
|
+
body?: TBody,
|
|
208
226
|
simulatedResponse?: TResponse
|
|
209
227
|
) => {
|
|
210
228
|
return request<TResponse, TBody>('PUT', url, body, simulatedResponse);
|
|
@@ -114,8 +114,9 @@ const fixMRZ = (rawText: string): string => {
|
|
|
114
114
|
// Must contain filler characters
|
|
115
115
|
if (!/<+/.test(line)) return false;
|
|
116
116
|
|
|
117
|
-
//
|
|
118
|
-
|
|
117
|
+
// Lines starting with a valid MRZ document code (ICAO 9303):
|
|
118
|
+
// I (ID cards), A (residence permits, admin docs), C (crew), P (passport), V (visa)
|
|
119
|
+
if (/^[IACPV][<A-Z]/.test(line)) return true;
|
|
119
120
|
|
|
120
121
|
// Or lines with numbers and fillers (second line of TD1: birth date, expiry, etc.)
|
|
121
122
|
if (/\d{5,}/.test(line) && /<{3,}/.test(line)) return true;
|
|
@@ -4,17 +4,31 @@ const runWithRetry = async <T>(
|
|
|
4
4
|
delay: number = 1000
|
|
5
5
|
): Promise<T> => {
|
|
6
6
|
let retries = 0;
|
|
7
|
+
let lastError: Error | unknown;
|
|
7
8
|
let result: T;
|
|
8
9
|
while (retries < maxRetries) {
|
|
9
10
|
try {
|
|
11
|
+
if (retries > 0) {
|
|
12
|
+
console.log(`[Retry] Attempt ${retries + 1}/${maxRetries}...`);
|
|
13
|
+
}
|
|
10
14
|
result = await fn();
|
|
15
|
+
if (retries > 0) {
|
|
16
|
+
console.log(`[Retry] ✓ Success on attempt ${retries + 1}`);
|
|
17
|
+
}
|
|
11
18
|
return result;
|
|
12
19
|
} catch (error) {
|
|
20
|
+
lastError = error;
|
|
13
21
|
retries++;
|
|
14
|
-
|
|
22
|
+
console.error(`[Retry] ✗ Attempt ${retries}/${maxRetries} failed:`, error instanceof Error ? error.message : error);
|
|
23
|
+
if (retries < maxRetries) {
|
|
24
|
+
const waitTime = delay * retries;
|
|
25
|
+
console.log(`[Retry] Waiting ${waitTime}ms before retry...`);
|
|
26
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
27
|
+
}
|
|
15
28
|
}
|
|
16
29
|
}
|
|
17
|
-
|
|
30
|
+
console.error('[Retry] ✗ All retries exhausted. Last error:', lastError);
|
|
31
|
+
throw new Error(`Max retries (${maxRetries}) exceeded. Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
18
32
|
};
|
|
19
33
|
|
|
20
34
|
export { runWithRetry };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Platform, StatusBar } from 'react-native';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configure status bar for white background with dark content for better contrast
|
|
6
|
+
*/
|
|
7
|
+
export const configureStatusBarForWhiteBackground = (): void => {
|
|
8
|
+
StatusBar.setBarStyle('dark-content', true);
|
|
9
|
+
if (Platform.OS === 'android') {
|
|
10
|
+
StatusBar.setBackgroundColor('#ffffff', true);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hook to configure status bar for white background on mount
|
|
16
|
+
*/
|
|
17
|
+
export const useStatusBarWhiteBackground = (): void => {
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
configureStatusBarForWhiteBackground();
|
|
20
|
+
}, []);
|
|
21
|
+
};
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import RNFS from 'react-native-fs';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
import type {
|
|
4
|
+
IdentificationInfo,
|
|
5
|
+
ScannedIdentityDocument,
|
|
6
|
+
LivenessDetection,
|
|
7
|
+
} from '../Types/identificationInfo';
|
|
8
|
+
import { getSessionKey, encryptWithAes } from '../Libs/crypto.utils';
|
|
9
|
+
import mrzUtils from '../Libs/mrz.utils';
|
|
10
|
+
import httpClient from '../Libs/http-client';
|
|
11
|
+
import { NotFoundError } from '../Libs/http-client';
|
|
12
|
+
import { runWithRetry } from '../Libs/promise.utils';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Service to upload collected identification data to the backend.
|
|
16
|
+
* This is used to send data before/during video call so agents can see it.
|
|
17
|
+
*/
|
|
18
|
+
export class DataUploadService {
|
|
19
|
+
private baseUrl: string;
|
|
20
|
+
private apiUrl: string;
|
|
21
|
+
|
|
22
|
+
constructor(baseUrl: string) {
|
|
23
|
+
this.baseUrl = baseUrl;
|
|
24
|
+
this.apiUrl = `${baseUrl}/api/app/mobile`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private async ensureIdentificationExists(
|
|
28
|
+
identificationId: string
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
console.log(
|
|
31
|
+
'[DataUploadService] POST',
|
|
32
|
+
`${this.apiUrl}/identifications/${identificationId}`
|
|
33
|
+
);
|
|
34
|
+
await httpClient.post(
|
|
35
|
+
`${this.apiUrl}/identifications/${identificationId}`,
|
|
36
|
+
{}
|
|
37
|
+
);
|
|
38
|
+
console.log('[DataUploadService] ✓ Identification created/verified');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Submit document data to the backend (same approach as ResultScreen)
|
|
43
|
+
*/
|
|
44
|
+
async submitDocumentData(
|
|
45
|
+
identificationId: string,
|
|
46
|
+
scannedDocument: ScannedIdentityDocument,
|
|
47
|
+
sessionKey: string
|
|
48
|
+
): Promise<void> {
|
|
49
|
+
if (!scannedDocument || scannedDocument.documentType === 'UNKNOWN') {
|
|
50
|
+
console.log('[DataUploadService] No document data to submit');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const mrzFields = scannedDocument.mrzFields;
|
|
55
|
+
if (!mrzFields) {
|
|
56
|
+
console.log('[DataUploadService] No MRZ fields to submit');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const identificationDocument = {
|
|
61
|
+
type: mrzFields.documentCode,
|
|
62
|
+
name: mrzFields.firstName,
|
|
63
|
+
surname: mrzFields.lastName,
|
|
64
|
+
gender: this.getGenderEnumType(mrzFields.sex),
|
|
65
|
+
number: mrzFields.documentNumber,
|
|
66
|
+
country: mrzFields.issuingState,
|
|
67
|
+
barcodeValue: scannedDocument.barcodeValue,
|
|
68
|
+
personalNumber: mrzFields.personalNumber || mrzFields.optional1,
|
|
69
|
+
birthDate: mrzUtils.convertMRZDateToISODate(mrzFields.birthDate),
|
|
70
|
+
expiryDate: mrzUtils.convertMRZDateToISODate(mrzFields.expirationDate),
|
|
71
|
+
dataSource: scannedDocument.dataSource,
|
|
72
|
+
mrzText: scannedDocument.mrzText,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
console.log(
|
|
76
|
+
'[DataUploadService] Submitting document data:',
|
|
77
|
+
identificationDocument.type,
|
|
78
|
+
identificationDocument.number
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const { encryptedData, nonce } = encryptWithAes(
|
|
82
|
+
JSON.stringify(identificationDocument),
|
|
83
|
+
sessionKey
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
console.log(
|
|
87
|
+
'[DataUploadService] POST',
|
|
88
|
+
`${this.apiUrl}/identifications/${identificationId}/documents`
|
|
89
|
+
);
|
|
90
|
+
await runWithRetry(() =>
|
|
91
|
+
httpClient.post(
|
|
92
|
+
`${this.apiUrl}/identifications/${identificationId}/documents`,
|
|
93
|
+
{
|
|
94
|
+
encryptedData,
|
|
95
|
+
nonce,
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
console.log('[DataUploadService] ✓ Document data submitted');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Upload media files (document images, selfies, etc.)
|
|
105
|
+
*/
|
|
106
|
+
async uploadMedia(
|
|
107
|
+
identificationId: string,
|
|
108
|
+
scannedDocument?: ScannedIdentityDocument,
|
|
109
|
+
livenessDetection?: LivenessDetection,
|
|
110
|
+
onProgress?: (progress: number) => void
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
const uploadFileOptions: RNFS.UploadFileOptions = {
|
|
113
|
+
toUrl: `${this.apiUrl}/identifications/${identificationId}/media`,
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: {
|
|
116
|
+
Accept: 'application/json',
|
|
117
|
+
},
|
|
118
|
+
files: [],
|
|
119
|
+
progress: (res) => {
|
|
120
|
+
const progress = res.totalBytesSent / res.totalBytesExpectedToSend;
|
|
121
|
+
onProgress?.(progress);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Add document front image
|
|
126
|
+
const frontImage = scannedDocument?.frontImage;
|
|
127
|
+
if (frontImage && frontImage !== '') {
|
|
128
|
+
const filePath = `${RNFS.TemporaryDirectoryPath}/DOCUMENT_FRONT_IMAGE.jpg`;
|
|
129
|
+
await RNFS.writeFile(decodeURIComponent(filePath), frontImage, 'base64');
|
|
130
|
+
uploadFileOptions.files.push({
|
|
131
|
+
name: 'files',
|
|
132
|
+
filename: 'DOCUMENT_FRONT_IMAGE.jpg',
|
|
133
|
+
filepath: decodeURIComponent(filePath),
|
|
134
|
+
filetype: 'image/jpeg',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Add document back image
|
|
139
|
+
const backImage = scannedDocument?.backImage;
|
|
140
|
+
if (backImage && backImage !== '') {
|
|
141
|
+
const filePath = `${RNFS.TemporaryDirectoryPath}/DOCUMENT_BACK_IMAGE.jpg`;
|
|
142
|
+
await RNFS.writeFile(decodeURIComponent(filePath), backImage, 'base64');
|
|
143
|
+
uploadFileOptions.files.push({
|
|
144
|
+
name: 'files',
|
|
145
|
+
filename: 'DOCUMENT_BACK_IMAGE.jpg',
|
|
146
|
+
filepath: decodeURIComponent(filePath),
|
|
147
|
+
filetype: 'image/jpeg',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Add face image from document
|
|
152
|
+
const faceImage = scannedDocument?.faceImage;
|
|
153
|
+
if (faceImage && faceImage !== '') {
|
|
154
|
+
const filePath = `${RNFS.TemporaryDirectoryPath}/FACE_IMAGE.jpg`;
|
|
155
|
+
await RNFS.writeFile(decodeURIComponent(filePath), faceImage, 'base64');
|
|
156
|
+
uploadFileOptions.files.push({
|
|
157
|
+
name: 'files',
|
|
158
|
+
filename: 'FACE_IMAGE.jpg',
|
|
159
|
+
filepath: decodeURIComponent(filePath),
|
|
160
|
+
filetype: 'image/jpeg',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Add secondary face image from document (optional)
|
|
165
|
+
const secondaryFaceImage = scannedDocument?.secondaryFaceImage;
|
|
166
|
+
if (secondaryFaceImage && secondaryFaceImage !== '') {
|
|
167
|
+
const filePath = `${RNFS.TemporaryDirectoryPath}/DOCUMENT_SECONDARY_FACE_IMAGE.jpg`;
|
|
168
|
+
await RNFS.writeFile(
|
|
169
|
+
decodeURIComponent(filePath),
|
|
170
|
+
secondaryFaceImage,
|
|
171
|
+
'base64'
|
|
172
|
+
);
|
|
173
|
+
uploadFileOptions.files.push({
|
|
174
|
+
name: 'files',
|
|
175
|
+
filename: 'DOCUMENT_SECONDARY_FACE_IMAGE.jpg',
|
|
176
|
+
filepath: decodeURIComponent(filePath),
|
|
177
|
+
filetype: 'image/jpeg',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Add hologram image from document (optional)
|
|
182
|
+
const hologramImage = scannedDocument?.hologramImage;
|
|
183
|
+
if (hologramImage && hologramImage !== '') {
|
|
184
|
+
const filePath = `${RNFS.TemporaryDirectoryPath}/DOCUMENT_HOLOGRAM_IMAGE.jpg`;
|
|
185
|
+
await RNFS.writeFile(
|
|
186
|
+
decodeURIComponent(filePath),
|
|
187
|
+
hologramImage,
|
|
188
|
+
'base64'
|
|
189
|
+
);
|
|
190
|
+
uploadFileOptions.files.push({
|
|
191
|
+
name: 'files',
|
|
192
|
+
filename: 'DOCUMENT_HOLOGRAM_IMAGE.jpg',
|
|
193
|
+
filepath: decodeURIComponent(filePath),
|
|
194
|
+
filetype: 'image/jpeg',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Add liveness images and selfie from liveness detection
|
|
199
|
+
if (livenessDetection?.instructions) {
|
|
200
|
+
for (const instruction of livenessDetection.instructions) {
|
|
201
|
+
if (instruction?.photo) {
|
|
202
|
+
const filePath = `${RNFS.TemporaryDirectoryPath}/LIVENESS_${instruction.instruction}_IMAGE.jpg`;
|
|
203
|
+
await RNFS.writeFile(
|
|
204
|
+
decodeURIComponent(filePath),
|
|
205
|
+
instruction.photo,
|
|
206
|
+
'base64'
|
|
207
|
+
);
|
|
208
|
+
uploadFileOptions.files.push({
|
|
209
|
+
name: 'files',
|
|
210
|
+
filename: `LIVENESS_${instruction.instruction}_IMAGE.jpg`,
|
|
211
|
+
filepath: decodeURIComponent(filePath),
|
|
212
|
+
filetype: 'image/jpeg',
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (instruction.instruction === 'LOOK_STRAIGHT_AND_BLINK') {
|
|
216
|
+
uploadFileOptions.files.push({
|
|
217
|
+
name: 'files',
|
|
218
|
+
filename: 'SELFIE_IMAGE.jpg',
|
|
219
|
+
filepath: decodeURIComponent(filePath),
|
|
220
|
+
filetype: 'image/jpeg',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Add liveness video (optional)
|
|
228
|
+
if (livenessDetection?.videoPath) {
|
|
229
|
+
let videoFilePath: string;
|
|
230
|
+
if (Platform.OS === 'ios') {
|
|
231
|
+
const tempDir = `${RNFS.TemporaryDirectoryPath}/${new Date().getTime()}`;
|
|
232
|
+
await RNFS.mkdir(tempDir);
|
|
233
|
+
videoFilePath = `${tempDir}/LIVENESS_VIDEO.mp4`;
|
|
234
|
+
} else {
|
|
235
|
+
videoFilePath = `${RNFS.TemporaryDirectoryPath}/LIVENESS_VIDEO.mp4`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
await RNFS.copyFile(livenessDetection.videoPath, videoFilePath);
|
|
239
|
+
|
|
240
|
+
uploadFileOptions.files.push({
|
|
241
|
+
name: 'files',
|
|
242
|
+
filename: 'LIVENESS_VIDEO.mp4',
|
|
243
|
+
filepath: decodeURIComponent(videoFilePath),
|
|
244
|
+
filetype: 'video/mp4',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Skip upload if no files
|
|
249
|
+
if (uploadFileOptions.files.length === 0) {
|
|
250
|
+
console.log('[DataUploadService] No media files to upload');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(
|
|
255
|
+
'[DataUploadService] Uploading',
|
|
256
|
+
uploadFileOptions.files.length,
|
|
257
|
+
'media files to',
|
|
258
|
+
uploadFileOptions.toUrl
|
|
259
|
+
);
|
|
260
|
+
const response = await runWithRetry(
|
|
261
|
+
() => RNFS.uploadFiles(uploadFileOptions).promise
|
|
262
|
+
);
|
|
263
|
+
console.log(
|
|
264
|
+
'[DataUploadService] Upload response status:',
|
|
265
|
+
response.statusCode
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (![200, 201, 204].includes(response.statusCode)) {
|
|
269
|
+
console.error(
|
|
270
|
+
'[DataUploadService] Media upload failed:',
|
|
271
|
+
response.statusCode,
|
|
272
|
+
response.body
|
|
273
|
+
);
|
|
274
|
+
throw new Error(`Media upload failed: ${response.statusCode}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log('[DataUploadService] ✓ Media uploaded successfully');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Upload all collected data (document + media) before video call
|
|
282
|
+
*/
|
|
283
|
+
async uploadCollectedData(
|
|
284
|
+
identificationInfo: IdentificationInfo,
|
|
285
|
+
onProgress?: (progress: number) => void
|
|
286
|
+
): Promise<boolean> {
|
|
287
|
+
const { identificationId, sessionId, scannedDocument, livenessDetection } =
|
|
288
|
+
identificationInfo;
|
|
289
|
+
let { authToken } = identificationInfo;
|
|
290
|
+
|
|
291
|
+
if (!identificationId) {
|
|
292
|
+
console.log('[DataUploadService] No identification ID, skipping upload');
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
console.log(
|
|
297
|
+
'[DataUploadService] ========== UPLOADING COLLECTED DATA =========='
|
|
298
|
+
);
|
|
299
|
+
console.log('[DataUploadService] Identification ID:', identificationId);
|
|
300
|
+
console.log('[DataUploadService] Has document:', !!scannedDocument);
|
|
301
|
+
console.log('[DataUploadService] Has liveness:', !!livenessDetection);
|
|
302
|
+
console.log('[DataUploadService] Has auth token:', !!authToken);
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
await runWithRetry(() =>
|
|
306
|
+
this.ensureIdentificationExists(identificationId)
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Always refresh session key for current session (required for encrypted submission)
|
|
310
|
+
if (sessionId) {
|
|
311
|
+
const existingAuthToken = authToken;
|
|
312
|
+
console.log('[DataUploadService] Getting session key...');
|
|
313
|
+
try {
|
|
314
|
+
authToken = await runWithRetry(() =>
|
|
315
|
+
getSessionKey(this.apiUrl, sessionId)
|
|
316
|
+
);
|
|
317
|
+
console.log('[DataUploadService] ✓ Session key obtained');
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (existingAuthToken) {
|
|
320
|
+
console.warn(
|
|
321
|
+
'[DataUploadService] Session key refresh failed, using existing token'
|
|
322
|
+
);
|
|
323
|
+
authToken = existingAuthToken;
|
|
324
|
+
} else {
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!authToken) {
|
|
331
|
+
console.log(
|
|
332
|
+
'[DataUploadService] No session key available, skipping upload'
|
|
333
|
+
);
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Step 1: Submit document data (using same encryption as ResultScreen)
|
|
338
|
+
if (scannedDocument) {
|
|
339
|
+
onProgress?.(0.1);
|
|
340
|
+
await runWithRetry(() =>
|
|
341
|
+
this.submitDocumentData(identificationId, scannedDocument, authToken)
|
|
342
|
+
);
|
|
343
|
+
onProgress?.(0.3);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Step 2: Upload media files (images only, skip video for now - too slow)
|
|
347
|
+
onProgress?.(0.4);
|
|
348
|
+
await runWithRetry(() =>
|
|
349
|
+
this.uploadMedia(
|
|
350
|
+
identificationId,
|
|
351
|
+
scannedDocument,
|
|
352
|
+
livenessDetection,
|
|
353
|
+
(p) => {
|
|
354
|
+
onProgress?.(0.4 + p * 0.5);
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
);
|
|
358
|
+
onProgress?.(1.0);
|
|
359
|
+
|
|
360
|
+
console.log('[DataUploadService] ✓ All collected data uploaded');
|
|
361
|
+
|
|
362
|
+
// Mark media as uploaded during video call
|
|
363
|
+
identificationInfo.mediaUploadedDuringVideoCall = true;
|
|
364
|
+
|
|
365
|
+
// Store the auth token back for future use
|
|
366
|
+
identificationInfo.authToken = authToken;
|
|
367
|
+
return true;
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (error instanceof NotFoundError) {
|
|
370
|
+
console.warn(
|
|
371
|
+
'[DataUploadService] Upload skipped: identification is not active anymore'
|
|
372
|
+
);
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
console.error(
|
|
376
|
+
'[DataUploadService] Failed to upload collected data:',
|
|
377
|
+
error
|
|
378
|
+
);
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private getGenderEnumType(sex: string | undefined): string {
|
|
384
|
+
switch (sex?.toLowerCase()) {
|
|
385
|
+
case 'male':
|
|
386
|
+
case 'm':
|
|
387
|
+
return 'M';
|
|
388
|
+
case 'female':
|
|
389
|
+
case 'f':
|
|
390
|
+
return 'F';
|
|
391
|
+
default:
|
|
392
|
+
return 'X';
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|