@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.
Files changed (204) hide show
  1. package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKModule.kt +2 -8
  2. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +60 -13
  3. package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -1
  4. package/ios/Camera/TrustchexCameraView.swift +10 -13
  5. package/ios/MLKit/MLKitModule.swift +1 -1
  6. package/lib/module/Screens/Debug/BarcodeTestScreen.js +308 -0
  7. package/lib/module/Screens/Debug/MRZTestScreen.js +105 -13
  8. package/lib/module/Screens/Debug/NFCScanTestScreen.js +635 -0
  9. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +49 -32
  10. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +22 -4
  11. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -0
  12. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +126 -27
  13. package/lib/module/Screens/Dynamic/VerbalConsentScreen.js +1079 -0
  14. package/lib/module/Screens/Dynamic/VideoCallScreen.js +678 -0
  15. package/lib/module/Screens/Static/OTPVerificationScreen.js +6 -0
  16. package/lib/module/Screens/Static/QrCodeScanningScreen.js +7 -1
  17. package/lib/module/Screens/Static/ResultScreen.js +154 -34
  18. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +59 -51
  19. package/lib/module/Shared/Animations/recording.json +1 -0
  20. package/lib/module/Shared/Animations/video-call.json +1 -0
  21. package/lib/module/Shared/Components/DebugNavigationPanel.js +231 -67
  22. package/lib/module/Shared/Components/EIDScanner.js +213 -112
  23. package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +5 -3
  24. package/lib/module/Shared/Components/IdentityDocumentCamera.js +77 -39
  25. package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +13 -4
  26. package/lib/module/Shared/Components/NavigationManager.js +39 -19
  27. package/lib/module/Shared/Contexts/AppContext.js +1 -0
  28. package/lib/module/Shared/EIDReader/aesSecureMessagingWrapper.js +51 -0
  29. package/lib/module/Shared/EIDReader/apduLevelPACECapable.js +3 -0
  30. package/lib/module/Shared/EIDReader/bacKey.js +16 -2
  31. package/lib/module/Shared/EIDReader/eidReader.js +354 -13
  32. package/lib/module/Shared/EIDReader/eidService.js +25 -1
  33. package/lib/module/Shared/EIDReader/nfcManagerCardService.js +4 -7
  34. package/lib/module/Shared/EIDReader/paceInfo.js +85 -0
  35. package/lib/module/Shared/EIDReader/paceKeySpec.js +51 -0
  36. package/lib/module/Shared/EIDReader/protocol/paceAPDUSender.js +100 -0
  37. package/lib/module/Shared/EIDReader/protocol/paceProtocol.js +655 -0
  38. package/lib/module/Shared/EIDReader/protocol/paceResult.js +37 -0
  39. package/lib/module/Shared/EIDReader/secureMessagingWrapper.js +27 -4
  40. package/lib/module/Shared/EIDReader/smartcards/commandAPDU.js +2 -1
  41. package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +1 -1
  42. package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +6 -3
  43. package/lib/module/Shared/EIDReader/utils/aesCrypto.utils.js +189 -0
  44. package/lib/module/Shared/Libs/SignalingClient.js +128 -0
  45. package/lib/module/Shared/Libs/analytics.utils.js +8 -0
  46. package/lib/module/Shared/Libs/contains.js +1 -40
  47. package/lib/module/Shared/Libs/country-display.utils.js +34 -0
  48. package/lib/module/Shared/Libs/deeplink.utils.js +9 -1
  49. package/lib/module/Shared/Libs/demo.utils.js +8 -0
  50. package/lib/module/Shared/Libs/http-client.js +9 -0
  51. package/lib/module/Shared/Libs/mrz.utils.js +3 -2
  52. package/lib/module/Shared/Libs/promise.utils.js +16 -2
  53. package/lib/module/Shared/Libs/status-bar.utils.js +23 -0
  54. package/lib/module/Shared/Services/DataUploadService.js +294 -0
  55. package/lib/module/Shared/Services/VideoSessionService.js +156 -0
  56. package/lib/module/Shared/Services/WebRTCService.js +510 -0
  57. package/lib/module/Shared/Types/analytics.types.js +4 -0
  58. package/lib/module/Translation/Resources/en.js +61 -2
  59. package/lib/module/Translation/Resources/tr.js +61 -2
  60. package/lib/module/Trustchex.js +64 -20
  61. package/lib/module/version.js +1 -1
  62. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts +3 -0
  63. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts.map +1 -0
  64. package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
  65. package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts +3 -0
  66. package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts.map +1 -0
  67. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  68. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  69. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  70. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  71. package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts +3 -0
  72. package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts.map +1 -0
  73. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts +3 -0
  74. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -0
  75. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
  76. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  77. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  78. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  79. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  80. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  81. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  82. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +1 -1
  83. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -1
  84. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +5 -0
  85. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
  86. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  87. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +1 -0
  88. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  89. package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts +18 -0
  90. package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts.map +1 -0
  91. package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts +23 -0
  92. package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts.map +1 -0
  93. package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts +6 -0
  94. package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts.map +1 -1
  95. package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
  96. package/lib/typescript/src/Shared/EIDReader/eidService.d.ts +9 -0
  97. package/lib/typescript/src/Shared/EIDReader/eidService.d.ts.map +1 -1
  98. package/lib/typescript/src/Shared/EIDReader/nfcManagerCardService.d.ts.map +1 -1
  99. package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts +50 -0
  100. package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts.map +1 -0
  101. package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts +30 -0
  102. package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts.map +1 -0
  103. package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts +17 -0
  104. package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts.map +1 -0
  105. package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts +105 -0
  106. package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts.map +1 -0
  107. package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts +24 -0
  108. package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts.map +1 -0
  109. package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts +15 -0
  110. package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts.map +1 -1
  111. package/lib/typescript/src/Shared/EIDReader/smartcards/commandAPDU.d.ts.map +1 -1
  112. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
  113. package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts +39 -0
  114. package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts.map +1 -0
  115. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts +24 -0
  116. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -0
  117. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
  118. package/lib/typescript/src/Shared/Libs/contains.d.ts +0 -7
  119. package/lib/typescript/src/Shared/Libs/contains.d.ts.map +1 -1
  120. package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts +2 -0
  121. package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts.map +1 -0
  122. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  123. package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
  124. package/lib/typescript/src/Shared/Libs/http-client.d.ts +1 -1
  125. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  126. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  127. package/lib/typescript/src/Shared/Libs/promise.utils.d.ts.map +1 -1
  128. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts +9 -0
  129. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -0
  130. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +25 -0
  131. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -0
  132. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts +33 -0
  133. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -0
  134. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts +58 -0
  135. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts.map +1 -0
  136. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +4 -0
  137. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
  138. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +13 -1
  139. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
  140. package/lib/typescript/src/Translation/Resources/en.d.ts +60 -1
  141. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  142. package/lib/typescript/src/Translation/Resources/tr.d.ts +60 -1
  143. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  144. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  145. package/lib/typescript/src/version.d.ts +1 -1
  146. package/package.json +35 -5
  147. package/src/Screens/Debug/BarcodeTestScreen.tsx +317 -0
  148. package/src/Screens/Debug/MRZTestScreen.tsx +107 -13
  149. package/src/Screens/Debug/NFCScanTestScreen.tsx +692 -0
  150. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +58 -35
  151. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +27 -4
  152. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +6 -0
  153. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +156 -27
  154. package/src/Screens/Dynamic/VerbalConsentScreen.tsx +1401 -0
  155. package/src/Screens/Dynamic/VideoCallScreen.tsx +766 -0
  156. package/src/Screens/Static/OTPVerificationScreen.tsx +6 -0
  157. package/src/Screens/Static/QrCodeScanningScreen.tsx +7 -1
  158. package/src/Screens/Static/ResultScreen.tsx +235 -48
  159. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +67 -72
  160. package/src/Shared/Animations/recording.json +1 -0
  161. package/src/Shared/Animations/video-call.json +1 -0
  162. package/src/Shared/Components/DebugNavigationPanel.tsx +252 -51
  163. package/src/Shared/Components/EIDScanner.tsx +223 -116
  164. package/src/Shared/Components/IdentityDocumentCamera.flows.ts +7 -4
  165. package/src/Shared/Components/IdentityDocumentCamera.tsx +224 -188
  166. package/src/Shared/Components/IdentityDocumentCamera.utils.ts +13 -4
  167. package/src/Shared/Components/NavigationManager.tsx +41 -19
  168. package/src/Shared/Contexts/AppContext.ts +2 -0
  169. package/src/Shared/EIDReader/aesSecureMessagingWrapper.ts +69 -0
  170. package/src/Shared/EIDReader/apduLevelPACECapable.ts +34 -0
  171. package/src/Shared/EIDReader/bacKey.ts +24 -8
  172. package/src/Shared/EIDReader/eidReader.ts +398 -12
  173. package/src/Shared/EIDReader/eidService.ts +49 -1
  174. package/src/Shared/EIDReader/nfcManagerCardService.ts +4 -6
  175. package/src/Shared/EIDReader/paceInfo.ts +159 -0
  176. package/src/Shared/EIDReader/paceKeySpec.ts +56 -0
  177. package/src/Shared/EIDReader/protocol/paceAPDUSender.ts +163 -0
  178. package/src/Shared/EIDReader/protocol/paceProtocol.ts +946 -0
  179. package/src/Shared/EIDReader/protocol/paceResult.ts +62 -0
  180. package/src/Shared/EIDReader/secureMessagingWrapper.ts +28 -10
  181. package/src/Shared/EIDReader/smartcards/commandAPDU.ts +2 -1
  182. package/src/Shared/EIDReader/tlv/tlv.helpers.ts +1 -1
  183. package/src/Shared/EIDReader/tlv/tlv.utils.ts +8 -5
  184. package/src/Shared/EIDReader/utils/aesCrypto.utils.ts +217 -0
  185. package/src/Shared/Libs/SignalingClient.ts +189 -0
  186. package/src/Shared/Libs/analytics.utils.ts +8 -0
  187. package/src/Shared/Libs/contains.ts +0 -53
  188. package/src/Shared/Libs/country-display.utils.ts +55 -0
  189. package/src/Shared/Libs/crypto.utils.ts +2 -2
  190. package/src/Shared/Libs/deeplink.utils.ts +12 -1
  191. package/src/Shared/Libs/demo.utils.ts +10 -0
  192. package/src/Shared/Libs/http-client.ts +19 -1
  193. package/src/Shared/Libs/mrz.utils.ts +3 -2
  194. package/src/Shared/Libs/promise.utils.ts +16 -2
  195. package/src/Shared/Libs/status-bar.utils.ts +21 -0
  196. package/src/Shared/Services/DataUploadService.ts +395 -0
  197. package/src/Shared/Services/VideoSessionService.ts +190 -0
  198. package/src/Shared/Services/WebRTCService.ts +636 -0
  199. package/src/Shared/Types/analytics.types.ts +4 -0
  200. package/src/Shared/Types/identificationInfo.ts +16 -1
  201. package/src/Translation/Resources/en.ts +88 -3
  202. package/src/Translation/Resources/tr.ts +89 -3
  203. package/src/Trustchex.tsx +65 -19
  204. 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: String },
13
- { clientPublicKey: String }
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 (segments[i] === 'verification-session') {
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?: any,
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
- // Prioritize lines starting with I< (document code)
118
- if (line.startsWith('I<')) return true;
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
- await new Promise((resolve) => setTimeout(resolve, delay * retries));
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
- throw new Error('Max retries exceeded');
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
+ }