@sanctum-key/react-native-sdk 1.0.35 → 1.0.37
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/build/package.json +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +21 -54
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/modules/camera/VisionCameraModule.d.ts +0 -34
- package/build/src/modules/camera/VisionCameraModule.d.ts.map +1 -1
- package/build/src/modules/camera/VisionCameraModule.js +20 -102
- package/build/src/modules/camera/VisionCameraModule.js.map +1 -1
- package/build/src/utils/pathToBase64.d.ts.map +1 -1
- package/build/src/utils/pathToBase64.js +3 -5
- package/build/src/utils/pathToBase64.js.map +1 -1
- package/package.json +1 -1
- package/src/components/KYCElements/IDCardCapture.tsx +51 -97
- package/src/modules/camera/VisionCameraModule.ts +18 -106
- package/src/utils/pathToBase64.ts +4 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator, TouchableOpacity, useWindowDimensions
|
|
2
|
+
import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator, TouchableOpacity, useWindowDimensions } from 'react-native';
|
|
3
3
|
import { showAlert } from '../../utils/platformAlert';
|
|
4
4
|
import { EnhancedCameraView } from '../EnhancedCameraView';
|
|
5
5
|
import { TemplateComponent, LocalizedText, GovernmentDocumentType, ISilentCaptureResult, IBbox, GovernmentDocumentTypeShorted, GovernmentDocumentTypeBackend } from '../../types/KYC.types';
|
|
@@ -554,43 +554,34 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
554
554
|
if (isProcessingCaptureRef.current) return;
|
|
555
555
|
if (scanPhaseRef.current === 'barcode') return;
|
|
556
556
|
if (isAnalyzingRef.current) return;
|
|
557
|
-
|
|
557
|
+
|
|
558
558
|
if (result.success && result.path) {
|
|
559
559
|
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
|
|
560
|
-
|
|
561
|
-
const safeCapturePath = Platform.OS === 'ios' && !result.path.startsWith('file://')
|
|
562
|
-
? `file://${result.path}`
|
|
563
|
-
: result.path;
|
|
564
|
-
|
|
560
|
+
|
|
561
|
+
const safeCapturePath = Platform.OS === 'ios' && !result.path.startsWith('file://') ? `file://${result.path}` : result.path;
|
|
565
562
|
let templatePath = capturedImages[currentSide]?.templatePath || '';
|
|
566
563
|
let templateBbox: IBbox | undefined;
|
|
567
564
|
let templateResponse: any;
|
|
568
|
-
|
|
565
|
+
|
|
569
566
|
if (!selectedDocumentType) {
|
|
570
567
|
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' }));
|
|
571
568
|
return;
|
|
572
569
|
}
|
|
573
|
-
|
|
570
|
+
|
|
574
571
|
const issuingSubdivision = selectedDocumentType.region !== 'root' ? selectedDocumentType.region : undefined;
|
|
575
|
-
|
|
576
|
-
//
|
|
572
|
+
|
|
573
|
+
// Subtle timeout handling without blocking pop-ups
|
|
577
574
|
const watchdog = setTimeout(() => {
|
|
578
575
|
if (isAnalyzingRef.current) {
|
|
579
576
|
setSilentCaptureResult((prev) => ({
|
|
580
577
|
...prev,
|
|
581
578
|
isAnalyzing: false,
|
|
582
579
|
success: false,
|
|
583
|
-
error: state.currentLanguage === 'en' ? '
|
|
580
|
+
error: state.currentLanguage === 'en' ? 'Taking longer than expected. Hold steady.' : 'Traitement long. Veuillez rester immobile.'
|
|
584
581
|
}));
|
|
585
|
-
Alert.alert(
|
|
586
|
-
state.currentLanguage === 'en' ? "Timeout" : "Délai dépassé",
|
|
587
|
-
state.currentLanguage === 'en'
|
|
588
|
-
? "The request took too long. Please verify your network and try again."
|
|
589
|
-
: "La requête a pris trop de temps. Veuillez vérifier votre connexion et réessayer."
|
|
590
|
-
);
|
|
591
582
|
}
|
|
592
583
|
}, 20000);
|
|
593
|
-
|
|
584
|
+
|
|
594
585
|
try {
|
|
595
586
|
if (!templatePath) {
|
|
596
587
|
const templateType = await checkTemplateType({
|
|
@@ -600,9 +591,10 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
600
591
|
postfix: currentSide,
|
|
601
592
|
issuingSubdivision: issuingSubdivision
|
|
602
593
|
}, env);
|
|
603
|
-
|
|
594
|
+
|
|
604
595
|
templateResponse = templateType;
|
|
605
596
|
if (templateType.template_path) templatePath = templateType.template_path;
|
|
597
|
+
|
|
606
598
|
if (templateType.card_obb) {
|
|
607
599
|
const obbConfidence = getObbConfidence((templateType as any).card_obb);
|
|
608
600
|
if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
|
|
@@ -616,21 +608,18 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
616
608
|
} catch {}
|
|
617
609
|
}
|
|
618
610
|
}
|
|
619
|
-
|
|
611
|
+
|
|
620
612
|
const extractedCountryKey = templatePath ? templatePath.split('/')[0] : (ISO_TO_COUNTRY_NAME[countryData?.code || ''] || 'root');
|
|
621
613
|
const regionMappings = getCurrentSideVerification(currentSide, extractedCountryKey);
|
|
622
|
-
|
|
623
614
|
let verificationRes: any;
|
|
615
|
+
|
|
624
616
|
if (currentSide === 'front') {
|
|
625
617
|
const matchedAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'front');
|
|
626
618
|
const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || 'TD1';
|
|
627
|
-
|
|
619
|
+
|
|
628
620
|
verificationRes = await frontVerification({
|
|
629
621
|
path: safeCapturePath,
|
|
630
|
-
regionMapping: {
|
|
631
|
-
authMethod: matchedAuthMethod ? [matchedAuthMethod] : [],
|
|
632
|
-
mrzTypes: mrzType ? [mrzType] : []
|
|
633
|
-
},
|
|
622
|
+
regionMapping: { authMethod: matchedAuthMethod ? [matchedAuthMethod] : [], mrzTypes: mrzType ? [mrzType] : [] },
|
|
634
623
|
selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType?.type as keyof typeof GovernmentDocumentTypeShorted] || '',
|
|
635
624
|
code: countryData?.code || '',
|
|
636
625
|
currentSide,
|
|
@@ -639,19 +628,15 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
639
628
|
issuingSubdivision: issuingSubdivision,
|
|
640
629
|
clientSideBarcodeData: silentCaptureResult.barcode
|
|
641
630
|
}, env);
|
|
642
|
-
|
|
643
631
|
} else {
|
|
644
632
|
logger.log("Attempting backVerification with:", { path: safeCapturePath, templatePath, side: currentSide });
|
|
645
633
|
const matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
|
|
646
634
|
const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
|
|
647
635
|
const frontTemplatePath = capturedImages['front']?.templatePath || '';
|
|
648
|
-
|
|
636
|
+
|
|
649
637
|
verificationRes = await backVerification({
|
|
650
638
|
path: safeCapturePath,
|
|
651
|
-
regionMapping: {
|
|
652
|
-
authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : [],
|
|
653
|
-
mrzTypes: backMrzType ? [backMrzType] : []
|
|
654
|
-
},
|
|
639
|
+
regionMapping: { authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : [], mrzTypes: backMrzType ? [backMrzType] : [] },
|
|
655
640
|
selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type as keyof typeof GovernmentDocumentTypeShorted] || '',
|
|
656
641
|
code: countryData?.code || '',
|
|
657
642
|
currentSide,
|
|
@@ -662,35 +647,37 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
662
647
|
frontTemplatePath: frontTemplatePath
|
|
663
648
|
}, env);
|
|
664
649
|
}
|
|
665
|
-
|
|
650
|
+
|
|
666
651
|
const bbox = verificationRes?.bbox || templateBbox;
|
|
667
652
|
const verifiedMrz = verificationRes?.mrz ? JSON.stringify(verificationRes.mrz) : "";
|
|
668
|
-
|
|
653
|
+
|
|
669
654
|
if (isBarcodeRequired()) {
|
|
670
655
|
const barcodeData = scannedBarcodeRef.current;
|
|
671
656
|
const docTypeStr = selectedDocumentType!.type;
|
|
672
657
|
const isHIC = docTypeStr === 'health_insurance_card';
|
|
673
658
|
const isValidScan = typeof barcodeData === 'string' && ((isHIC && barcodeData.length > 0) || barcodeData.length > 50);
|
|
674
|
-
|
|
659
|
+
|
|
675
660
|
if (isValidScan) {
|
|
676
661
|
logger.log('[CA Barcode] Image captured, verified & barcode ALREADY scanned! Skipping Phase 2.');
|
|
677
662
|
let parsedDictionary;
|
|
663
|
+
|
|
678
664
|
if (isHIC && barcodeData.length < 50) {
|
|
679
665
|
parsedDictionary = { data: { card_number: barcodeData, barcode_type: "1D" } };
|
|
680
666
|
} else {
|
|
681
667
|
const token = await authentification();
|
|
682
668
|
parsedDictionary = await kycService.formatBarcodeData({ textInput: barcodeData, token }, env);
|
|
683
669
|
}
|
|
684
|
-
|
|
670
|
+
|
|
685
671
|
const verifiedResult: ISilentCaptureResult & { region?: string } = {
|
|
686
672
|
path: safeCapturePath, templatePath: templatePath, bbox: bbox, success: true,
|
|
687
673
|
mrz: JSON.stringify(parsedDictionary), barcode: barcodeData, isAnalyzing: false,
|
|
688
674
|
country: countryData?.code, documentType: selectedDocumentType!.type,
|
|
689
675
|
region: selectedDocumentType!.region !== 'root' ? selectedDocumentType!.region : undefined,
|
|
690
676
|
};
|
|
691
|
-
|
|
677
|
+
|
|
692
678
|
clearTimeout(watchdog);
|
|
693
679
|
setSilentCaptureResult(verifiedResult);
|
|
680
|
+
|
|
694
681
|
if (bbox) {
|
|
695
682
|
const safeBbox = bbox as IBbox;
|
|
696
683
|
setBboxBySide(prev => ({ ...prev, ['back']: safeBbox }));
|
|
@@ -698,7 +685,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
698
685
|
await autoCapture(safeCapturePath!, verifiedResult);
|
|
699
686
|
return;
|
|
700
687
|
}
|
|
701
|
-
|
|
688
|
+
|
|
702
689
|
logger.log('[CA Barcode] Image captured & template VERIFIED. Switching to Barcode phase.');
|
|
703
690
|
clearTimeout(watchdog);
|
|
704
691
|
setPendingCAImage({ path: safeCapturePath!, templateResponse, templatePath, bbox });
|
|
@@ -706,7 +693,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
706
693
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
|
|
707
694
|
return;
|
|
708
695
|
}
|
|
709
|
-
|
|
696
|
+
|
|
710
697
|
const verifiedResult: ISilentCaptureResult & { region?: string } = {
|
|
711
698
|
path: safeCapturePath, templatePath, bbox, success: true, mrz: verifiedMrz,
|
|
712
699
|
isAnalyzing: false, country: countryData?.code, documentType: selectedDocumentType.type,
|
|
@@ -717,46 +704,34 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
717
704
|
setSilentCaptureResult(verifiedResult);
|
|
718
705
|
if (bbox) setBboxBySide(prev => ({ ...prev, [currentSide]: bbox }));
|
|
719
706
|
await autoCapture(safeCapturePath, verifiedResult);
|
|
720
|
-
|
|
707
|
+
|
|
721
708
|
} catch (error: any) {
|
|
722
709
|
clearTimeout(watchdog);
|
|
723
|
-
|
|
710
|
+
const isNetworkError = !error.response || error.message === 'Network Error' || error.code === 'ECONNABORTED' || error.message?.includes('Network');
|
|
724
711
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
error.code === 'ECONNABORTED' ||
|
|
729
|
-
error.message?.includes('Network');
|
|
730
|
-
|
|
731
|
-
let userFriendlyMessage = 'Verification failed. Please try again.';
|
|
732
|
-
|
|
712
|
+
// Keep UI errors extremely brief and actionable
|
|
713
|
+
let hintMessage = '';
|
|
714
|
+
|
|
733
715
|
if (isNetworkError) {
|
|
734
|
-
|
|
735
|
-
? 'Cannot connect to verification server. Please check your internet connection.'
|
|
736
|
-
: 'Impossible de se connecter au serveur de vérification. Veuillez vérifier votre connexion internet.';
|
|
737
|
-
|
|
738
|
-
Alert.alert(
|
|
739
|
-
state.currentLanguage === 'en' ? "Connection Failed" : "Échec de connexion",
|
|
740
|
-
userFriendlyMessage
|
|
741
|
-
);
|
|
716
|
+
hintMessage = state.currentLanguage === 'en' ? 'Poor connection. Retrying...' : 'Connexion faible. Nouvelle tentative...';
|
|
742
717
|
} else {
|
|
743
718
|
const rawMessage = (error?.message || '').toLowerCase();
|
|
744
719
|
if (rawMessage.includes('impossible') || rawMessage.includes('unreadable')) {
|
|
745
|
-
|
|
720
|
+
hintMessage = state.currentLanguage === 'en' ? 'Move to better lighting.' : 'Améliorez l\'éclairage.';
|
|
746
721
|
} else if (rawMessage.includes('country mismatch') || rawMessage.includes('pays sélectionné')) {
|
|
747
|
-
|
|
722
|
+
hintMessage = state.currentLanguage === 'en' ? 'Wrong document type.' : 'Mauvais type de document.';
|
|
748
723
|
} else if (rawMessage.includes('card_not_fully_in_frame')) {
|
|
749
|
-
|
|
724
|
+
hintMessage = state.currentLanguage === 'en' ? 'Move closer.' : 'Rapprochez-vous.';
|
|
750
725
|
} else if (rawMessage.includes('ne correspond pas au recto')) {
|
|
751
|
-
|
|
726
|
+
hintMessage = state.currentLanguage === 'en' ? 'Scan the same ID.' : 'Scannez la même pièce.';
|
|
752
727
|
}
|
|
753
728
|
}
|
|
754
|
-
|
|
755
|
-
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error:
|
|
729
|
+
|
|
730
|
+
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: hintMessage }));
|
|
756
731
|
}
|
|
757
732
|
}
|
|
758
733
|
};
|
|
759
|
-
|
|
734
|
+
|
|
760
735
|
useEffect(() => {
|
|
761
736
|
if (scanPhase === 'barcode' && pendingCAImage && silentCaptureResult.barcode) {
|
|
762
737
|
const bData = silentCaptureResult.barcode;
|
|
@@ -766,44 +741,23 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
766
741
|
}
|
|
767
742
|
}
|
|
768
743
|
}, [scanPhase, pendingCAImage, silentCaptureResult.barcode]);
|
|
744
|
+
|
|
769
745
|
|
|
770
746
|
const handleError = (event: { message: string, name?: string, code?: string }) => {
|
|
771
747
|
logger.error("Camera Error:", event);
|
|
772
|
-
|
|
773
|
-
// Normalize the error to lowercase for easy checking
|
|
774
748
|
const errString = `${event.message} ${event.name} ${event.code}`.toLowerCase();
|
|
775
749
|
|
|
776
|
-
let friendlyMessage = state.currentLanguage === 'en'
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
errString.includes('in use') ||
|
|
785
|
-
errString.includes('concurrent') ||
|
|
786
|
-
errString.includes('camera unavailable')
|
|
787
|
-
) {
|
|
788
|
-
friendlyMessage = state.currentLanguage === 'en'
|
|
789
|
-
? 'Your camera is currently being used by another application (like Zoom or Teams). Please close the other app and try again.'
|
|
790
|
-
: 'Votre caméra est actuellement utilisée par une autre application. Veuillez fermer l\'autre application et réessayer.';
|
|
791
|
-
}
|
|
792
|
-
// 2. Detect Permission Denied
|
|
793
|
-
else if (errString.includes('notallowederror') || errString.includes('permission')) {
|
|
794
|
-
friendlyMessage = state.currentLanguage === 'en'
|
|
795
|
-
? 'Camera permission denied. Please allow camera access in your settings to proceed.'
|
|
796
|
-
: 'Autorisation de la caméra refusée. Veuillez autoriser l\'accès dans vos paramètres.';
|
|
797
|
-
}
|
|
798
|
-
// 3. Detect No Camera Found
|
|
799
|
-
else if (errString.includes('notfounderror') || errString.includes('no camera')) {
|
|
800
|
-
friendlyMessage = state.currentLanguage === 'en'
|
|
801
|
-
? 'No camera detected on this device.'
|
|
802
|
-
: 'Aucune caméra détectée sur cet appareil.';
|
|
750
|
+
let friendlyMessage = state.currentLanguage === 'en' ? 'Camera error.' : 'Erreur de caméra.';
|
|
751
|
+
|
|
752
|
+
if (errString.includes('notreadableerror') || errString.includes('trackstarterror') || errString.includes('in use') || errString.includes('concurrent') || errString.includes('camera unavailable')) {
|
|
753
|
+
friendlyMessage = state.currentLanguage === 'en' ? 'Camera is in use by another app.' : 'Caméra utilisée par une autre application.';
|
|
754
|
+
} else if (errString.includes('notallowederror') || errString.includes('permission')) {
|
|
755
|
+
friendlyMessage = state.currentLanguage === 'en' ? 'Allow camera access in settings.' : 'Autorisez l\'accès à la caméra.';
|
|
756
|
+
} else if (errString.includes('notfounderror') || errString.includes('no camera')) {
|
|
757
|
+
friendlyMessage = state.currentLanguage === 'en' ? 'No camera detected.' : 'Aucune caméra détectée.';
|
|
803
758
|
}
|
|
804
|
-
|
|
805
|
-
showAlert(state.currentLanguage === 'en' ? 'Camera Error' : 'Erreur
|
|
806
|
-
|
|
759
|
+
|
|
760
|
+
showAlert(state.currentLanguage === 'en' ? 'Camera Error' : 'Erreur', friendlyMessage);
|
|
807
761
|
setShowCamera(false);
|
|
808
762
|
setIsProcessingCapture(false);
|
|
809
763
|
};
|
|
@@ -3,51 +3,40 @@ import { Camera, CameraDevice, CameraPermissionStatus, PhotoFile } from 'react-n
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Backward-compatible file copy function
|
|
6
|
-
* Tries new File API first (Expo SDK 54+), then legacy API, then regular import for SDK 53
|
|
7
6
|
*/
|
|
8
7
|
async function copyFileCompat(from: string, to: string): Promise<void> {
|
|
9
8
|
// Try new File API first (Expo SDK 54+)
|
|
10
9
|
try {
|
|
11
|
-
// @ts-ignore - File API might not be in types yet
|
|
12
10
|
const { File } = require('expo-file-system');
|
|
13
11
|
if (File && typeof File === 'function') {
|
|
14
12
|
const sourceFile = new File(from);
|
|
15
13
|
const destFile = new File(to);
|
|
16
|
-
// File.copy() is synchronous but may throw
|
|
17
14
|
sourceFile.copy(destFile);
|
|
18
15
|
return;
|
|
19
16
|
}
|
|
20
17
|
} catch (error) {
|
|
21
18
|
// New API not available or failed, try legacy
|
|
22
|
-
// Continue to legacy API
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
// Try legacy API from expo-file-system/legacy (Expo SDK 54+)
|
|
26
|
-
// This is the recommended way to avoid deprecation warnings
|
|
27
22
|
try {
|
|
28
|
-
//
|
|
29
|
-
const LegacyFileSystem = require('expo-file-system');
|
|
23
|
+
// FIX: Appended /legacy to the require string
|
|
24
|
+
const LegacyFileSystem = require('expo-file-system/legacy');
|
|
30
25
|
if (LegacyFileSystem && LegacyFileSystem.copyAsync && typeof LegacyFileSystem.copyAsync === 'function') {
|
|
31
26
|
await LegacyFileSystem.copyAsync({ from, to });
|
|
32
27
|
return;
|
|
33
28
|
}
|
|
34
29
|
} catch (error) {
|
|
35
|
-
// Legacy not available (SDK 53 or earlier), fall through to regular import
|
|
36
|
-
// Check if it's a "module not found" error (SDK 53) vs other error
|
|
37
30
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
38
31
|
if (errorMessage.includes('Cannot find module') || errorMessage.includes('Module not found')) {
|
|
39
|
-
// SDK 53
|
|
40
|
-
// This will show a deprecation warning on SDK 54+, but is necessary for SDK 53 compatibility
|
|
32
|
+
// SDK 53 fallback logic...
|
|
41
33
|
} else {
|
|
42
|
-
// Other error, re-throw
|
|
43
34
|
throw new Error(`Unable to copy file: legacy FileSystem API error. Error: ${errorMessage}`);
|
|
44
35
|
}
|
|
45
36
|
}
|
|
46
37
|
|
|
47
38
|
// Fallback to regular import for SDK 53 and earlier
|
|
48
|
-
// Note: This will show a deprecation warning on SDK 54+, but is necessary for backward compatibility
|
|
49
39
|
try {
|
|
50
|
-
// @ts-ignore
|
|
51
40
|
const FileSystem = require('expo-file-system');
|
|
52
41
|
if (FileSystem && FileSystem.copyAsync && typeof FileSystem.copyAsync === 'function') {
|
|
53
42
|
await FileSystem.copyAsync({ from, to });
|
|
@@ -63,12 +52,9 @@ async function copyFileCompat(from: string, to: string): Promise<void> {
|
|
|
63
52
|
|
|
64
53
|
/**
|
|
65
54
|
* Get document directory path in a backward-compatible way
|
|
66
|
-
* Supports SDK 53 (regular import) and SDK 54+ (legacy or new API)
|
|
67
55
|
*/
|
|
68
56
|
function getDocumentDirectory(): string {
|
|
69
57
|
try {
|
|
70
|
-
// Try new Paths API first (Expo SDK 54+)
|
|
71
|
-
// @ts-ignore
|
|
72
58
|
const { Paths } = require('expo-file-system');
|
|
73
59
|
if (Paths?.document) {
|
|
74
60
|
return Paths.document.uri;
|
|
@@ -77,29 +63,20 @@ function getDocumentDirectory(): string {
|
|
|
77
63
|
// New API not available, try legacy
|
|
78
64
|
}
|
|
79
65
|
|
|
80
|
-
// Try legacy API from expo-file-system/legacy (Expo SDK 54+)
|
|
81
|
-
// This is the recommended way to avoid deprecation warnings
|
|
82
66
|
try {
|
|
83
|
-
//
|
|
84
|
-
const LegacyFileSystem = require('expo-file-system');
|
|
67
|
+
// FIX: Appended /legacy to the require string
|
|
68
|
+
const LegacyFileSystem = require('expo-file-system/legacy');
|
|
85
69
|
if (LegacyFileSystem && LegacyFileSystem.documentDirectory) {
|
|
86
70
|
return LegacyFileSystem.documentDirectory;
|
|
87
71
|
}
|
|
88
72
|
} catch (error) {
|
|
89
|
-
// Legacy not available (SDK 53 or earlier), fall through to regular import
|
|
90
73
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
91
74
|
if (errorMessage.includes('Cannot find module') || errorMessage.includes('Module not found')) {
|
|
92
|
-
// SDK 53
|
|
93
|
-
// This will show a deprecation warning on SDK 54+, but is necessary for SDK 53 compatibility
|
|
94
|
-
} else {
|
|
95
|
-
// Other error, continue to fallback
|
|
75
|
+
// SDK 53 fallback logic...
|
|
96
76
|
}
|
|
97
77
|
}
|
|
98
78
|
|
|
99
|
-
// Fallback to regular import for SDK 53 and earlier
|
|
100
|
-
// Note: This will show a deprecation warning on SDK 54+, but is necessary for backward compatibility
|
|
101
79
|
try {
|
|
102
|
-
// @ts-ignore
|
|
103
80
|
const FileSystem = require('expo-file-system');
|
|
104
81
|
if (FileSystem && FileSystem.documentDirectory) {
|
|
105
82
|
return FileSystem.documentDirectory;
|
|
@@ -133,50 +110,35 @@ export class VisionCameraModule {
|
|
|
133
110
|
return VisionCameraModule.instance;
|
|
134
111
|
}
|
|
135
112
|
|
|
136
|
-
/**
|
|
137
|
-
* Get available camera devices
|
|
138
|
-
*/
|
|
139
113
|
async getCameraDevices(): Promise<CameraDevice[]> {
|
|
140
114
|
try {
|
|
141
|
-
|
|
142
|
-
return devices;
|
|
115
|
+
return Camera.getAvailableCameraDevices();
|
|
143
116
|
} catch (error) {
|
|
144
117
|
console.error('Error getting camera devices:', error);
|
|
145
118
|
return [];
|
|
146
119
|
}
|
|
147
120
|
}
|
|
148
121
|
|
|
149
|
-
/**
|
|
150
|
-
* Get the best available device for selfies (front camera)
|
|
151
|
-
*/
|
|
152
122
|
async getFrontCameraDevice(): Promise<CameraDevice | null> {
|
|
153
123
|
try {
|
|
154
124
|
const devices = await this.getCameraDevices();
|
|
155
|
-
|
|
156
|
-
return frontCamera || null;
|
|
125
|
+
return devices.find(device => device.position === 'front') || null;
|
|
157
126
|
} catch (error) {
|
|
158
127
|
console.error('Error getting front camera:', error);
|
|
159
128
|
return null;
|
|
160
129
|
}
|
|
161
130
|
}
|
|
162
131
|
|
|
163
|
-
/**
|
|
164
|
-
* Get the best available device for document capture (back camera)
|
|
165
|
-
*/
|
|
166
132
|
async getBackCameraDevice(): Promise<CameraDevice | null> {
|
|
167
133
|
try {
|
|
168
134
|
const devices = await this.getCameraDevices();
|
|
169
|
-
|
|
170
|
-
return backCamera || null;
|
|
135
|
+
return devices.find(device => device.position === 'back') || null;
|
|
171
136
|
} catch (error) {
|
|
172
137
|
console.error('Error getting back camera:', error);
|
|
173
138
|
return null;
|
|
174
139
|
}
|
|
175
140
|
}
|
|
176
141
|
|
|
177
|
-
/**
|
|
178
|
-
* Request camera permissions
|
|
179
|
-
*/
|
|
180
142
|
async requestCameraPermission(): Promise<boolean> {
|
|
181
143
|
try {
|
|
182
144
|
const permission = await Camera.requestCameraPermission();
|
|
@@ -187,9 +149,6 @@ export class VisionCameraModule {
|
|
|
187
149
|
}
|
|
188
150
|
}
|
|
189
151
|
|
|
190
|
-
/**
|
|
191
|
-
* Request microphone permissions
|
|
192
|
-
*/
|
|
193
152
|
async requestMicrophonePermission(): Promise<boolean> {
|
|
194
153
|
try {
|
|
195
154
|
const permission = await Camera.requestMicrophonePermission();
|
|
@@ -200,30 +159,18 @@ export class VisionCameraModule {
|
|
|
200
159
|
}
|
|
201
160
|
}
|
|
202
161
|
|
|
203
|
-
/**
|
|
204
|
-
* Get current permission status
|
|
205
|
-
*/
|
|
206
162
|
async getPermissionStatus(): Promise<CameraPermissions> {
|
|
207
163
|
try {
|
|
208
|
-
const cameraStatus = await Camera.getCameraPermissionStatus();
|
|
209
|
-
const microphoneStatus = await Camera.getMicrophonePermissionStatus();
|
|
210
|
-
|
|
211
164
|
return {
|
|
212
|
-
camera:
|
|
213
|
-
microphone:
|
|
165
|
+
camera: await Camera.getCameraPermissionStatus(),
|
|
166
|
+
microphone: await Camera.getMicrophonePermissionStatus(),
|
|
214
167
|
};
|
|
215
168
|
} catch (error) {
|
|
216
169
|
console.error('Error getting permission status:', error);
|
|
217
|
-
return {
|
|
218
|
-
camera: 'denied',
|
|
219
|
-
microphone: 'denied',
|
|
220
|
-
};
|
|
170
|
+
return { camera: 'denied', microphone: 'denied' };
|
|
221
171
|
}
|
|
222
172
|
}
|
|
223
173
|
|
|
224
|
-
/**
|
|
225
|
-
* Check if all required permissions are granted
|
|
226
|
-
*/
|
|
227
174
|
async hasAllPermissions(): Promise<boolean> {
|
|
228
175
|
try {
|
|
229
176
|
const status = await this.getPermissionStatus();
|
|
@@ -235,16 +182,12 @@ export class VisionCameraModule {
|
|
|
235
182
|
}
|
|
236
183
|
}
|
|
237
184
|
|
|
238
|
-
/**
|
|
239
|
-
* Request all required permissions
|
|
240
|
-
*/
|
|
241
185
|
async requestAllPermissions(): Promise<boolean> {
|
|
242
186
|
try {
|
|
243
187
|
const [cameraGranted, microphoneGranted] = await Promise.all([
|
|
244
188
|
this.requestCameraPermission(),
|
|
245
189
|
this.requestMicrophonePermission(),
|
|
246
190
|
]);
|
|
247
|
-
|
|
248
191
|
return cameraGranted && microphoneGranted;
|
|
249
192
|
} catch (error) {
|
|
250
193
|
console.error('Error requesting permissions:', error);
|
|
@@ -252,15 +195,9 @@ export class VisionCameraModule {
|
|
|
252
195
|
}
|
|
253
196
|
}
|
|
254
197
|
|
|
255
|
-
/**
|
|
256
|
-
* Check if camera is available
|
|
257
|
-
*/
|
|
258
198
|
async isCameraAvailable(): Promise<boolean> {
|
|
259
199
|
try {
|
|
260
|
-
if (Platform.OS === 'web')
|
|
261
|
-
return false; // Vision Camera doesn't support web
|
|
262
|
-
}
|
|
263
|
-
|
|
200
|
+
if (Platform.OS === 'web') return false;
|
|
264
201
|
const devices = await this.getCameraDevices();
|
|
265
202
|
return devices.length > 0;
|
|
266
203
|
} catch (error) {
|
|
@@ -269,9 +206,6 @@ export class VisionCameraModule {
|
|
|
269
206
|
}
|
|
270
207
|
}
|
|
271
208
|
|
|
272
|
-
/**
|
|
273
|
-
* Process photo capture result
|
|
274
|
-
*/
|
|
275
209
|
async processPhotoResult(photo: PhotoFile): Promise<CameraCaptureResult> {
|
|
276
210
|
try {
|
|
277
211
|
const documentDir = getDocumentDirectory();
|
|
@@ -292,41 +226,19 @@ export class VisionCameraModule {
|
|
|
292
226
|
}
|
|
293
227
|
}
|
|
294
228
|
|
|
295
|
-
/**
|
|
296
|
-
* Legacy method for backward compatibility
|
|
297
|
-
* @deprecated Use VisionCameraView component instead
|
|
298
|
-
*/
|
|
299
229
|
async openCameraWithInstructions(instructions: string): Promise<CameraCaptureResult> {
|
|
300
230
|
console.warn('openCameraWithInstructions is deprecated. Use VisionCameraView component instead.');
|
|
301
|
-
|
|
302
231
|
try {
|
|
303
232
|
const hasPermissions = await this.requestAllPermissions();
|
|
304
|
-
if (!hasPermissions) {
|
|
305
|
-
|
|
306
|
-
success: false,
|
|
307
|
-
error: 'Camera permissions not granted',
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
|
|
233
|
+
if (!hasPermissions) return { success: false, error: 'Camera permissions not granted' };
|
|
234
|
+
|
|
311
235
|
const isAvailable = await this.isCameraAvailable();
|
|
312
|
-
if (!isAvailable) {
|
|
313
|
-
return {
|
|
314
|
-
success: false,
|
|
315
|
-
error: 'Camera not available',
|
|
316
|
-
};
|
|
317
|
-
}
|
|
236
|
+
if (!isAvailable) return { success: false, error: 'Camera not available' };
|
|
318
237
|
|
|
319
|
-
|
|
320
|
-
return {
|
|
321
|
-
success: true,
|
|
322
|
-
path: `mock_photo_${Date.now()}.jpg`,
|
|
323
|
-
};
|
|
238
|
+
return { success: true, path: `mock_photo_${Date.now()}.jpg` };
|
|
324
239
|
} catch (error) {
|
|
325
240
|
console.error('Error in openCameraWithInstructions:', error);
|
|
326
|
-
return {
|
|
327
|
-
success: false,
|
|
328
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
329
|
-
};
|
|
241
|
+
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
330
242
|
}
|
|
331
243
|
}
|
|
332
244
|
}
|
|
@@ -10,10 +10,9 @@ export async function pathToBase64(uri: string): Promise<string> {
|
|
|
10
10
|
// Try Expo FileSystem if available (React Native + Expo)
|
|
11
11
|
try {
|
|
12
12
|
// Try legacy API from expo-file-system/legacy first (Expo SDK 54+)
|
|
13
|
-
// This is the recommended way to avoid deprecation warnings
|
|
14
13
|
try {
|
|
15
|
-
//
|
|
16
|
-
const LegacyFileSystem = require('expo-file-system');
|
|
14
|
+
// FIX: Appended /legacy to the require string
|
|
15
|
+
const LegacyFileSystem = require('expo-file-system/legacy');
|
|
17
16
|
if (LegacyFileSystem?.readAsStringAsync) {
|
|
18
17
|
const base64 = await LegacyFileSystem.readAsStringAsync(uri, { encoding: LegacyFileSystem.EncodingType.Base64 });
|
|
19
18
|
if (base64) return base64;
|
|
@@ -30,13 +29,12 @@ export async function pathToBase64(uri: string): Promise<string> {
|
|
|
30
29
|
if (base64) return base64;
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
|
-
// If it's a different error, continue to other strategies
|
|
34
32
|
}
|
|
35
33
|
} catch (_) {
|
|
36
34
|
// All FileSystem APIs failed, fall back to other strategies (fetch + FileReader)
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
// Generic fetch + FileReader strategy
|
|
37
|
+
// Generic fetch + FileReader strategy
|
|
40
38
|
try {
|
|
41
39
|
const response = await fetch(uri);
|
|
42
40
|
const blob = await response.blob();
|
|
@@ -58,6 +56,4 @@ export async function pathToBase64(uri: string): Promise<string> {
|
|
|
58
56
|
throw new Error('pathToBase64: unable to convert uri to base64');
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
export default pathToBase64;
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
export default pathToBase64;
|