@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.
@@ -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, Alert } from 'react-native';
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
- // 🚨 WATCHDOG TIMER: If the iOS network drops and doesn't throw, this guarantees the UI unlocks after 20 seconds.
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' ? 'Connection timeout. Please try again.' : 'Délai de connexion dépassé. Veuillez réessayer.'
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
- console.log("Backend Verification Error:", error);
710
+ const isNetworkError = !error.response || error.message === 'Network Error' || error.code === 'ECONNABORTED' || error.message?.includes('Network');
724
711
 
725
- const isNetworkError =
726
- !error.response ||
727
- error.message === 'Network Error' ||
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
- userFriendlyMessage = state.currentLanguage === 'en'
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
- userFriendlyMessage = 'Document unreadable. Please hold the camera steady in good light.';
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
- userFriendlyMessage = 'The document does not match your selected country.';
722
+ hintMessage = state.currentLanguage === 'en' ? 'Wrong document type.' : 'Mauvais type de document.';
748
723
  } else if (rawMessage.includes('card_not_fully_in_frame')) {
749
- userFriendlyMessage = 'Move closer to the document and ensure all corners are visible.';
724
+ hintMessage = state.currentLanguage === 'en' ? 'Move closer.' : 'Rapprochez-vous.';
750
725
  } else if (rawMessage.includes('ne correspond pas au recto')) {
751
- userFriendlyMessage = 'The back of the document does not match the front. You must scan the same ID.';
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: userFriendlyMessage }));
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
- ? 'An unknown camera error occurred.'
778
- : 'Une erreur de caméra inconnue est survenue.';
779
-
780
- // 1. Detect Hardware Lock (Camera in use by Zoom, Teams, or another app)
781
- if (
782
- errString.includes('notreadableerror') ||
783
- errString.includes('trackstarterror') ||
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 de Caméra', friendlyMessage);
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
- // @ts-ignore - legacy export might not be in types
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 or earlier - use regular import as fallback
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
- // @ts-ignore
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 or earlier - use regular import as fallback
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
- const devices = Camera.getAvailableCameraDevices();
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
- const frontCamera = devices.find(device => device.position === 'front');
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
- const backCamera = devices.find(device => device.position === 'back');
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: cameraStatus,
213
- microphone: microphoneStatus,
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
- return {
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
- // Return a placeholder result since this should be handled by the component
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
- // @ts-ignore - legacy export might not be in types
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 (works on web; often works on RN for file:// URIs)
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;