@sanctum-key/react-native-sdk 1.0.33 → 1.0.35

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanctum-key/react-native-sdk",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "description": "Sanctum Key React Native SDK",
5
5
  "main": "build/src/index.js",
6
6
  "types": "build/src/index.d.ts",
@@ -1 +1 @@
1
- {"version":3,"file":"EnhancedCameraView.d.ts","sourceRoot":"","sources":["../../../src/components/EnhancedCameraView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAExE,OAAO,EAAU,QAAQ,EAAmC,MAAM,4BAA4B,CAAC;AAG/F,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC;IACnF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC9E;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA0O5D,CAAC"}
1
+ {"version":3,"file":"EnhancedCameraView.d.ts","sourceRoot":"","sources":["../../../src/components/EnhancedCameraView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAExE,OAAO,EAAU,QAAQ,EAAmC,MAAM,4BAA4B,CAAC;AAG/F,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC;IACnF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC9E;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAqS5D,CAAC"}
@@ -10,6 +10,9 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
10
10
  const webVideoRef = useRef(null);
11
11
  const isCapturingRef = useRef(false);
12
12
  const isProcessingRef = useRef(isProcessing);
13
+ // Ref-tracked isAnalyzing so captureSilentPhoto always reads the latest
14
+ // value — the prop-based closure goes stale on iOS between renders.
15
+ const isAnalyzingRef = useRef(silentCaptureResult?.isAnalyzing ?? false);
13
16
  const [cameraType] = useState(initialCameraType);
14
17
  const [hasPermission, setHasPermission] = useState(null);
15
18
  const [isInitialized, setIsInitialized] = useState(false);
@@ -19,9 +22,8 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
19
22
  const containerRef = useRef(null);
20
23
  const [appState, setAppState] = useState(AppState.currentState);
21
24
  const device = useCameraDevice(cameraType);
22
- useEffect(() => {
23
- isProcessingRef.current = isProcessing;
24
- }, [isProcessing]);
25
+ useEffect(() => { isProcessingRef.current = isProcessing; }, [isProcessing]);
26
+ useEffect(() => { isAnalyzingRef.current = silentCaptureResult?.isAnalyzing ?? false; }, [silentCaptureResult?.isAnalyzing]);
25
27
  useEffect(() => {
26
28
  const subscription = AppState.addEventListener('change', nextAppState => {
27
29
  setAppState(nextAppState);
@@ -32,20 +34,26 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
32
34
  }, []);
33
35
  const checkPermissions = async () => {
34
36
  try {
35
- let cameraStatus = Camera.getCameraPermissionStatus();
36
- if (cameraStatus !== 'granted') {
37
- cameraStatus = await Camera.requestCameraPermission();
38
- if (cameraStatus !== 'granted') {
39
- setHasPermission(false);
40
- onError?.({
41
- message: t('camera.permissionRequired'),
42
- name: 'NotAllowedError',
43
- code: 'permission-denied'
44
- });
37
+ // getCameraPermissionStatus is synchronous — no await
38
+ const cameraStatus = Camera.getCameraPermissionStatus();
39
+ if (cameraStatus === 'granted') {
40
+ setHasPermission(true);
41
+ return;
42
+ }
43
+ if (cameraStatus === 'not-determined') {
44
+ const result = await Camera.requestCameraPermission();
45
+ if (result === 'granted') {
46
+ setHasPermission(true);
45
47
  return;
46
48
  }
47
49
  }
48
- setHasPermission(true);
50
+ // 'denied' or 'restricted' — user must enable in Settings
51
+ setHasPermission(false);
52
+ onError?.({
53
+ message: t('camera.permissionRequired'),
54
+ name: 'NotAllowedError',
55
+ code: 'permission-denied'
56
+ });
49
57
  }
50
58
  catch (e) {
51
59
  setHasPermission(false);
@@ -61,7 +69,6 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
61
69
  checkPermissions();
62
70
  }, [showCamera, refreshCamera]);
63
71
  const onInitialized = useCallback(() => setIsInitialized(true), []);
64
- // 🚨 UPDATED: Catch Vision Camera deep errors (e.g. session-in-use) and pass them up
65
72
  const onCameraError = useCallback((error) => {
66
73
  console.warn("🎥 [Vision Camera Error Caught]:", error);
67
74
  onError?.({
@@ -84,35 +91,82 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
84
91
  }
85
92
  }
86
93
  });
94
+ // 1. ISOLATE THE CALLBACK: This breaks the dependency death spiral for all platforms.
95
+ const onSilentCaptureRef = useRef(onSilentCapture);
96
+ useEffect(() => {
97
+ onSilentCaptureRef.current = onSilentCapture;
98
+ }, [onSilentCapture]);
99
+ // 2. THE CAPTURE FUNCTION: Zero dependencies. Immune to re-renders.
87
100
  const captureSilentPhoto = useCallback(async () => {
88
- if (!camera.current || !isInitialized || isProcessingRef.current || isCapturingRef.current)
101
+ // Web handles its own stream via WebBarcodeScanner; do not run native camera methods on web.
102
+ if (Platform.OS === 'web')
89
103
  return;
90
- if (silentCaptureResult?.isAnalyzing)
104
+ // We intentionally ignore `isInitialized`. If AVFoundation (iOS) or CameraX (Android)
105
+ // aren't ready, the try/catch will handle it gracefully.
106
+ if (!camera.current || isProcessingRef.current || isCapturingRef.current)
107
+ return;
108
+ if (isAnalyzingRef.current)
91
109
  return;
92
110
  try {
93
111
  isCapturingRef.current = true;
94
112
  const photo = await camera.current.takePhoto({
95
113
  enableShutterSound: false,
96
114
  flash: 'off',
97
- ...(Platform.OS === 'ios' ? {
98
- photoCodec: 'jpeg',
99
- qualityPrioritization: 'speed',
100
- } : {})
115
+ // iOS requires aggressive compression to prevent RAM crashes during interval captures
116
+ ...(Platform.OS === 'ios' ? { photoCodec: 'jpeg', qualityPrioritization: 'speed' } : {})
101
117
  });
102
- const result = await VisionCameraModule.processPhotoResult(photo);
103
- onSilentCapture?.({
104
- ...result,
105
- path: result.path || photo.path,
118
+ // 3. CROSS-PLATFORM PATH NORMALIZATION
119
+ let finalPath = photo.path;
120
+ let captureSuccess = true;
121
+ // 4. DEFENSIVE NATIVE MODULE EXECUTION
122
+ if (VisionCameraModule && VisionCameraModule.processPhotoResult) {
123
+ try {
124
+ const moduleResult = await VisionCameraModule.processPhotoResult(photo);
125
+ if (moduleResult) {
126
+ captureSuccess = moduleResult.success ?? true;
127
+ finalPath = moduleResult.path || photo.path;
128
+ }
129
+ }
130
+ catch (nativeError) {
131
+ console.warn("⚠️ Native module failed, falling back to raw device path");
132
+ }
133
+ }
134
+ onSilentCaptureRef.current?.({
135
+ success: captureSuccess,
136
+ path: finalPath,
106
137
  barcode: scannedBarcodeRef.current
107
138
  });
108
139
  }
109
- catch {
110
- // Intentionally swallow silent capture errors so we don't spam the UI
140
+ catch (error) {
141
+ // 📸 Silent fail: The camera is busy, focusing, or warming up. Just wait for the next tick.
142
+ console.log(`📸 Frame dropped (${Platform.OS}):`, error?.message);
111
143
  }
112
144
  finally {
113
145
  isCapturingRef.current = false;
114
146
  }
115
- }, [isInitialized, onSilentCapture, silentCaptureResult]);
147
+ }, []); // <-- EMPTY ARRAY GUARANTEES STABILITY
148
+ // 5. THE UNBREAKABLE INTERVAL
149
+ useEffect(() => {
150
+ if (!showCamera || isProcessing)
151
+ return;
152
+ if (Platform.OS === 'web')
153
+ return; // Let web handle its own interval logic
154
+ let active = true;
155
+ let id;
156
+ // Slight delay to let the UI mount before hammering the camera hardware
157
+ const timer = setTimeout(() => {
158
+ if (!active)
159
+ return;
160
+ const intervalDelay = isScanningBarcode ? 3000 : 1500;
161
+ id = setInterval(captureSilentPhoto, intervalDelay);
162
+ }, 1500);
163
+ return () => {
164
+ active = false;
165
+ clearTimeout(timer);
166
+ if (id)
167
+ clearInterval(id);
168
+ };
169
+ }, [showCamera, isProcessing, isScanningBarcode, captureSilentPhoto]);
116
170
  useEffect(() => {
117
171
  if (!showCamera || !isInitialized || isProcessing)
118
172
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"EnhancedCameraView.js","sourceRoot":"","sources":["../../../src/components/EnhancedCameraView.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { View, StyleSheet, Text, Platform, AppState, AppStateStatus } from 'react-native';\nimport { Camera, CodeType, useCameraDevice, useCodeScanner } from 'react-native-vision-camera';\nimport VisionCameraModule from '../modules/camera/VisionCameraModule';\nimport { useI18n } from '../hooks/useI18n';\nimport { EnhancedCameraViewProps } from './OverLay/type';\nimport { WebBarcodeScanner } from './WebBarcodeScanner';\n\nexport interface ExtendedCameraProps extends Omit<EnhancedCameraViewProps, 'onError'> {\n isScanningBarcode?: boolean;\n onBarcodeScanned?: (data: string) => void;\n barcodeTypes?: CodeType[];\n onError?: (error: { message: string; name?: string; code?: string }) => void; \n}\n\nexport const EnhancedCameraView: React.FC<ExtendedCameraProps> = ({\n showCamera,\n cameraType: initialCameraType = 'front',\n style,\n onError,\n onSilentCapture,\n silentCaptureResult,\n isProcessing = false,\n overlayComponent,\n isScanningBarcode = false,\n onBarcodeScanned,\n barcodeTypes\n}) => {\n const { t } = useI18n();\n const camera = useRef<Camera>(null);\n const webVideoRef = useRef<HTMLVideoElement | null>(null);\n \n const isCapturingRef = useRef(false);\n const isProcessingRef = useRef(isProcessing);\n \n const [cameraType] = useState<'front' | 'back'>(initialCameraType);\n const [hasPermission, setHasPermission] = useState<boolean | null>(null);\n const [isInitialized, setIsInitialized] = useState(false);\n const [refreshCamera] = useState(false);\n \n const [isVideoFound, setIsVideoFound] = useState(false);\n const scannedBarcodeRef = useRef<string | null>(null);\n const containerRef = useRef<any>(null);\n\n const [appState, setAppState] = useState<AppStateStatus>(AppState.currentState);\n\n const device = useCameraDevice(cameraType);\n\n useEffect(() => {\n isProcessingRef.current = isProcessing;\n }, [isProcessing]);\n\n useEffect(() => {\n const subscription = AppState.addEventListener('change', nextAppState => {\n setAppState(nextAppState);\n });\n return () => {\n subscription.remove();\n };\n }, []);\n\n const checkPermissions = async () => {\n try {\n let cameraStatus = Camera.getCameraPermissionStatus();\n \n if (cameraStatus !== 'granted') {\n cameraStatus = await Camera.requestCameraPermission();\n \n if (cameraStatus !== 'granted') {\n setHasPermission(false);\n onError?.({ \n message: t('camera.permissionRequired'),\n name: 'NotAllowedError', \n code: 'permission-denied'\n });\n return;\n }\n }\n setHasPermission(true);\n } catch (e: any) {\n setHasPermission(false);\n onError?.({ \n message: e.message || t('camera.errorOccurred'),\n name: e.name || 'UnknownError',\n code: e.code || 'unknown'\n });\n }\n };\n\n useEffect(() => {\n if (showCamera) checkPermissions();\n }, [showCamera, refreshCamera]);\n\n const onInitialized = useCallback(() => setIsInitialized(true), []);\n\n // 🚨 UPDATED: Catch Vision Camera deep errors (e.g. session-in-use) and pass them up\n const onCameraError = useCallback((error: any) => {\n console.warn(\"🎥 [Vision Camera Error Caught]:\", error);\n onError?.({ \n message: error.message || t('camera.errorOccurred'),\n code: error.code, // Contains specific RNVC codes (e.g., 'session/camera-in-use')\n name: error.name // Contains Web standard names (e.g., 'NotReadableError')\n });\n }, [onError, t]);\n\n const codeScanner = useCodeScanner({\n codeTypes: barcodeTypes || ['pdf-417', 'qr', 'code-128', 'code-39'],\n onCodeScanned: (codes) => {\n if (!isProcessingRef.current && isScanningBarcode) {\n for (const code of codes) {\n if (code.value && scannedBarcodeRef.current !== code.value) {\n scannedBarcodeRef.current = code.value;\n onBarcodeScanned?.(code.value);\n break;\n }\n }\n }\n }\n });\n\n const captureSilentPhoto = useCallback(async () => {\n if (!camera.current || !isInitialized || isProcessingRef.current || isCapturingRef.current) return;\n if (silentCaptureResult?.isAnalyzing) return;\n\n try {\n isCapturingRef.current = true;\n const photo = await camera.current.takePhoto({\n enableShutterSound: false,\n flash: 'off',\n\n ...(Platform.OS === 'ios' ? {\n photoCodec: 'jpeg', \n qualityPrioritization: 'speed', \n } : {})\n });\n \n const result = await VisionCameraModule.processPhotoResult(photo);\n onSilentCapture?.({ \n ...result, \n path: result.path || photo.path,\n barcode: scannedBarcodeRef.current \n });\n } catch {\n // Intentionally swallow silent capture errors so we don't spam the UI\n } finally {\n isCapturingRef.current = false;\n }\n }, [isInitialized, onSilentCapture, silentCaptureResult]);\n\n useEffect(() => {\n if (!showCamera || !isInitialized || isProcessing) return;\n let active = true;\n let id: ReturnType<typeof setInterval>;\n \n const timer = setTimeout(() => {\n if (!active) return;\n const intervalDelay = isScanningBarcode ? 3000 : 1500;\n id = setInterval(captureSilentPhoto, intervalDelay);\n }, 1500);\n\n return () => {\n active = false;\n clearTimeout(timer);\n if (id) clearInterval(id);\n };\n }, [showCamera, isInitialized, isProcessing, captureSilentPhoto, isScanningBarcode]);\n\n useEffect(() => {\n if (Platform.OS !== 'web' || !showCamera) return;\n \n let timeoutId: ReturnType<typeof setTimeout>;\n \n const findVideo = () => {\n let video: HTMLVideoElement | null = null;\n if (containerRef.current?.querySelector) {\n video = containerRef.current.querySelector('video');\n }\n if (!video) {\n video = document.querySelector('video');\n }\n \n if (video) {\n webVideoRef.current = video;\n setIsVideoFound(true);\n } else {\n timeoutId = setTimeout(findVideo, 500);\n }\n };\n \n findVideo();\n return () => clearTimeout(timeoutId);\n }, [showCamera]);\n\n const handleWebBarcodeScanned = useCallback((value: string) => {\n if (!isProcessingRef.current && value !== scannedBarcodeRef.current) {\n scannedBarcodeRef.current = value;\n onBarcodeScanned?.(value);\n }\n }, [onBarcodeScanned]);\n\n if (hasPermission === null) return <View style={[styles.container, style]} />;\n \n if (hasPermission === false) return (\n <View style={[styles.container, style]}>\n <Text style={styles.permissionMessage}>{t('camera.permissionRequired')}</Text>\n </View>\n );\n\n if (!device && showCamera) {\n // We delay the missing device error slightly to account for the camera mounting delay\n setTimeout(() => {\n if (!device) {\n onError?.({ \n message: \"No camera detected\", \n name: \"NotFoundError\", \n code: \"device/not-found\" \n });\n }\n }, 1000);\n return <View style={[styles.container, style]} />;\n }\n\n if (!device || !showCamera) return <View style={[styles.container, style]} />;\n\n return (\n <View ref={containerRef} style={[styles.container, style]}>\n <Camera\n ref={camera}\n style={StyleSheet.absoluteFill}\n device={device}\n resizeMode=\"cover\"\n isActive={showCamera && !isProcessing && appState === 'active'}\n photo={true}\n video={false}\n audio={false}\n onInitialized={onInitialized}\n onError={onCameraError}\n codeScanner={Platform.OS !== 'web' && isScanningBarcode ? codeScanner : undefined}\n />\n {Platform.OS === 'web' && isScanningBarcode && isVideoFound && (\n <WebBarcodeScanner\n videoRef={webVideoRef as React.RefObject<HTMLVideoElement>}\n onBarcodeScanned={handleWebBarcodeScanned}\n active={!isProcessingRef.current}\n />\n )}\n {overlayComponent}\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: { flex: 1, backgroundColor: 'black' },\n permissionMessage: { color: 'white', textAlign: 'center', margin: 20, fontSize: 16 },\n});"]}
1
+ {"version":3,"file":"EnhancedCameraView.js","sourceRoot":"","sources":["../../../src/components/EnhancedCameraView.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { View, StyleSheet, Text, Platform, AppState, AppStateStatus } from 'react-native';\nimport { Camera, CodeType, useCameraDevice, useCodeScanner } from 'react-native-vision-camera';\nimport VisionCameraModule from '../modules/camera/VisionCameraModule';\nimport { useI18n } from '../hooks/useI18n';\nimport { EnhancedCameraViewProps } from './OverLay/type';\nimport { WebBarcodeScanner } from './WebBarcodeScanner';\n\nexport interface ExtendedCameraProps extends Omit<EnhancedCameraViewProps, 'onError'> {\n isScanningBarcode?: boolean;\n onBarcodeScanned?: (data: string) => void;\n barcodeTypes?: CodeType[];\n onError?: (error: { message: string; name?: string; code?: string }) => void; \n}\n\nexport const EnhancedCameraView: React.FC<ExtendedCameraProps> = ({\n showCamera,\n cameraType: initialCameraType = 'front',\n style,\n onError,\n onSilentCapture,\n silentCaptureResult,\n isProcessing = false,\n overlayComponent,\n isScanningBarcode = false,\n onBarcodeScanned,\n barcodeTypes\n}) => {\n const { t } = useI18n();\n const camera = useRef<Camera>(null);\n const webVideoRef = useRef<HTMLVideoElement | null>(null);\n \n const isCapturingRef = useRef(false);\n const isProcessingRef = useRef(isProcessing);\n // Ref-tracked isAnalyzing so captureSilentPhoto always reads the latest\n // value — the prop-based closure goes stale on iOS between renders.\n const isAnalyzingRef = useRef(silentCaptureResult?.isAnalyzing ?? false);\n \n const [cameraType] = useState<'front' | 'back'>(initialCameraType);\n const [hasPermission, setHasPermission] = useState<boolean | null>(null);\n const [isInitialized, setIsInitialized] = useState(false);\n const [refreshCamera] = useState(false);\n \n const [isVideoFound, setIsVideoFound] = useState(false);\n const scannedBarcodeRef = useRef<string | null>(null);\n const containerRef = useRef<any>(null);\n\n const [appState, setAppState] = useState<AppStateStatus>(AppState.currentState);\n\n const device = useCameraDevice(cameraType);\n\n useEffect(() => { isProcessingRef.current = isProcessing; }, [isProcessing]);\n useEffect(() => { isAnalyzingRef.current = silentCaptureResult?.isAnalyzing ?? false; }, [silentCaptureResult?.isAnalyzing]);\n\n useEffect(() => {\n const subscription = AppState.addEventListener('change', nextAppState => {\n setAppState(nextAppState);\n });\n return () => {\n subscription.remove();\n };\n }, []);\n\n const checkPermissions = async () => {\n try {\n // getCameraPermissionStatus is synchronous — no await\n const cameraStatus = Camera.getCameraPermissionStatus();\n\n if (cameraStatus === 'granted') {\n setHasPermission(true);\n return;\n }\n\n if (cameraStatus === 'not-determined') {\n const result = await Camera.requestCameraPermission();\n if (result === 'granted') {\n setHasPermission(true);\n return;\n }\n }\n\n // 'denied' or 'restricted' — user must enable in Settings\n setHasPermission(false);\n onError?.({\n message: t('camera.permissionRequired'),\n name: 'NotAllowedError',\n code: 'permission-denied'\n });\n } catch (e: any) {\n setHasPermission(false);\n onError?.({\n message: e.message || t('camera.errorOccurred'),\n name: e.name || 'UnknownError',\n code: e.code || 'unknown'\n });\n }\n };\n\n useEffect(() => {\n if (showCamera) checkPermissions();\n }, [showCamera, refreshCamera]);\n\n const onInitialized = useCallback(() => setIsInitialized(true), []);\n\n const onCameraError = useCallback((error: any) => {\n console.warn(\"🎥 [Vision Camera Error Caught]:\", error);\n onError?.({ \n message: error.message || t('camera.errorOccurred'),\n code: error.code, // Contains specific RNVC codes (e.g., 'session/camera-in-use')\n name: error.name // Contains Web standard names (e.g., 'NotReadableError')\n });\n }, [onError, t]);\n\n const codeScanner = useCodeScanner({\n codeTypes: barcodeTypes || ['pdf-417', 'qr', 'code-128', 'code-39'],\n onCodeScanned: (codes) => {\n if (!isProcessingRef.current && isScanningBarcode) {\n for (const code of codes) {\n if (code.value && scannedBarcodeRef.current !== code.value) {\n scannedBarcodeRef.current = code.value;\n onBarcodeScanned?.(code.value);\n break;\n }\n }\n }\n }\n });\n\n // 1. ISOLATE THE CALLBACK: This breaks the dependency death spiral for all platforms.\n const onSilentCaptureRef = useRef(onSilentCapture);\n useEffect(() => {\n onSilentCaptureRef.current = onSilentCapture;\n }, [onSilentCapture]);\n\n // 2. THE CAPTURE FUNCTION: Zero dependencies. Immune to re-renders.\n const captureSilentPhoto = useCallback(async () => {\n // Web handles its own stream via WebBarcodeScanner; do not run native camera methods on web.\n if (Platform.OS === 'web') return; \n \n // We intentionally ignore `isInitialized`. If AVFoundation (iOS) or CameraX (Android) \n // aren't ready, the try/catch will handle it gracefully.\n if (!camera.current || isProcessingRef.current || isCapturingRef.current) return;\n if (isAnalyzingRef.current) return;\n\n try {\n isCapturingRef.current = true;\n \n const photo = await camera.current.takePhoto({\n enableShutterSound: false,\n flash: 'off',\n // iOS requires aggressive compression to prevent RAM crashes during interval captures\n ...(Platform.OS === 'ios' ? { photoCodec: 'jpeg', qualityPrioritization: 'speed' } : {})\n });\n\n // 3. CROSS-PLATFORM PATH NORMALIZATION\n let finalPath = photo.path;\n let captureSuccess = true;\n\n // 4. DEFENSIVE NATIVE MODULE EXECUTION\n if (VisionCameraModule && VisionCameraModule.processPhotoResult) {\n try {\n const moduleResult = await VisionCameraModule.processPhotoResult(photo);\n if (moduleResult) {\n captureSuccess = moduleResult.success ?? true;\n finalPath = moduleResult.path || photo.path;\n }\n } catch (nativeError) {\n console.warn(\"⚠️ Native module failed, falling back to raw device path\");\n }\n }\n\n onSilentCaptureRef.current?.({\n success: captureSuccess,\n path: finalPath,\n barcode: scannedBarcodeRef.current\n });\n \n } catch (error: any) {\n // 📸 Silent fail: The camera is busy, focusing, or warming up. Just wait for the next tick.\n console.log(`📸 Frame dropped (${Platform.OS}):`, error?.message);\n } finally {\n isCapturingRef.current = false;\n }\n }, []); // <-- EMPTY ARRAY GUARANTEES STABILITY\n\n // 5. THE UNBREAKABLE INTERVAL\n useEffect(() => {\n if (!showCamera || isProcessing) return;\n if (Platform.OS === 'web') return; // Let web handle its own interval logic\n\n let active = true;\n let id: ReturnType<typeof setInterval>;\n \n // Slight delay to let the UI mount before hammering the camera hardware\n const timer = setTimeout(() => {\n if (!active) return;\n const intervalDelay = isScanningBarcode ? 3000 : 1500;\n id = setInterval(captureSilentPhoto, intervalDelay);\n }, 1500);\n\n return () => {\n active = false;\n clearTimeout(timer);\n if (id) clearInterval(id);\n };\n }, [showCamera, isProcessing, isScanningBarcode, captureSilentPhoto]);\n\n\n useEffect(() => {\n if (!showCamera || !isInitialized || isProcessing) return;\n let active = true;\n let id: ReturnType<typeof setInterval>;\n \n const timer = setTimeout(() => {\n if (!active) return;\n const intervalDelay = isScanningBarcode ? 3000 : 1500;\n id = setInterval(captureSilentPhoto, intervalDelay);\n }, 1500);\n\n return () => {\n active = false;\n clearTimeout(timer);\n if (id) clearInterval(id);\n };\n }, [showCamera, isInitialized, isProcessing, captureSilentPhoto, isScanningBarcode]);\n\n useEffect(() => {\n if (Platform.OS !== 'web' || !showCamera) return;\n \n let timeoutId: ReturnType<typeof setTimeout>;\n \n const findVideo = () => {\n let video: HTMLVideoElement | null = null;\n if (containerRef.current?.querySelector) {\n video = containerRef.current.querySelector('video');\n }\n if (!video) {\n video = document.querySelector('video');\n }\n \n if (video) {\n webVideoRef.current = video;\n setIsVideoFound(true);\n } else {\n timeoutId = setTimeout(findVideo, 500);\n }\n };\n \n findVideo();\n return () => clearTimeout(timeoutId);\n }, [showCamera]);\n\n const handleWebBarcodeScanned = useCallback((value: string) => {\n if (!isProcessingRef.current && value !== scannedBarcodeRef.current) {\n scannedBarcodeRef.current = value;\n onBarcodeScanned?.(value);\n }\n }, [onBarcodeScanned]);\n\n if (hasPermission === null) return <View style={[styles.container, style]} />;\n \n if (hasPermission === false) return (\n <View style={[styles.container, style]}>\n <Text style={styles.permissionMessage}>{t('camera.permissionRequired')}</Text>\n </View>\n );\n\n if (!device && showCamera) {\n // We delay the missing device error slightly to account for the camera mounting delay\n setTimeout(() => {\n if (!device) {\n onError?.({ \n message: \"No camera detected\", \n name: \"NotFoundError\", \n code: \"device/not-found\" \n });\n }\n }, 1000);\n return <View style={[styles.container, style]} />;\n }\n\n if (!device || !showCamera) return <View style={[styles.container, style]} />;\n\n return (\n <View ref={containerRef} style={[styles.container, style]}>\n <Camera\n ref={camera}\n style={StyleSheet.absoluteFill}\n device={device}\n resizeMode=\"cover\"\n isActive={showCamera && !isProcessing && appState === 'active'}\n photo={true}\n video={false}\n audio={false}\n onInitialized={onInitialized}\n onError={onCameraError}\n codeScanner={Platform.OS !== 'web' && isScanningBarcode ? codeScanner : undefined}\n />\n {Platform.OS === 'web' && isScanningBarcode && isVideoFound && (\n <WebBarcodeScanner\n videoRef={webVideoRef as React.RefObject<HTMLVideoElement>}\n onBarcodeScanned={handleWebBarcodeScanned}\n active={!isProcessingRef.current}\n />\n )}\n {overlayComponent}\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: { flex: 1, backgroundColor: 'black' },\n permissionMessage: { color: 'white', textAlign: 'center', margin: 20, fontSize: 16 },\n});"]}
@@ -1 +1 @@
1
- {"version":3,"file":"IDCardCapture.d.ts","sourceRoot":"","sources":["../../../../src/components/KYCElements/IDCardCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAIpE,OAAO,EAAE,iBAAiB,EAAoI,MAAM,uBAAuB,CAAC;AAsB5L,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsCD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA0kCtD,CAAC"}
1
+ {"version":3,"file":"IDCardCapture.d.ts","sourceRoot":"","sources":["../../../../src/components/KYCElements/IDCardCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAIpE,OAAO,EAAE,iBAAiB,EAAoI,MAAM,uBAAuB,CAAC;AAsB5L,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsCD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAyoCtD,CAAC"}
@@ -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 } from 'react-native';
2
+ import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator, TouchableOpacity, useWindowDimensions, Alert } from 'react-native';
3
3
  import { showAlert } from '../../utils/platformAlert';
4
4
  import { EnhancedCameraView } from '../EnhancedCameraView';
5
5
  import { GovernmentDocumentTypeShorted, GovernmentDocumentTypeBackend } from '../../types/KYC.types';
@@ -70,6 +70,10 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
70
70
  const [scanPhase, setScanPhase] = useState('image');
71
71
  const [pendingCAImage, setPendingCAImage] = useState(null);
72
72
  const scannedBarcodeRef = useRef(null);
73
+ // Ref mirrors — prevent stale closure reads in handleSilentCapture on iOS
74
+ const isAnalyzingRef = useRef(false);
75
+ const isProcessingCaptureRef = useRef(false);
76
+ const scanPhaseRef = useRef('image');
73
77
  const [generatedCode, setGeneratedCode] = useState(null);
74
78
  const [isGeneratingCode, setIsGeneratingCode] = useState(false);
75
79
  const handleGenerateCode = async () => {
@@ -208,6 +212,10 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
208
212
  }
209
213
  }
210
214
  }, [value, currentSide]);
215
+ // Keep refs in sync with state so handleSilentCapture always reads current values
216
+ useEffect(() => { isAnalyzingRef.current = silentCaptureResult.isAnalyzing ?? false; }, [silentCaptureResult.isAnalyzing]);
217
+ useEffect(() => { isProcessingCaptureRef.current = isProcessingCapture; }, [isProcessingCapture]);
218
+ useEffect(() => { scanPhaseRef.current = scanPhase; }, [scanPhase]);
211
219
  const cameraConfig = useMemo(() => {
212
220
  let instructions = selectedDocumentType
213
221
  ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr)
@@ -355,7 +363,9 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
355
363
  const base64 = await pathToBase64(safeCroppedPath);
356
364
  const newImages = {
357
365
  ...capturedImages,
358
- [currentSide]: { dir: imagePathForUpload, file: base64, mrz: verified.mrz || "", templatePath: verified.templatePath },
366
+ // Store safeCroppedPath (guaranteed file:// on iOS) as dir so
367
+ // Image source={{ uri }} never receives a bare path on iOS.
368
+ [currentSide]: { dir: safeCroppedPath, file: base64, mrz: verified.mrz || "", templatePath: verified.templatePath },
359
369
  };
360
370
  setCapturedImages(newImages);
361
371
  if (verified.country && verified.documentType) {
@@ -466,14 +476,17 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
466
476
  }
467
477
  };
468
478
  const handleSilentCapture = async (result) => {
469
- if (isProcessingCapture)
479
+ if (isProcessingCaptureRef.current)
470
480
  return;
471
- if (scanPhase === 'barcode')
481
+ if (scanPhaseRef.current === 'barcode')
472
482
  return;
473
- if (silentCaptureResult.isAnalyzing)
483
+ if (isAnalyzingRef.current)
474
484
  return;
475
485
  if (result.success && result.path) {
476
486
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
487
+ const safeCapturePath = Platform.OS === 'ios' && !result.path.startsWith('file://')
488
+ ? `file://${result.path}`
489
+ : result.path;
477
490
  let templatePath = capturedImages[currentSide]?.templatePath || '';
478
491
  let templateBbox;
479
492
  let templateResponse;
@@ -482,11 +495,21 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
482
495
  return;
483
496
  }
484
497
  const issuingSubdivision = selectedDocumentType.region !== 'root' ? selectedDocumentType.region : undefined;
485
- const safeCapturePath = (Platform.OS === 'ios' && !result.path.startsWith('file://'))
486
- ? `file://${result.path}`
487
- : result.path;
498
+ // 🚨 WATCHDOG TIMER: If the iOS network drops and doesn't throw, this guarantees the UI unlocks after 20 seconds.
499
+ const watchdog = setTimeout(() => {
500
+ if (isAnalyzingRef.current) {
501
+ setSilentCaptureResult((prev) => ({
502
+ ...prev,
503
+ isAnalyzing: false,
504
+ success: false,
505
+ error: state.currentLanguage === 'en' ? 'Connection timeout. Please try again.' : 'Délai de connexion dépassé. Veuillez réessayer.'
506
+ }));
507
+ Alert.alert(state.currentLanguage === 'en' ? "Timeout" : "Délai dépassé", state.currentLanguage === 'en'
508
+ ? "The request took too long. Please verify your network and try again."
509
+ : "La requête a pris trop de temps. Veuillez vérifier votre connexion et réessayer.");
510
+ }
511
+ }, 20000);
488
512
  try {
489
- // 1. Check & Crop the Template visually first
490
513
  if (!templatePath) {
491
514
  const templateType = await checkTemplateType({
492
515
  path: safeCapturePath,
@@ -501,6 +524,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
501
524
  if (templateType.card_obb) {
502
525
  const obbConfidence = getObbConfidence(templateType.card_obb);
503
526
  if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
527
+ clearTimeout(watchdog);
504
528
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: t('kyc.idCardCapture.cardNotFullyInFrame') }));
505
529
  return;
506
530
  }
@@ -519,10 +543,17 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
519
543
  const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || 'TD1';
520
544
  verificationRes = await frontVerification({
521
545
  path: safeCapturePath,
522
- regionMapping: { authMethod: matchedAuthMethod ? [matchedAuthMethod] : [], mrzTypes: mrzType ? [mrzType] : [] },
546
+ regionMapping: {
547
+ authMethod: matchedAuthMethod ? [matchedAuthMethod] : [],
548
+ mrzTypes: mrzType ? [mrzType] : []
549
+ },
523
550
  selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType?.type] || '',
524
- code: countryData?.code || '', currentSide, templatePath, mrzType,
525
- issuingSubdivision: issuingSubdivision, clientSideBarcodeData: silentCaptureResult.barcode
551
+ code: countryData?.code || '',
552
+ currentSide,
553
+ templatePath,
554
+ mrzType,
555
+ issuingSubdivision: issuingSubdivision,
556
+ clientSideBarcodeData: silentCaptureResult.barcode
526
557
  }, env);
527
558
  }
528
559
  else {
@@ -530,13 +561,20 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
530
561
  const matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
531
562
  const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
532
563
  const frontTemplatePath = capturedImages['front']?.templatePath || '';
533
- // This validates if the back matches the front template!
534
564
  verificationRes = await backVerification({
535
565
  path: safeCapturePath,
536
- regionMapping: { authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : [], mrzTypes: backMrzType ? [backMrzType] : [] },
566
+ regionMapping: {
567
+ authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : [],
568
+ mrzTypes: backMrzType ? [backMrzType] : []
569
+ },
537
570
  selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type] || '',
538
- code: countryData?.code || '', currentSide, templatePath, mrzType: backMrzType, templateResponse,
539
- issuingSubdivision: issuingSubdivision, frontTemplatePath: frontTemplatePath
571
+ code: countryData?.code || '',
572
+ currentSide,
573
+ templatePath,
574
+ mrzType: backMrzType,
575
+ templateResponse,
576
+ issuingSubdivision: issuingSubdivision,
577
+ frontTemplatePath: frontTemplatePath
540
578
  }, env);
541
579
  }
542
580
  const bbox = verificationRes?.bbox || templateBbox;
@@ -546,7 +584,6 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
546
584
  const docTypeStr = selectedDocumentType.type;
547
585
  const isHIC = docTypeStr === 'health_insurance_card';
548
586
  const isValidScan = typeof barcodeData === 'string' && ((isHIC && barcodeData.length > 0) || barcodeData.length > 50);
549
- // If the barcode was caught extremely fast in the same frame
550
587
  if (isValidScan) {
551
588
  logger.log('[CA Barcode] Image captured, verified & barcode ALREADY scanned! Skipping Phase 2.');
552
589
  let parsedDictionary;
@@ -563,6 +600,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
563
600
  country: countryData?.code, documentType: selectedDocumentType.type,
564
601
  region: selectedDocumentType.region !== 'root' ? selectedDocumentType.region : undefined,
565
602
  };
603
+ clearTimeout(watchdog);
566
604
  setSilentCaptureResult(verifiedResult);
567
605
  if (bbox) {
568
606
  const safeBbox = bbox;
@@ -571,40 +609,53 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
571
609
  await autoCapture(safeCapturePath, verifiedResult);
572
610
  return;
573
611
  }
574
- // If validation passed but we still need the barcode, trigger the UI phase!
575
612
  logger.log('[CA Barcode] Image captured & template VERIFIED. Switching to Barcode phase.');
613
+ clearTimeout(watchdog);
576
614
  setPendingCAImage({ path: safeCapturePath, templateResponse, templatePath, bbox });
577
615
  setScanPhase('barcode');
578
616
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
579
617
  return;
580
618
  }
581
- // 4. NORMAL COMPLETION (For standard MRZ / Passport / Non-barcode documents)
582
619
  const verifiedResult = {
583
- path: safeCapturePath, templatePath, bbox, success: true, mrz: verifiedMrz, isAnalyzing: false,
584
- country: countryData?.code, documentType: selectedDocumentType.type, region: issuingSubdivision
620
+ path: safeCapturePath, templatePath, bbox, success: true, mrz: verifiedMrz,
621
+ isAnalyzing: false, country: countryData?.code, documentType: selectedDocumentType.type,
622
+ region: issuingSubdivision
585
623
  };
624
+ clearTimeout(watchdog);
586
625
  setSilentCaptureResult(verifiedResult);
587
626
  if (bbox)
588
627
  setBboxBySide(prev => ({ ...prev, [currentSide]: bbox }));
589
628
  await autoCapture(safeCapturePath, verifiedResult);
590
629
  }
591
630
  catch (error) {
631
+ clearTimeout(watchdog);
592
632
  console.log("Backend Verification Error:", error);
593
- const rawMessage = (error?.message || '').toLowerCase();
633
+ const isNetworkError = !error.response ||
634
+ error.message === 'Network Error' ||
635
+ error.code === 'ECONNABORTED' ||
636
+ error.message?.includes('Network');
594
637
  let userFriendlyMessage = 'Verification failed. Please try again.';
595
- if (rawMessage.includes('impossible') || rawMessage.includes('unreadable')) {
596
- userFriendlyMessage = 'Document unreadable. Please hold the camera steady in good light.';
597
- }
598
- else if (rawMessage.includes('country mismatch') || rawMessage.includes('pays sélectionné')) {
599
- userFriendlyMessage = 'The document does not match your selected country.';
638
+ if (isNetworkError) {
639
+ userFriendlyMessage = state.currentLanguage === 'en'
640
+ ? 'Cannot connect to verification server. Please check your internet connection.'
641
+ : 'Impossible de se connecter au serveur de vérification. Veuillez vérifier votre connexion internet.';
642
+ Alert.alert(state.currentLanguage === 'en' ? "Connection Failed" : "Échec de connexion", userFriendlyMessage);
600
643
  }
601
- else if (rawMessage.includes('card_not_fully_in_frame')) {
602
- userFriendlyMessage = 'Move closer to the document and ensure all corners are visible.';
603
- }
604
- else if (rawMessage.includes('ne correspond pas au recto')) {
605
- userFriendlyMessage = 'The back of the document does not match the front. You must scan the same ID.';
644
+ else {
645
+ const rawMessage = (error?.message || '').toLowerCase();
646
+ if (rawMessage.includes('impossible') || rawMessage.includes('unreadable')) {
647
+ userFriendlyMessage = 'Document unreadable. Please hold the camera steady in good light.';
648
+ }
649
+ else if (rawMessage.includes('country mismatch') || rawMessage.includes('pays sélectionné')) {
650
+ userFriendlyMessage = 'The document does not match your selected country.';
651
+ }
652
+ else if (rawMessage.includes('card_not_fully_in_frame')) {
653
+ userFriendlyMessage = 'Move closer to the document and ensure all corners are visible.';
654
+ }
655
+ else if (rawMessage.includes('ne correspond pas au recto')) {
656
+ userFriendlyMessage = 'The back of the document does not match the front. You must scan the same ID.';
657
+ }
606
658
  }
607
- // The error is thrown here, completely blocking the switch to 'barcode' phase
608
659
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: userFriendlyMessage }));
609
660
  }
610
661
  }