@sanctum-key/react-native-sdk 1.0.33 → 1.0.34
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/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +25 -18
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +82 -33
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +0 -7
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +48 -119
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/package.json +1 -1
- package/src/components/EnhancedCameraView.tsx +29 -22
- package/src/components/KYCElements/IDCardCapture.tsx +113 -53
- package/src/modules/api/KYCService.ts +357 -534
package/build/package.json
CHANGED
|
@@ -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,
|
|
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,CAiP5D,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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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?.({
|
|
@@ -87,7 +94,7 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
|
|
|
87
94
|
const captureSilentPhoto = useCallback(async () => {
|
|
88
95
|
if (!camera.current || !isInitialized || isProcessingRef.current || isCapturingRef.current)
|
|
89
96
|
return;
|
|
90
|
-
if (
|
|
97
|
+
if (isAnalyzingRef.current)
|
|
91
98
|
return;
|
|
92
99
|
try {
|
|
93
100
|
isCapturingRef.current = true;
|
|
@@ -106,8 +113,8 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
|
|
|
106
113
|
barcode: scannedBarcodeRef.current
|
|
107
114
|
});
|
|
108
115
|
}
|
|
109
|
-
catch {
|
|
110
|
-
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.log('Silent capture skipped/failed:', error?.message);
|
|
111
118
|
}
|
|
112
119
|
finally {
|
|
113
120
|
isCapturingRef.current = false;
|
|
@@ -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 const captureSilentPhoto = useCallback(async () => {\n if (!camera.current || !isInitialized || isProcessingRef.current || isCapturingRef.current) return;\n if (isAnalyzingRef.current) 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 const result = await VisionCameraModule.processPhotoResult(photo);\n onSilentCapture?.({ \n ...result, \n path: result.path || photo.path,\n barcode: scannedBarcodeRef.current \n });\n } catch (error: any) { \n console.log('Silent capture skipped/failed:', error?.message);\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 +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,
|
|
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,CAsoCtD,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
|
-
|
|
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,11 +476,11 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
466
476
|
}
|
|
467
477
|
};
|
|
468
478
|
const handleSilentCapture = async (result) => {
|
|
469
|
-
if (
|
|
479
|
+
if (isProcessingCaptureRef.current)
|
|
470
480
|
return;
|
|
471
|
-
if (
|
|
481
|
+
if (scanPhaseRef.current === 'barcode')
|
|
472
482
|
return;
|
|
473
|
-
if (
|
|
483
|
+
if (isAnalyzingRef.current)
|
|
474
484
|
return;
|
|
475
485
|
if (result.success && result.path) {
|
|
476
486
|
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
|
|
@@ -482,11 +492,22 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
482
492
|
return;
|
|
483
493
|
}
|
|
484
494
|
const issuingSubdivision = selectedDocumentType.region !== 'root' ? selectedDocumentType.region : undefined;
|
|
485
|
-
const safeCapturePath = (Platform.OS === 'ios' && !result.path.startsWith('file://'))
|
|
486
|
-
|
|
487
|
-
|
|
495
|
+
const safeCapturePath = (Platform.OS === 'ios' && !result.path.startsWith('file://')) ? `file://${result.path}` : result.path;
|
|
496
|
+
// 🚨 WATCHDOG TIMER: If the iOS network drops and doesn't throw, this guarantees the UI unlocks after 20 seconds.
|
|
497
|
+
const watchdog = setTimeout(() => {
|
|
498
|
+
if (isAnalyzingRef.current) {
|
|
499
|
+
setSilentCaptureResult((prev) => ({
|
|
500
|
+
...prev,
|
|
501
|
+
isAnalyzing: false,
|
|
502
|
+
success: false,
|
|
503
|
+
error: state.currentLanguage === 'en' ? 'Connection timeout. Please try again.' : 'Délai de connexion dépassé. Veuillez réessayer.'
|
|
504
|
+
}));
|
|
505
|
+
Alert.alert(state.currentLanguage === 'en' ? "Timeout" : "Délai dépassé", state.currentLanguage === 'en'
|
|
506
|
+
? "The request took too long. Please verify your network and try again."
|
|
507
|
+
: "La requête a pris trop de temps. Veuillez vérifier votre connexion et réessayer.");
|
|
508
|
+
}
|
|
509
|
+
}, 20000);
|
|
488
510
|
try {
|
|
489
|
-
// 1. Check & Crop the Template visually first
|
|
490
511
|
if (!templatePath) {
|
|
491
512
|
const templateType = await checkTemplateType({
|
|
492
513
|
path: safeCapturePath,
|
|
@@ -501,6 +522,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
501
522
|
if (templateType.card_obb) {
|
|
502
523
|
const obbConfidence = getObbConfidence(templateType.card_obb);
|
|
503
524
|
if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
|
|
525
|
+
clearTimeout(watchdog);
|
|
504
526
|
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: t('kyc.idCardCapture.cardNotFullyInFrame') }));
|
|
505
527
|
return;
|
|
506
528
|
}
|
|
@@ -519,10 +541,17 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
519
541
|
const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || 'TD1';
|
|
520
542
|
verificationRes = await frontVerification({
|
|
521
543
|
path: safeCapturePath,
|
|
522
|
-
regionMapping: {
|
|
544
|
+
regionMapping: {
|
|
545
|
+
authMethod: matchedAuthMethod ? [matchedAuthMethod] : [],
|
|
546
|
+
mrzTypes: mrzType ? [mrzType] : []
|
|
547
|
+
},
|
|
523
548
|
selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType?.type] || '',
|
|
524
|
-
code: countryData?.code || '',
|
|
525
|
-
|
|
549
|
+
code: countryData?.code || '',
|
|
550
|
+
currentSide,
|
|
551
|
+
templatePath,
|
|
552
|
+
mrzType,
|
|
553
|
+
issuingSubdivision: issuingSubdivision,
|
|
554
|
+
clientSideBarcodeData: silentCaptureResult.barcode
|
|
526
555
|
}, env);
|
|
527
556
|
}
|
|
528
557
|
else {
|
|
@@ -530,13 +559,20 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
530
559
|
const matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
|
|
531
560
|
const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
|
|
532
561
|
const frontTemplatePath = capturedImages['front']?.templatePath || '';
|
|
533
|
-
// This validates if the back matches the front template!
|
|
534
562
|
verificationRes = await backVerification({
|
|
535
563
|
path: safeCapturePath,
|
|
536
|
-
regionMapping: {
|
|
564
|
+
regionMapping: {
|
|
565
|
+
authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : [],
|
|
566
|
+
mrzTypes: backMrzType ? [backMrzType] : []
|
|
567
|
+
},
|
|
537
568
|
selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type] || '',
|
|
538
|
-
code: countryData?.code || '',
|
|
539
|
-
|
|
569
|
+
code: countryData?.code || '',
|
|
570
|
+
currentSide,
|
|
571
|
+
templatePath,
|
|
572
|
+
mrzType: backMrzType,
|
|
573
|
+
templateResponse,
|
|
574
|
+
issuingSubdivision: issuingSubdivision,
|
|
575
|
+
frontTemplatePath: frontTemplatePath
|
|
540
576
|
}, env);
|
|
541
577
|
}
|
|
542
578
|
const bbox = verificationRes?.bbox || templateBbox;
|
|
@@ -546,7 +582,6 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
546
582
|
const docTypeStr = selectedDocumentType.type;
|
|
547
583
|
const isHIC = docTypeStr === 'health_insurance_card';
|
|
548
584
|
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
585
|
if (isValidScan) {
|
|
551
586
|
logger.log('[CA Barcode] Image captured, verified & barcode ALREADY scanned! Skipping Phase 2.');
|
|
552
587
|
let parsedDictionary;
|
|
@@ -563,6 +598,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
563
598
|
country: countryData?.code, documentType: selectedDocumentType.type,
|
|
564
599
|
region: selectedDocumentType.region !== 'root' ? selectedDocumentType.region : undefined,
|
|
565
600
|
};
|
|
601
|
+
clearTimeout(watchdog);
|
|
566
602
|
setSilentCaptureResult(verifiedResult);
|
|
567
603
|
if (bbox) {
|
|
568
604
|
const safeBbox = bbox;
|
|
@@ -571,40 +607,53 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
571
607
|
await autoCapture(safeCapturePath, verifiedResult);
|
|
572
608
|
return;
|
|
573
609
|
}
|
|
574
|
-
// If validation passed but we still need the barcode, trigger the UI phase!
|
|
575
610
|
logger.log('[CA Barcode] Image captured & template VERIFIED. Switching to Barcode phase.');
|
|
611
|
+
clearTimeout(watchdog);
|
|
576
612
|
setPendingCAImage({ path: safeCapturePath, templateResponse, templatePath, bbox });
|
|
577
613
|
setScanPhase('barcode');
|
|
578
614
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
|
|
579
615
|
return;
|
|
580
616
|
}
|
|
581
|
-
// 4. NORMAL COMPLETION (For standard MRZ / Passport / Non-barcode documents)
|
|
582
617
|
const verifiedResult = {
|
|
583
|
-
path: safeCapturePath, templatePath, bbox, success: true, mrz: verifiedMrz,
|
|
584
|
-
country: countryData?.code, documentType: selectedDocumentType.type,
|
|
618
|
+
path: safeCapturePath, templatePath, bbox, success: true, mrz: verifiedMrz,
|
|
619
|
+
isAnalyzing: false, country: countryData?.code, documentType: selectedDocumentType.type,
|
|
620
|
+
region: issuingSubdivision
|
|
585
621
|
};
|
|
622
|
+
clearTimeout(watchdog);
|
|
586
623
|
setSilentCaptureResult(verifiedResult);
|
|
587
624
|
if (bbox)
|
|
588
625
|
setBboxBySide(prev => ({ ...prev, [currentSide]: bbox }));
|
|
589
626
|
await autoCapture(safeCapturePath, verifiedResult);
|
|
590
627
|
}
|
|
591
628
|
catch (error) {
|
|
629
|
+
clearTimeout(watchdog);
|
|
592
630
|
console.log("Backend Verification Error:", error);
|
|
593
|
-
const
|
|
631
|
+
const isNetworkError = !error.response ||
|
|
632
|
+
error.message === 'Network Error' ||
|
|
633
|
+
error.code === 'ECONNABORTED' ||
|
|
634
|
+
error.message?.includes('Network');
|
|
594
635
|
let userFriendlyMessage = 'Verification failed. Please try again.';
|
|
595
|
-
if (
|
|
596
|
-
userFriendlyMessage =
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
636
|
+
if (isNetworkError) {
|
|
637
|
+
userFriendlyMessage = state.currentLanguage === 'en'
|
|
638
|
+
? 'Cannot connect to verification server. Please check your internet connection.'
|
|
639
|
+
: 'Impossible de se connecter au serveur de vérification. Veuillez vérifier votre connexion internet.';
|
|
640
|
+
Alert.alert(state.currentLanguage === 'en' ? "Connection Failed" : "Échec de connexion", userFriendlyMessage);
|
|
600
641
|
}
|
|
601
|
-
else
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
642
|
+
else {
|
|
643
|
+
const rawMessage = (error?.message || '').toLowerCase();
|
|
644
|
+
if (rawMessage.includes('impossible') || rawMessage.includes('unreadable')) {
|
|
645
|
+
userFriendlyMessage = 'Document unreadable. Please hold the camera steady in good light.';
|
|
646
|
+
}
|
|
647
|
+
else if (rawMessage.includes('country mismatch') || rawMessage.includes('pays sélectionné')) {
|
|
648
|
+
userFriendlyMessage = 'The document does not match your selected country.';
|
|
649
|
+
}
|
|
650
|
+
else if (rawMessage.includes('card_not_fully_in_frame')) {
|
|
651
|
+
userFriendlyMessage = 'Move closer to the document and ensure all corners are visible.';
|
|
652
|
+
}
|
|
653
|
+
else if (rawMessage.includes('ne correspond pas au recto')) {
|
|
654
|
+
userFriendlyMessage = 'The back of the document does not match the front. You must scan the same ID.';
|
|
655
|
+
}
|
|
606
656
|
}
|
|
607
|
-
// The error is thrown here, completely blocking the switch to 'barcode' phase
|
|
608
657
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: userFriendlyMessage }));
|
|
609
658
|
}
|
|
610
659
|
}
|