@trustchex/react-native-sdk 1.475.1 → 1.481.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/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +76 -24
- package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -0
- package/ios/Camera/TrustchexCameraView.swift +20 -15
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +3 -2
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +12 -2
- package/lib/module/Screens/Static/ResultScreen.js +145 -113
- package/lib/module/Shared/Components/DebugOverlay.js +5 -2
- package/lib/module/Shared/Components/DiagnosticReportButton.js +64 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +185 -30
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +8 -1
- package/lib/module/Shared/Components/NavigationManager.js +7 -9
- package/lib/module/Shared/EIDReader/eidReader.js +159 -38
- package/lib/module/Shared/Libs/MRZ_KNOWN_ISSUES.md +41 -0
- package/lib/module/Shared/Libs/SignalingClient.js +6 -5
- package/lib/module/Shared/Libs/crypto.utils.js +25 -3
- package/lib/module/Shared/Libs/deeplink.utils.js +37 -7
- package/lib/module/Shared/Libs/diagnosticReport.js +133 -0
- package/lib/module/Shared/Libs/diagnostics.js +171 -0
- package/lib/module/Shared/Libs/http-client.js +19 -6
- package/lib/module/Shared/Libs/mrz.utils.js +193 -33
- package/lib/module/Shared/Libs/mrzFrameAggregator.js +74 -3
- package/lib/module/Shared/Libs/native-device-info.utils.js +67 -0
- package/lib/module/Shared/Libs/promise.utils.js +4 -3
- package/lib/module/Shared/Libs/sendDiagnosticReport.js +130 -0
- package/lib/module/Shared/Libs/shuffle.utils.js +17 -0
- package/lib/module/Shared/Services/AnalyticsService.js +8 -2
- package/lib/module/Shared/Services/DataUploadService.js +114 -127
- package/lib/module/Shared/Services/VideoSessionService.js +4 -3
- package/lib/module/Translation/Resources/en.js +6 -0
- package/lib/module/Translation/Resources/tr.js +6 -0
- package/lib/module/Trustchex.js +20 -4
- package/lib/module/version.js +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/Shared/Components/DebugOverlay.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DiagnosticReportButton.d.ts +20 -0
- package/lib/typescript/src/Shared/Components/DiagnosticReportButton.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +10 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +1 -1
- 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/Components/TrustchexCamera.d.ts +10 -0
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/crypto.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts +1 -0
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts +48 -0
- package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/diagnostics.d.ts +145 -0
- package/lib/typescript/src/Shared/Libs/diagnostics.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/http-client.d.ts +2 -0
- 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/mrzFrameAggregator.d.ts +55 -0
- package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts +33 -0
- package/lib/typescript/src/Shared/Libs/native-device-info.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/sendDiagnosticReport.d.ts +30 -0
- package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/shuffle.utils.d.ts +3 -0
- package/lib/typescript/src/Shared/Libs/shuffle.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +6 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +6 -0
- 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 +17 -3
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +4 -3
- package/src/Screens/Static/QrCodeScanningScreen.tsx +19 -2
- package/src/Screens/Static/ResultScreen.tsx +172 -162
- package/src/Shared/Components/DebugOverlay.tsx +5 -2
- package/src/Shared/Components/DiagnosticReportButton.tsx +77 -0
- package/src/Shared/Components/IdentityDocumentCamera.tsx +200 -29
- package/src/Shared/Components/IdentityDocumentCamera.types.ts +6 -0
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +9 -1
- package/src/Shared/Components/NavigationManager.tsx +7 -7
- package/src/Shared/Components/TrustchexCamera.tsx +6 -0
- package/src/Shared/EIDReader/eidReader.ts +181 -52
- package/src/Shared/Libs/MRZ_KNOWN_ISSUES.md +41 -0
- package/src/Shared/Libs/SignalingClient.ts +10 -5
- package/src/Shared/Libs/crypto.utils.ts +41 -4
- package/src/Shared/Libs/deeplink.utils.ts +39 -7
- package/src/Shared/Libs/diagnosticReport.ts +206 -0
- package/src/Shared/Libs/diagnostics.ts +251 -0
- package/src/Shared/Libs/http-client.ts +24 -8
- package/src/Shared/Libs/mrz.utils.ts +200 -34
- package/src/Shared/Libs/mrzFrameAggregator.ts +112 -2
- package/src/Shared/Libs/native-device-info.utils.ts +116 -0
- package/src/Shared/Libs/promise.utils.ts +11 -5
- package/src/Shared/Libs/sendDiagnosticReport.ts +165 -0
- package/src/Shared/Libs/shuffle.utils.ts +15 -0
- package/src/Shared/Services/AnalyticsService.ts +17 -1
- package/src/Shared/Services/DataUploadService.ts +155 -166
- package/src/Shared/Services/VideoSessionService.ts +15 -3
- package/src/Translation/Resources/en.ts +7 -0
- package/src/Translation/Resources/tr.ts +7 -0
- package/src/Trustchex.tsx +26 -3
- package/src/version.ts +1 -1
|
@@ -35,6 +35,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
35
35
|
import StyledButton from '../../Shared/Components/StyledButton';
|
|
36
36
|
import LottieView from 'lottie-react-native';
|
|
37
37
|
import { speak, resetLastMessage } from '../../Shared/Libs/tts.utils';
|
|
38
|
+
import { secureShuffle } from '../../Shared/Libs/shuffle.utils';
|
|
38
39
|
import { useStatusBarWhiteBackground } from '../../Shared/Libs/status-bar.utils';
|
|
39
40
|
import {
|
|
40
41
|
trackFunnelStep,
|
|
@@ -202,8 +203,8 @@ const LivenessDetectionScreen = () => {
|
|
|
202
203
|
}, [hasGuideShown]);
|
|
203
204
|
|
|
204
205
|
useEffect(() => {
|
|
205
|
-
const il =
|
|
206
|
-
.filter(
|
|
206
|
+
const il = secureShuffle(
|
|
207
|
+
Object.keys(instructions).filter(
|
|
207
208
|
(instruction) =>
|
|
208
209
|
!['START', 'FINISH'].includes(instruction) &&
|
|
209
210
|
// Look straight and blink is always included
|
|
@@ -216,7 +217,7 @@ const LivenessDetectionScreen = () => {
|
|
|
216
217
|
instruction as any
|
|
217
218
|
))
|
|
218
219
|
)
|
|
219
|
-
|
|
220
|
+
);
|
|
220
221
|
il.unshift('START');
|
|
221
222
|
il.push('FINISH');
|
|
222
223
|
setInitialState({
|
|
@@ -4,7 +4,8 @@ import { SafeAreaView, StyleSheet } from 'react-native';
|
|
|
4
4
|
import AppContext from '../../Shared/Contexts/AppContext';
|
|
5
5
|
import QrCodeScannerCamera from '../../Shared/Components/QrCodeScannerCamera';
|
|
6
6
|
import { useNavigation } from '@react-navigation/native';
|
|
7
|
-
import { handleDeepLink } from '../../Shared/Libs/deeplink.utils';
|
|
7
|
+
import { handleDeepLink, getUrlHost } from '../../Shared/Libs/deeplink.utils';
|
|
8
|
+
import { logWarn } from '../../Shared/Libs/debug.utils';
|
|
8
9
|
import { useKeepAwake } from '../../Shared/Libs/native-keep-awake.utils';
|
|
9
10
|
import { useStatusBarWhiteBackground } from '../../Shared/Libs/status-bar.utils';
|
|
10
11
|
|
|
@@ -17,7 +18,23 @@ const QrCodeScanningScreen = () => {
|
|
|
17
18
|
useStatusBarWhiteBackground();
|
|
18
19
|
|
|
19
20
|
const onQrCodeScanned = (data: string) => {
|
|
20
|
-
|
|
21
|
+
let [bUrl, sId] = handleDeepLink({ url: data });
|
|
22
|
+
|
|
23
|
+
// A scanned QR may not repoint an already-configured backend to another
|
|
24
|
+
// host — uploads carry identity documents and biometrics. Keep the
|
|
25
|
+
// configured baseUrl and only take the session id from the QR.
|
|
26
|
+
const configuredBaseUrl = appContext.baseUrl;
|
|
27
|
+
if (
|
|
28
|
+
bUrl &&
|
|
29
|
+
configuredBaseUrl &&
|
|
30
|
+
getUrlHost(bUrl) !== getUrlHost(configuredBaseUrl)
|
|
31
|
+
) {
|
|
32
|
+
logWarn(
|
|
33
|
+
'QrCodeScanningScreen',
|
|
34
|
+
'Ignoring QR-provided baseUrl with mismatched host'
|
|
35
|
+
);
|
|
36
|
+
bUrl = configuredBaseUrl;
|
|
37
|
+
}
|
|
21
38
|
|
|
22
39
|
if (bUrl && sId) {
|
|
23
40
|
if (appContext.setBaseUrl) {
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from 'react-native';
|
|
18
18
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
19
19
|
import AppContext from '../../Shared/Contexts/AppContext';
|
|
20
|
-
import httpClient from '../../Shared/Libs/http-client';
|
|
20
|
+
import httpClient, { getSessionToken } from '../../Shared/Libs/http-client';
|
|
21
21
|
import RNFS from 'react-native-fs';
|
|
22
22
|
import NativeProgressBar from '../../Shared/Components/NativeProgressBar';
|
|
23
23
|
import LottieView from 'lottie-react-native';
|
|
@@ -39,7 +39,32 @@ import { Video as VideoCompressor } from 'react-native-compressor';
|
|
|
39
39
|
import { encryptWithAes, getSessionKey } from '../../Shared/Libs/crypto.utils';
|
|
40
40
|
import Video from 'react-native-video';
|
|
41
41
|
import StyledButton from '../../Shared/Components/StyledButton';
|
|
42
|
+
import DiagnosticReportButton from '../../Shared/Components/DiagnosticReportButton';
|
|
43
|
+
import type { DiagnosticImage } from '../../Shared/Libs/sendDiagnosticReport';
|
|
42
44
|
import NativeDeviceInfo from '../../Shared/Libs/native-device-info.utils';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Collect the captured document/face images (base64) as attachments for a
|
|
48
|
+
* diagnostic report. On an ID card the BACK image is the MRZ side; on a passport
|
|
49
|
+
* the front carries the MRZ. The NFC chip face arrives as `faceImage` with its
|
|
50
|
+
* own mime type, which may be png.
|
|
51
|
+
*/
|
|
52
|
+
const collectDiagnosticImages = (
|
|
53
|
+
doc?: ScannedIdentityDocument
|
|
54
|
+
): DiagnosticImage[] => {
|
|
55
|
+
if (!doc) return [];
|
|
56
|
+
const out: DiagnosticImage[] = [];
|
|
57
|
+
const push = (name: string, b64?: string, mime: 'jpg' | 'png' = 'jpg') => {
|
|
58
|
+
if (b64 && b64 !== '') out.push({ name, base64: b64, mime });
|
|
59
|
+
};
|
|
60
|
+
push('mrz-side', doc.backImage);
|
|
61
|
+
push('front', doc.frontImage);
|
|
62
|
+
const faceMime = doc.faceImageMimeType === 'image/png' ? 'png' : 'jpg';
|
|
63
|
+
push('face', doc.faceImage, faceMime);
|
|
64
|
+
push('secondary-face', doc.secondaryFaceImage);
|
|
65
|
+
push('hologram', doc.hologramImage);
|
|
66
|
+
return out;
|
|
67
|
+
};
|
|
43
68
|
import {
|
|
44
69
|
trackError,
|
|
45
70
|
trackFunnelStep,
|
|
@@ -285,11 +310,15 @@ const ResultScreen = () => {
|
|
|
285
310
|
return;
|
|
286
311
|
}
|
|
287
312
|
|
|
313
|
+
const uploadSessionToken = getSessionToken();
|
|
288
314
|
const uploadFileOptions: RNFS.UploadFileOptions = {
|
|
289
315
|
toUrl: `${apiUrl}/identifications/${identificationId}/media`,
|
|
290
316
|
method: 'POST',
|
|
291
317
|
headers: {
|
|
292
318
|
Accept: 'application/json',
|
|
319
|
+
...(uploadSessionToken
|
|
320
|
+
? { 'X-Session-Token': uploadSessionToken }
|
|
321
|
+
: {}),
|
|
293
322
|
},
|
|
294
323
|
files: [],
|
|
295
324
|
progress: (res) => {
|
|
@@ -299,186 +328,143 @@ const ResultScreen = () => {
|
|
|
299
328
|
},
|
|
300
329
|
} satisfies RNFS.UploadFileOptions;
|
|
301
330
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
331
|
+
// Plaintext identity images/video staged on disk for the multipart
|
|
332
|
+
// upload; everything tracked here is deleted in the finally below.
|
|
333
|
+
const tempPaths: string[] = [];
|
|
334
|
+
|
|
335
|
+
const writeTempImage = async (
|
|
336
|
+
fileName: string,
|
|
337
|
+
base64Data: string,
|
|
338
|
+
filetype: string = 'image/jpeg'
|
|
339
|
+
) => {
|
|
340
|
+
const filePath = decodeURIComponent(
|
|
341
|
+
`${RNFS.TemporaryDirectoryPath}/${fileName}`
|
|
310
342
|
);
|
|
343
|
+
await RNFS.writeFile(filePath, base64Data, 'base64');
|
|
344
|
+
tempPaths.push(filePath);
|
|
311
345
|
uploadFileOptions.files.push({
|
|
312
346
|
name: 'files',
|
|
313
|
-
filename:
|
|
314
|
-
filepath:
|
|
315
|
-
|
|
316
|
-
),
|
|
317
|
-
filetype: 'image/jpeg',
|
|
347
|
+
filename: fileName,
|
|
348
|
+
filepath: filePath,
|
|
349
|
+
filetype,
|
|
318
350
|
});
|
|
319
|
-
|
|
351
|
+
return filePath;
|
|
352
|
+
};
|
|
320
353
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
),
|
|
327
|
-
backImage,
|
|
328
|
-
'base64'
|
|
329
|
-
);
|
|
330
|
-
uploadFileOptions.files.push({
|
|
331
|
-
name: 'files',
|
|
332
|
-
filename: 'DOCUMENT_BACK_IMAGE.jpg',
|
|
333
|
-
filepath: decodeURIComponent(
|
|
334
|
-
`${RNFS.TemporaryDirectoryPath}/DOCUMENT_BACK_IMAGE.jpg`
|
|
335
|
-
),
|
|
336
|
-
filetype: 'image/jpeg',
|
|
337
|
-
});
|
|
338
|
-
}
|
|
354
|
+
try {
|
|
355
|
+
const frontImage = scannedIdentityDocument?.frontImage;
|
|
356
|
+
if (frontImage && frontImage !== '') {
|
|
357
|
+
await writeTempImage('DOCUMENT_FRONT_IMAGE.jpg', frontImage);
|
|
358
|
+
}
|
|
339
359
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
scannedIdentityDocument.faceImageMimeType === 'image/jp2'
|
|
345
|
-
? 'jp2'
|
|
346
|
-
: scannedIdentityDocument.faceImageMimeType === 'image/png'
|
|
347
|
-
? 'png'
|
|
348
|
-
: 'jpg';
|
|
349
|
-
await RNFS.writeFile(
|
|
350
|
-
decodeURIComponent(
|
|
351
|
-
`${RNFS.TemporaryDirectoryPath}/FACE_IMAGE.${extension}`
|
|
352
|
-
),
|
|
353
|
-
faceImage,
|
|
354
|
-
'base64'
|
|
355
|
-
);
|
|
356
|
-
uploadFileOptions.files.push({
|
|
357
|
-
name: 'files',
|
|
358
|
-
filename: `FACE_IMAGE.${extension}`,
|
|
359
|
-
filepath: decodeURIComponent(
|
|
360
|
-
`${RNFS.TemporaryDirectoryPath}/FACE_IMAGE.${extension}`
|
|
361
|
-
),
|
|
362
|
-
filetype: scannedIdentityDocument.faceImageMimeType,
|
|
363
|
-
});
|
|
364
|
-
}
|
|
360
|
+
const backImage = scannedIdentityDocument?.backImage;
|
|
361
|
+
if (backImage && backImage !== '') {
|
|
362
|
+
await writeTempImage('DOCUMENT_BACK_IMAGE.jpg', backImage);
|
|
363
|
+
}
|
|
365
364
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
filetype: 'image/jpeg',
|
|
382
|
-
});
|
|
383
|
-
}
|
|
365
|
+
const faceImage = scannedIdentityDocument?.faceImage;
|
|
366
|
+
if (faceImage && faceImage !== '') {
|
|
367
|
+
scannedIdentityDocument.faceImageMimeType ||= 'image/jpeg';
|
|
368
|
+
const extension =
|
|
369
|
+
scannedIdentityDocument.faceImageMimeType === 'image/jp2'
|
|
370
|
+
? 'jp2'
|
|
371
|
+
: scannedIdentityDocument.faceImageMimeType === 'image/png'
|
|
372
|
+
? 'png'
|
|
373
|
+
: 'jpg';
|
|
374
|
+
await writeTempImage(
|
|
375
|
+
`FACE_IMAGE.${extension}`,
|
|
376
|
+
faceImage,
|
|
377
|
+
scannedIdentityDocument.faceImageMimeType
|
|
378
|
+
);
|
|
379
|
+
}
|
|
384
380
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
)
|
|
391
|
-
|
|
392
|
-
'base64'
|
|
393
|
-
);
|
|
394
|
-
uploadFileOptions.files.push({
|
|
395
|
-
name: 'files',
|
|
396
|
-
filename: 'DOCUMENT_HOLOGRAM_IMAGE.jpg',
|
|
397
|
-
filepath: decodeURIComponent(
|
|
398
|
-
`${RNFS.TemporaryDirectoryPath}/DOCUMENT_HOLOGRAM_IMAGE.jpg`
|
|
399
|
-
),
|
|
400
|
-
filetype: 'image/jpeg',
|
|
401
|
-
});
|
|
402
|
-
}
|
|
381
|
+
const secondaryFaceImage = scannedIdentityDocument?.secondaryFaceImage;
|
|
382
|
+
if (secondaryFaceImage && secondaryFaceImage !== '') {
|
|
383
|
+
await writeTempImage(
|
|
384
|
+
'DOCUMENT_SECONDARY_FACE_IMAGE.jpg',
|
|
385
|
+
secondaryFaceImage
|
|
386
|
+
);
|
|
387
|
+
}
|
|
403
388
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
await RNFS.writeFile(
|
|
408
|
-
decodeURIComponent(
|
|
409
|
-
`${RNFS.TemporaryDirectoryPath}/LIVENESS_${instruction.instruction}_IMAGE.jpg`
|
|
410
|
-
),
|
|
411
|
-
instruction.photo,
|
|
412
|
-
'base64'
|
|
413
|
-
);
|
|
414
|
-
uploadFileOptions.files.push({
|
|
415
|
-
name: 'files',
|
|
416
|
-
filename: `LIVENESS_${instruction.instruction}_IMAGE.jpg`,
|
|
417
|
-
filepath: decodeURIComponent(
|
|
418
|
-
`${RNFS.TemporaryDirectoryPath}/LIVENESS_${instruction.instruction}_IMAGE.jpg`
|
|
419
|
-
),
|
|
420
|
-
filetype: 'image/jpeg',
|
|
421
|
-
});
|
|
422
|
-
if (instruction.instruction === 'LOOK_STRAIGHT_AND_BLINK') {
|
|
423
|
-
uploadFileOptions.files.push({
|
|
424
|
-
name: 'files',
|
|
425
|
-
filename: 'SELFIE_IMAGE.jpg',
|
|
426
|
-
filepath: decodeURIComponent(
|
|
427
|
-
`${RNFS.TemporaryDirectoryPath}/LIVENESS_${instruction.instruction}_IMAGE.jpg`
|
|
428
|
-
),
|
|
429
|
-
filetype: 'image/jpeg',
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
}
|
|
389
|
+
const hologramImage = scannedIdentityDocument?.hologramImage;
|
|
390
|
+
if (hologramImage && hologramImage !== '') {
|
|
391
|
+
await writeTempImage('DOCUMENT_HOLOGRAM_IMAGE.jpg', hologramImage);
|
|
433
392
|
}
|
|
434
393
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
394
|
+
if (livenessDetection) {
|
|
395
|
+
for (const instruction of livenessDetection.instructions) {
|
|
396
|
+
if (instruction.photo !== '') {
|
|
397
|
+
const filePath = await writeTempImage(
|
|
398
|
+
`LIVENESS_${instruction.instruction}_IMAGE.jpg`,
|
|
399
|
+
instruction.photo
|
|
400
|
+
);
|
|
401
|
+
if (instruction.instruction === 'LOOK_STRAIGHT_AND_BLINK') {
|
|
402
|
+
uploadFileOptions.files.push({
|
|
403
|
+
name: 'files',
|
|
404
|
+
filename: 'SELFIE_IMAGE.jpg',
|
|
405
|
+
filepath: filePath,
|
|
406
|
+
filetype: 'image/jpeg',
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
444
410
|
}
|
|
445
|
-
await RNFS.copyFile(livenessVideoPath, videoFilePath);
|
|
446
411
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
videoFilePath
|
|
450
|
-
{
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
412
|
+
const livenessVideoPath = livenessDetection.videoPath;
|
|
413
|
+
if (livenessVideoPath) {
|
|
414
|
+
let videoFilePath: string;
|
|
415
|
+
if (Platform.OS === 'ios') {
|
|
416
|
+
const tmpTimestamp = new Date().getTime();
|
|
417
|
+
const tempDir = `${RNFS.TemporaryDirectoryPath}/${tmpTimestamp}`;
|
|
418
|
+
await RNFS.mkdir(tempDir);
|
|
419
|
+
tempPaths.push(tempDir);
|
|
420
|
+
videoFilePath = `${tempDir}/LIVENESS_VIDEO.mp4`;
|
|
421
|
+
} else {
|
|
422
|
+
videoFilePath = `${RNFS.TemporaryDirectoryPath}/LIVENESS_VIDEO.mp4`;
|
|
423
|
+
tempPaths.push(videoFilePath);
|
|
459
424
|
}
|
|
460
|
-
|
|
425
|
+
await RNFS.copyFile(livenessVideoPath, videoFilePath);
|
|
426
|
+
|
|
427
|
+
// Compress video with maximum compression settings
|
|
428
|
+
const compressedVideoPath = await VideoCompressor.compress(
|
|
429
|
+
videoFilePath,
|
|
430
|
+
{
|
|
431
|
+
compressionMethod: 'manual',
|
|
432
|
+
bitrate: 500000, // 500 kbps
|
|
433
|
+
maxSize: 1280, // HD 720p
|
|
434
|
+
minimumFileSizeForCompress: 0, // Always compress
|
|
435
|
+
},
|
|
436
|
+
(compressionProgress) => {
|
|
437
|
+
// Map compression progress (0-1) to main progress (40-60%)
|
|
438
|
+
setProgress(40 + compressionProgress * 20);
|
|
439
|
+
}
|
|
440
|
+
);
|
|
461
441
|
|
|
462
|
-
|
|
463
|
-
|
|
442
|
+
// Convert file:// URI to regular path for RNFS upload
|
|
443
|
+
const normalizedPath = compressedVideoPath.replace('file://', '');
|
|
444
|
+
tempPaths.push(normalizedPath);
|
|
464
445
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
446
|
+
uploadFileOptions.files.push({
|
|
447
|
+
name: 'files',
|
|
448
|
+
filename: 'LIVENESS_VIDEO.mp4',
|
|
449
|
+
filepath: normalizedPath,
|
|
450
|
+
filetype: 'video/mp4',
|
|
451
|
+
});
|
|
452
|
+
}
|
|
471
453
|
}
|
|
472
|
-
}
|
|
473
454
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
455
|
+
if (uploadFileOptions.files.length === 0) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
477
458
|
|
|
478
|
-
|
|
459
|
+
const response = await RNFS.uploadFiles(uploadFileOptions).promise;
|
|
479
460
|
|
|
480
|
-
|
|
481
|
-
|
|
461
|
+
if (![200, 201, 204].includes(response.statusCode)) {
|
|
462
|
+
throw new Error('Media upload failed');
|
|
463
|
+
}
|
|
464
|
+
} finally {
|
|
465
|
+
await Promise.all(
|
|
466
|
+
tempPaths.map((path) => RNFS.unlink(path).catch(() => {}))
|
|
467
|
+
);
|
|
482
468
|
}
|
|
483
469
|
},
|
|
484
470
|
[apiUrl]
|
|
@@ -518,11 +504,15 @@ const ResultScreen = () => {
|
|
|
518
504
|
''
|
|
519
505
|
);
|
|
520
506
|
|
|
507
|
+
const consentSessionToken = getSessionToken();
|
|
521
508
|
const response = await RNFS.uploadFiles({
|
|
522
509
|
toUrl: `${apiUrl}/identifications/${identificationId}/media`,
|
|
523
510
|
method: 'POST',
|
|
524
511
|
headers: {
|
|
525
512
|
Accept: 'application/json',
|
|
513
|
+
...(consentSessionToken
|
|
514
|
+
? { 'X-Session-Token': consentSessionToken }
|
|
515
|
+
: {}),
|
|
526
516
|
},
|
|
527
517
|
fields: {
|
|
528
518
|
consentTitle: consentVideo.title,
|
|
@@ -1080,7 +1070,7 @@ const ResultScreen = () => {
|
|
|
1080
1070
|
)}
|
|
1081
1071
|
</View>
|
|
1082
1072
|
</ScrollView>
|
|
1083
|
-
<View style={{ paddingBottom: insets.bottom }}>
|
|
1073
|
+
<View style={[styles.footer, { paddingBottom: insets.bottom + 20 }]}>
|
|
1084
1074
|
<StyledButton
|
|
1085
1075
|
mode="contained"
|
|
1086
1076
|
onPress={() => {
|
|
@@ -1090,6 +1080,26 @@ const ResultScreen = () => {
|
|
|
1090
1080
|
>
|
|
1091
1081
|
{t('resultScreen.demoStartOver')}
|
|
1092
1082
|
</StyledButton>
|
|
1083
|
+
<DiagnosticReportButton
|
|
1084
|
+
sessionId={appContext.identificationInfo?.sessionId}
|
|
1085
|
+
scan={{
|
|
1086
|
+
documentType:
|
|
1087
|
+
appContext.identificationInfo?.scannedDocument
|
|
1088
|
+
?.documentType,
|
|
1089
|
+
dataSource:
|
|
1090
|
+
appContext.identificationInfo?.scannedDocument?.dataSource,
|
|
1091
|
+
mrzText:
|
|
1092
|
+
appContext.identificationInfo?.scannedDocument?.mrzText,
|
|
1093
|
+
mrzFields:
|
|
1094
|
+
appContext.identificationInfo?.scannedDocument?.mrzFields,
|
|
1095
|
+
barcodeValue:
|
|
1096
|
+
appContext.identificationInfo?.scannedDocument
|
|
1097
|
+
?.barcodeValue,
|
|
1098
|
+
}}
|
|
1099
|
+
images={collectDiagnosticImages(
|
|
1100
|
+
appContext.identificationInfo?.scannedDocument
|
|
1101
|
+
)}
|
|
1102
|
+
/>
|
|
1093
1103
|
</View>
|
|
1094
1104
|
</>
|
|
1095
1105
|
) : (
|
|
@@ -625,14 +625,17 @@ export const TestModePanel = React.memo(function TestModePanel({
|
|
|
625
625
|
<View style={{ padding: 10 }}>
|
|
626
626
|
<TextView
|
|
627
627
|
style={{
|
|
628
|
-
|
|
628
|
+
// Green "GO" header: the panel only ever receives a STABLE + valid
|
|
629
|
+
// consensus MRZ (the caller withholds it until then), so showing it
|
|
630
|
+
// here IS the reliable-detection signal.
|
|
631
|
+
color: '#4CAF50',
|
|
629
632
|
fontSize: 12,
|
|
630
633
|
fontWeight: 'bold',
|
|
631
634
|
marginBottom: 8,
|
|
632
635
|
textAlign: 'center',
|
|
633
636
|
}}
|
|
634
637
|
>
|
|
635
|
-
MRZ
|
|
638
|
+
✓ MRZ STABLE — GO
|
|
636
639
|
</TextView>
|
|
637
640
|
<TextView
|
|
638
641
|
style={{
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Alert } from 'react-native';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import StyledButton from './StyledButton';
|
|
5
|
+
import {
|
|
6
|
+
sendDiagnosticReport,
|
|
7
|
+
isDiagnosticSharingAvailable,
|
|
8
|
+
type DiagnosticImage,
|
|
9
|
+
} from '../Libs/sendDiagnosticReport';
|
|
10
|
+
import type { ScanDataForReport } from '../Libs/diagnosticReport';
|
|
11
|
+
import { SDK_VERSION } from '../../version';
|
|
12
|
+
|
|
13
|
+
export interface DiagnosticReportButtonProps {
|
|
14
|
+
scan: ScanDataForReport;
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
images?: DiagnosticImage[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* "Report a scanning issue" button for the result screen.
|
|
21
|
+
*
|
|
22
|
+
* Shows a brief notice describing what will be shared (the draft itself is the
|
|
23
|
+
* user's review/consent step), then opens the OS share sheet — the user picks
|
|
24
|
+
* email / WhatsApp / Drive / etc. and sends the diagnostic bundle to support.
|
|
25
|
+
* Self-contained so the result screen only needs to render it with the scan
|
|
26
|
+
* data + captured images.
|
|
27
|
+
*/
|
|
28
|
+
const DiagnosticReportButton: React.FC<DiagnosticReportButtonProps> = ({
|
|
29
|
+
scan,
|
|
30
|
+
sessionId,
|
|
31
|
+
images,
|
|
32
|
+
}) => {
|
|
33
|
+
const { t } = useTranslation();
|
|
34
|
+
const [sending, setSending] = useState(false);
|
|
35
|
+
|
|
36
|
+
// `react-native-share` is optional — hide the button entirely when the host
|
|
37
|
+
// app hasn't installed it, so apps that don't want diagnostics see no UI.
|
|
38
|
+
if (!isDiagnosticSharingAvailable()) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const onPress = () => {
|
|
43
|
+
if (sending) return;
|
|
44
|
+
// Brief notice — the share draft is the actual review/consent step.
|
|
45
|
+
Alert.alert(
|
|
46
|
+
t('diagnosticReport.noticeTitle'),
|
|
47
|
+
t('diagnosticReport.noticeBody'),
|
|
48
|
+
[
|
|
49
|
+
{ text: t('diagnosticReport.cancel'), style: 'cancel' },
|
|
50
|
+
{
|
|
51
|
+
text: t('diagnosticReport.continue'),
|
|
52
|
+
onPress: async () => {
|
|
53
|
+
setSending(true);
|
|
54
|
+
try {
|
|
55
|
+
await sendDiagnosticReport({
|
|
56
|
+
scan,
|
|
57
|
+
sdkVersion: SDK_VERSION,
|
|
58
|
+
sessionId,
|
|
59
|
+
images,
|
|
60
|
+
});
|
|
61
|
+
} finally {
|
|
62
|
+
setSending(false);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
]
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<StyledButton mode="outlined" onPress={onPress} disabled={sending}>
|
|
72
|
+
{t('diagnosticReport.button')}
|
|
73
|
+
</StyledButton>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default DiagnosticReportButton;
|