@sanctum-key/react-native-sdk 1.0.27 → 1.0.29

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.
Files changed (34) hide show
  1. package/build/package.json +1 -1
  2. package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
  3. package/build/src/components/EnhancedCameraView.js +45 -20
  4. package/build/src/components/EnhancedCameraView.js.map +1 -1
  5. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  6. package/build/src/components/KYCElements/IDCardCapture.js +186 -50
  7. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  8. package/build/src/components/OverLay/type.d.ts +3 -1
  9. package/build/src/components/OverLay/type.d.ts.map +1 -1
  10. package/build/src/components/OverLay/type.js.map +1 -1
  11. package/build/src/components/TemplateKYCExample.d.ts +2 -1
  12. package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
  13. package/build/src/components/TemplateKYCExample.js +2 -2
  14. package/build/src/components/TemplateKYCExample.js.map +1 -1
  15. package/build/src/components/TemplateKYCFlowRefactored.d.ts +1 -0
  16. package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  17. package/build/src/components/TemplateKYCFlowRefactored.js +2 -2
  18. package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
  19. package/build/src/hooks/useTemplateKYCFlow.d.ts +1 -0
  20. package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  21. package/build/src/hooks/useTemplateKYCFlow.js +31 -17
  22. package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
  23. package/build/src/modules/api/KYCService.d.ts +8 -0
  24. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  25. package/build/src/modules/api/KYCService.js +30 -0
  26. package/build/src/modules/api/KYCService.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/components/EnhancedCameraView.tsx +60 -27
  29. package/src/components/KYCElements/IDCardCapture.tsx +236 -80
  30. package/src/components/OverLay/type.ts +5 -1
  31. package/src/components/TemplateKYCExample.tsx +7 -3
  32. package/src/components/TemplateKYCFlowRefactored.tsx +3 -0
  33. package/src/hooks/useTemplateKYCFlow.tsx +65 -34
  34. package/src/modules/api/KYCService.ts +41 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanctum-key/react-native-sdk",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
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,uBAAuB;IAClE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAuL5D,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,uBAAuB;IAClE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAwN5D,CAAC"}
@@ -14,7 +14,6 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
14
14
  const [hasPermission, setHasPermission] = useState(null);
15
15
  const [isInitialized, setIsInitialized] = useState(false);
16
16
  const [refreshCamera] = useState(false);
17
- // 🚨 NEW STATE: Tells React when it is safe to mount the Web Scanner
18
17
  const [isVideoFound, setIsVideoFound] = useState(false);
19
18
  const scannedBarcodeRef = useRef(null);
20
19
  const containerRef = useRef(null);
@@ -29,15 +28,23 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
29
28
  const granted = await VisionCameraModule.requestAllPermissions();
30
29
  if (!granted) {
31
30
  setHasPermission(false);
32
- onError?.({ message: t('camera.permissionRequired') });
31
+ onError?.({
32
+ message: t('camera.permissionRequired'),
33
+ name: 'NotAllowedError', // Emulate web standard for easy catching
34
+ code: 'permission-denied'
35
+ });
33
36
  return;
34
37
  }
35
38
  }
36
39
  setHasPermission(true);
37
40
  }
38
- catch {
41
+ catch (e) {
39
42
  setHasPermission(false);
40
- onError?.({ message: t('camera.errorOccurred') });
43
+ onError?.({
44
+ message: e.message || t('camera.errorOccurred'),
45
+ name: e.name || 'UnknownError',
46
+ code: e.code || 'unknown'
47
+ });
41
48
  }
42
49
  };
43
50
  useEffect(() => {
@@ -45,8 +52,14 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
45
52
  checkPermissions();
46
53
  }, [showCamera, refreshCamera]);
47
54
  const onInitialized = useCallback(() => setIsInitialized(true), []);
55
+ // 🚨 UPDATED: Catch Vision Camera deep errors (e.g. session-in-use) and pass them up
48
56
  const onCameraError = useCallback((error) => {
49
- onError?.({ message: error.message || t('camera.errorOccurred') });
57
+ console.warn("🎥 [Vision Camera Error Caught]:", error);
58
+ onError?.({
59
+ message: error.message || t('camera.errorOccurred'),
60
+ code: error.code, // Contains specific RNVC codes (e.g., 'session/camera-in-use')
61
+ name: error.name // Contains Web standard names (e.g., 'NotReadableError')
62
+ });
50
63
  }, [onError, t]);
51
64
  const codeScanner = useCodeScanner({
52
65
  codeTypes: barcodeTypes || ['pdf-417', 'qr', 'code-128', 'code-39'],
@@ -69,11 +82,19 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
69
82
  return;
70
83
  try {
71
84
  isCapturingRef.current = true;
72
- const photo = await camera.current.takePhoto({ enableShutterSound: false, flash: 'off' });
85
+ const photo = await camera.current.takePhoto({
86
+ enableShutterSound: false,
87
+ flash: 'off'
88
+ });
73
89
  const result = await VisionCameraModule.processPhotoResult(photo);
74
- onSilentCapture?.({ ...result, path: result.path || photo.path, barcode: scannedBarcodeRef.current });
90
+ onSilentCapture?.({
91
+ ...result,
92
+ path: result.path || photo.path,
93
+ barcode: scannedBarcodeRef.current
94
+ });
75
95
  }
76
96
  catch {
97
+ // Intentionally swallow silent capture errors so we don't spam the UI
77
98
  }
78
99
  finally {
79
100
  isCapturingRef.current = false;
@@ -97,14 +118,11 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
97
118
  clearInterval(id);
98
119
  };
99
120
  }, [showCamera, isInitialized, isProcessing, captureSilentPhoto, isScanningBarcode]);
100
- // Web: Aggressive Video Element Hunter
101
121
  useEffect(() => {
102
- // 🚨 Kick off the search immediately if we are on the web and the camera should be showing
103
122
  if (Platform.OS !== 'web' || !showCamera)
104
123
  return;
105
124
  let timeoutId;
106
125
  const findVideo = () => {
107
- console.log('🔍 [WEB ENGINE] Searching for <video> element in DOM...');
108
126
  let video = null;
109
127
  if (containerRef.current?.querySelector) {
110
128
  video = containerRef.current.querySelector('video');
@@ -113,23 +131,17 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
113
131
  video = document.querySelector('video');
114
132
  }
115
133
  if (video) {
116
- console.log('✅ [WEB ENGINE] <video> element locked! Booting ZXing Scanner.');
117
134
  webVideoRef.current = video;
118
- setIsVideoFound(true); // This tells React to mount the scanner!
135
+ setIsVideoFound(true);
119
136
  }
120
137
  else {
121
- console.log('⏳ [WEB ENGINE] <video> not found yet, retrying in 500ms...');
122
138
  timeoutId = setTimeout(findVideo, 500);
123
139
  }
124
140
  };
125
- // Start hunting immediately, do NOT wait for isInitialized
126
141
  findVideo();
127
142
  return () => clearTimeout(timeoutId);
128
143
  }, [showCamera]);
129
- // 🚨 FIX: Memoized Web Callback to prevent infinite re-rendering loop
130
144
  const handleWebBarcodeScanned = useCallback((value) => {
131
- console.log(`🎉 [WEB SCANNER] SUCCESS! Decoded value:`, value);
132
- alert(`Decoded: ${value.substring(0, 50)}...`);
133
145
  if (!isProcessingRef.current && value !== scannedBarcodeRef.current) {
134
146
  scannedBarcodeRef.current = value;
135
147
  onBarcodeScanned?.(value);
@@ -138,14 +150,27 @@ export const EnhancedCameraView = ({ showCamera, cameraType: initialCameraType =
138
150
  if (hasPermission === null)
139
151
  return <View style={[styles.container, style]}/>;
140
152
  if (hasPermission === false)
141
- return <View style={[styles.container, style]}><Text style={styles.permissionMessage}>{t('camera.permissionRequired')}</Text></View>;
153
+ return (<View style={[styles.container, style]}>
154
+ <Text style={styles.permissionMessage}>{t('camera.permissionRequired')}</Text>
155
+ </View>);
156
+ if (!device && showCamera) {
157
+ // We delay the missing device error slightly to account for the camera mounting delay
158
+ setTimeout(() => {
159
+ if (!device) {
160
+ onError?.({
161
+ message: "No camera detected",
162
+ name: "NotFoundError",
163
+ code: "device/not-found"
164
+ });
165
+ }
166
+ }, 1000);
167
+ return <View style={[styles.container, style]}/>;
168
+ }
142
169
  if (!device || !showCamera)
143
170
  return <View style={[styles.container, style]}/>;
144
171
  return (<View ref={containerRef} style={[styles.container, style]}>
145
172
  <Camera ref={camera} style={StyleSheet.absoluteFill} device={device} resizeMode="cover" isActive={showCamera && !isProcessing} photo={true} video={false} audio={false} onInitialized={onInitialized} onError={onCameraError} codeScanner={Platform.OS !== 'web' && isScanningBarcode ? codeScanner : undefined}/>
146
-
147
173
  {Platform.OS === 'web' && isScanningBarcode && isVideoFound && (<WebBarcodeScanner videoRef={webVideoRef} onBarcodeScanned={handleWebBarcodeScanned} active={!isProcessingRef.current}/>)}
148
-
149
174
  {overlayComponent}
150
175
  </View>);
151
176
  };
@@ -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 } 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 EnhancedCameraViewProps {\n isScanningBarcode?: boolean;\n onBarcodeScanned?: (data: string) => void;\n barcodeTypes?: CodeType[];\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 \n const webVideoRef = useRef<HTMLVideoElement | null>(null);\n const isCapturingRef = useRef(false);\n const isProcessingRef = useRef(isProcessing);\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 // 🚨 NEW STATE: Tells React when it is safe to mount the Web Scanner\n const [isVideoFound, setIsVideoFound] = useState(false);\n\n const scannedBarcodeRef = useRef<string | null>(null);\n const containerRef = useRef<any>(null);\n const device = useCameraDevice(cameraType);\n\n useEffect(() => {\n isProcessingRef.current = isProcessing;\n }, [isProcessing]);\n\n const checkPermissions = async () => {\n try {\n const hasAllPermissions = await VisionCameraModule.hasAllPermissions();\n if (!hasAllPermissions) {\n const granted = await VisionCameraModule.requestAllPermissions();\n if (!granted) {\n setHasPermission(false);\n onError?.({ message: t('camera.permissionRequired') });\n return;\n }\n }\n setHasPermission(true);\n } catch {\n setHasPermission(false);\n onError?.({ message: t('camera.errorOccurred') });\n }\n };\n\n useEffect(() => {\n if (showCamera) checkPermissions();\n }, [showCamera, refreshCamera]);\n\n const onInitialized = useCallback(() => setIsInitialized(true), []);\n const onCameraError = useCallback((error: any) => {\n onError?.({ message: error.message || t('camera.errorOccurred') });\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 try {\n isCapturingRef.current = true;\n const photo = await camera.current.takePhoto({ enableShutterSound: false, flash: 'off' });\n const result = await VisionCameraModule.processPhotoResult(photo);\n onSilentCapture?.({ ...result, path: result.path || photo.path, barcode: scannedBarcodeRef.current });\n } catch {\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 const timer = setTimeout(() => {\n if (!active) return;\n const intervalDelay = isScanningBarcode ? 3000 : 1500;\n id = setInterval(captureSilentPhoto, intervalDelay);\n }, 1500);\n return () => {\n active = false;\n clearTimeout(timer);\n if (id) clearInterval(id);\n };\n }, [showCamera, isInitialized, isProcessing, captureSilentPhoto, isScanningBarcode]);\n\n // Web: Aggressive Video Element Hunter\n useEffect(() => {\n // 🚨 Kick off the search immediately if we are on the web and the camera should be showing\n if (Platform.OS !== 'web' || !showCamera) return;\n \n let timeoutId: ReturnType<typeof setTimeout>;\n\n const findVideo = () => {\n console.log('🔍 [WEB ENGINE] Searching for <video> element in DOM...');\n \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 console.log('✅ [WEB ENGINE] <video> element locked! Booting ZXing Scanner.');\n webVideoRef.current = video;\n setIsVideoFound(true); // This tells React to mount the scanner!\n } else {\n console.log('⏳ [WEB ENGINE] <video> not found yet, retrying in 500ms...');\n timeoutId = setTimeout(findVideo, 500);\n }\n };\n \n // Start hunting immediately, do NOT wait for isInitialized\n findVideo();\n\n return () => clearTimeout(timeoutId);\n }, [showCamera]);\n\n // 🚨 FIX: Memoized Web Callback to prevent infinite re-rendering loop\n const handleWebBarcodeScanned = useCallback((value: string) => {\n console.log(`🎉 [WEB SCANNER] SUCCESS! Decoded value:`, value);\n alert(`Decoded: ${value.substring(0, 50)}...`);\n \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 if (hasPermission === false) return <View style={[styles.container, style]}><Text style={styles.permissionMessage}>{t('camera.permissionRequired')}</Text></View>;\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}\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 \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 \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 } 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 EnhancedCameraViewProps {\n isScanningBarcode?: boolean;\n onBarcodeScanned?: (data: string) => void;\n barcodeTypes?: CodeType[];\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 device = useCameraDevice(cameraType);\n\n useEffect(() => {\n isProcessingRef.current = isProcessing;\n }, [isProcessing]);\n\n const checkPermissions = async () => {\n try {\n const hasAllPermissions = await VisionCameraModule.hasAllPermissions();\n if (!hasAllPermissions) {\n const granted = await VisionCameraModule.requestAllPermissions();\n if (!granted) {\n setHasPermission(false);\n onError?.({ \n message: t('camera.permissionRequired'),\n name: 'NotAllowedError', // Emulate web standard for easy catching\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 \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}\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;AAUD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAg/BtD,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;AAUD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA6lCtD,CAAC"}
@@ -47,6 +47,58 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
47
47
  const [scanPhase, setScanPhase] = useState('image');
48
48
  const [pendingCAImage, setPendingCAImage] = useState(null);
49
49
  const scannedBarcodeRef = useRef(null);
50
+ // 🚨 NEW: State for the expiring link code
51
+ const [generatedCode, setGeneratedCode] = useState(null);
52
+ const [isGeneratingCode, setIsGeneratingCode] = useState(false);
53
+ const [codeTimeLeft, setCodeTimeLeft] = useState(0);
54
+ // 🚨 NEW: Countdown Timer Effect
55
+ useEffect(() => {
56
+ if (codeTimeLeft <= 0) {
57
+ if (generatedCode)
58
+ setGeneratedCode(null); // Clear the code when timer hits 0
59
+ return;
60
+ }
61
+ const timer = setInterval(() => {
62
+ setCodeTimeLeft((prev) => prev - 1);
63
+ }, 1000);
64
+ return () => clearInterval(timer);
65
+ }, [codeTimeLeft, generatedCode]);
66
+ // 🚨 NEW: Formatting helper
67
+ const formatTime = (seconds) => {
68
+ const m = Math.floor(seconds / 60);
69
+ const s = seconds % 60;
70
+ return `${m}:${s.toString().padStart(2, '0')}`;
71
+ };
72
+ const handleGenerateCode = async () => {
73
+ if (isGeneratingCode)
74
+ return;
75
+ setIsGeneratingCode(true);
76
+ try {
77
+ const apiKey = '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';
78
+ if (!apiKey) {
79
+ showAlert('Error', 'API Key is missing.');
80
+ setIsGeneratingCode(false);
81
+ return;
82
+ }
83
+ // 🚨 Pass the apiKey as the strictly required second parameter
84
+ const res = await kycService.generateReferralCode({ session_id: state.session.session_id }, apiKey);
85
+ // Extract the code from the response
86
+ const newCode = res.code || res.referral_code || res?.data?.code || res?.data?.referral_code;
87
+ if (newCode) {
88
+ setGeneratedCode(newCode);
89
+ setCodeTimeLeft(300); // 300 seconds = 5 minutes
90
+ }
91
+ else {
92
+ showAlert('Error', 'Failed to parse code from server response.');
93
+ }
94
+ }
95
+ catch (error) {
96
+ showAlert('Error', error.message || 'Failed to generate code');
97
+ }
98
+ finally {
99
+ setIsGeneratingCode(false);
100
+ }
101
+ };
50
102
  useEffect(() => {
51
103
  if (typeof window !== 'undefined' && window.location)
52
104
  setCurrentUrl(window.location.href);
@@ -130,7 +182,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
130
182
  setTimeout(() => {
131
183
  setCameraKey(prev => prev + 1);
132
184
  setIsRebootingCamera(false);
133
- }, 500);
185
+ }, 1000);
134
186
  };
135
187
  const toggleCameraLens = () => {
136
188
  setCameraType(prev => prev === 'back' ? 'front' : 'back');
@@ -557,7 +609,35 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
557
609
  }
558
610
  }, [scanPhase, pendingCAImage, silentCaptureResult.barcode]);
559
611
  const handleError = (event) => {
560
- showAlert('Erreur', event.message);
612
+ logger.error("Camera Error:", event);
613
+ // Normalize the error to lowercase for easy checking
614
+ const errString = `${event.message} ${event.name} ${event.code}`.toLowerCase();
615
+ let friendlyMessage = state.currentLanguage === 'en'
616
+ ? 'An unknown camera error occurred.'
617
+ : 'Une erreur de caméra inconnue est survenue.';
618
+ // 1. Detect Hardware Lock (Camera in use by Zoom, Teams, or another app)
619
+ if (errString.includes('notreadableerror') ||
620
+ errString.includes('trackstarterror') ||
621
+ errString.includes('in use') ||
622
+ errString.includes('concurrent') ||
623
+ errString.includes('camera unavailable')) {
624
+ friendlyMessage = state.currentLanguage === 'en'
625
+ ? 'Your camera is currently being used by another application (like Zoom or Teams). Please close the other app and try again.'
626
+ : 'Votre caméra est actuellement utilisée par une autre application. Veuillez fermer l\'autre application et réessayer.';
627
+ }
628
+ // 2. Detect Permission Denied
629
+ else if (errString.includes('notallowederror') || errString.includes('permission')) {
630
+ friendlyMessage = state.currentLanguage === 'en'
631
+ ? 'Camera permission denied. Please allow camera access in your settings to proceed.'
632
+ : 'Autorisation de la caméra refusée. Veuillez autoriser l\'accès dans vos paramètres.';
633
+ }
634
+ // 3. Detect No Camera Found
635
+ else if (errString.includes('notfounderror') || errString.includes('no camera')) {
636
+ friendlyMessage = state.currentLanguage === 'en'
637
+ ? 'No camera detected on this device.'
638
+ : 'Aucune caméra détectée sur cet appareil.';
639
+ }
640
+ showAlert(state.currentLanguage === 'en' ? 'Camera Error' : 'Erreur de Caméra', friendlyMessage);
561
641
  setShowCamera(false);
562
642
  setIsProcessingCapture(false);
563
643
  };
@@ -814,62 +894,71 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
814
894
  </View>
815
895
 
816
896
  {showQRModal && Platform.OS === 'web' && (<View style={styles.qrModalOverlay}>
817
- <View style={styles.qrModalContainer}>
818
- <View style={styles.qrModalHeader}>
819
- <Text style={styles.qrModalTitle}>
820
- {state.currentLanguage === 'en' ? 'Continue on your phone' : 'Continuer sur votre téléphone'}
821
- </Text>
822
- <Text style={styles.qrModalText}>
823
- {state.currentLanguage === 'en'
897
+ <View style={styles.qrModalContainer}>
898
+ <View style={styles.qrModalHeader}>
899
+ <Text style={styles.qrModalTitle}>
900
+ {state.currentLanguage === 'en' ? 'Continue on your phone' : 'Continuer sur votre téléphone'}
901
+ </Text>
902
+ <Text style={styles.qrModalText}>
903
+ {state.currentLanguage === 'en'
824
904
  ? "Scan the QR code to download our mobile app and complete the scanning process securely."
825
905
  : "Scannez le code QR pour télécharger notre application et terminer la numérisation en toute sécurité."}
826
- </Text>
827
- </View>
906
+ </Text>
907
+ </View>
828
908
 
829
- <View style={styles.qrToggleContainer}>
830
- <TouchableOpacity style={[styles.qrToggleButton, qrPlatform === 'android' && styles.qrToggleButtonActive]} onPress={() => setQrPlatform('android')}>
831
- <Text style={[styles.qrToggleText, qrPlatform === 'android' && styles.qrToggleTextActive]}>
832
- Android
833
- </Text>
834
- </TouchableOpacity>
835
- <TouchableOpacity style={[styles.qrToggleButton, qrPlatform === 'ios' && styles.qrToggleButtonActive]} onPress={() => setQrPlatform('ios')}>
836
- <Text style={[styles.qrToggleText, qrPlatform === 'ios' && styles.qrToggleTextActive]}>
837
- iOS (Apple)
838
- </Text>
839
- </TouchableOpacity>
840
- </View>
909
+ <View style={styles.qrToggleContainer}>
910
+ <TouchableOpacity style={[styles.qrToggleButton, qrPlatform === 'android' && styles.qrToggleButtonActive]} onPress={() => setQrPlatform('android')}>
911
+ <Text style={[styles.qrToggleText, qrPlatform === 'android' && styles.qrToggleTextActive]}>
912
+ Android
913
+ </Text>
914
+ </TouchableOpacity>
915
+ <TouchableOpacity style={[styles.qrToggleButton, qrPlatform === 'ios' && styles.qrToggleButtonActive]} onPress={() => setQrPlatform('ios')}>
916
+ <Text style={[styles.qrToggleText, qrPlatform === 'ios' && styles.qrToggleTextActive]}>
917
+ iOS (Apple)
918
+ </Text>
919
+ </TouchableOpacity>
920
+ </View>
841
921
 
842
- <View style={styles.qrCodeWrapper}>
843
- <QRCode value={qrPlatform === 'android'
922
+ <View style={styles.qrCodeWrapper}>
923
+ <QRCode value={qrPlatform === 'android'
844
924
  ? 'https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US'
845
- : 'https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731'} size={200} // Slightly reduced to accommodate the toggle switch
846
- backgroundColor="#FFFFFF" color="#0F172A"/>
847
- </View>
925
+ : 'https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731'} size={180} backgroundColor="#FFFFFF" color="#0F172A"/>
926
+ </View>
848
927
 
849
- {isWebScanningRestricted && (<View style={styles.storeLinksContainer}>
850
- <Text style={styles.storeLinksTitle}>
851
- {state.currentLanguage === 'en' ? 'Or click to open directly:' : 'Ou cliquez pour ouvrir directement :'}
852
- </Text>
853
- <View style={styles.storeButtonsRow}>
854
- <TouchableOpacity style={styles.storeButton} onPress={() => window.open('https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US', '_blank')}>
855
- <Text style={styles.storeButtonText}>Google Play</Text>
856
- </TouchableOpacity>
857
- <TouchableOpacity style={styles.storeButton} onPress={() => window.open('https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731', '_blank')}>
858
- <Text style={styles.storeButtonText}>App Store</Text>
859
- </TouchableOpacity>
860
- </View>
861
- </View>)}
928
+ <View style={styles.linkCodeSection}>
929
+ <Text style={styles.linkCodeHeader}>
930
+ {state.currentLanguage === 'en' ? 'Use code below to proceed on the Sanctum Key App.' : 'Vous avez déjà l\'application ? Liez-la :'}
931
+ </Text>
932
+
933
+ {generatedCode && codeTimeLeft > 0 ? (<View style={styles.activeCodeContainer}>
934
+ <Text style={styles.activeCodeText}>{generatedCode}</Text>
935
+ <Text style={styles.timerText}>
936
+ {state.currentLanguage === 'en' ? `Expires in ${formatTime(codeTimeLeft)}` : `Expire dans ${formatTime(codeTimeLeft)}`}
937
+ </Text>
938
+ </View>) : (<TouchableOpacity style={styles.generateCodeButton} onPress={handleGenerateCode} disabled={isGeneratingCode}>
939
+ {isGeneratingCode ? (<ActivityIndicator size="small" color="#0F172A"/>) : (<Text style={styles.generateCodeButtonText}>
940
+ {state.currentLanguage === 'en' ? 'Generate 5-Digit Link Code' : 'Générer un code à 5 chiffres'}
941
+ </Text>)}
942
+ </TouchableOpacity>)}
943
+ </View>
862
944
 
863
- <View style={styles.listeningContainer}>
864
- <ActivityIndicator size="small" color="#2DBD60"/>
865
- <Text style={styles.listeningText}>
866
- {state.currentLanguage === 'en' ? "Waiting for you to complete on mobile..." : "En attente de votre complétion sur mobile..."}
867
- </Text>
868
- </View>
945
+ {isWebScanningRestricted && (<View style={styles.storeLinksContainer}>
946
+ <Text style={styles.storeLinksTitle}>
947
+ {state.currentLanguage === 'en' ? 'Or click to open directly:' : 'Ou cliquez pour ouvrir directement :'}
948
+ </Text>
949
+ <View style={styles.storeButtonsRow}>
950
+ <TouchableOpacity style={styles.storeButton} onPress={() => window.open('https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US', '_blank')}>
951
+ <Text style={styles.storeButtonText}>Google Play</Text>
952
+ </TouchableOpacity>
953
+ <TouchableOpacity style={styles.storeButton} onPress={() => window.open('https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731', '_blank')}>
954
+ <Text style={styles.storeButtonText}>App Store</Text>
955
+ </TouchableOpacity>
956
+ </View>
957
+ </View>)}
869
958
 
870
- <Button title={state.currentLanguage === 'en' ? "Cancel & Stay on Laptop" : "Annuler et rester sur l'ordinateur"} onPress={() => setShowQRModal(false)} variant="outline" fullWidth/>
871
- </View>
872
- </View>)}
959
+ <Button title={state.currentLanguage === 'en' ? "Cancel & Stay on Laptop" : "Annuler et rester sur l'ordinateur"} onPress={() => setShowQRModal(false)} variant="outline" fullWidth/>
960
+ </View>
961
+ </View>)}
873
962
  </View>);
874
963
  };
875
964
  const styles = StyleSheet.create({
@@ -986,5 +1075,52 @@ const styles = StyleSheet.create({
986
1075
  qrToggleTextActive: {
987
1076
  color: '#0F172A', // Active text color
988
1077
  },
1078
+ linkCodeSection: {
1079
+ width: '100%',
1080
+ alignItems: 'center',
1081
+ backgroundColor: '#F8FAFC', // Very light slate
1082
+ borderRadius: 16,
1083
+ padding: 16,
1084
+ marginBottom: 20,
1085
+ borderWidth: 1,
1086
+ borderColor: '#E2E8F0',
1087
+ },
1088
+ linkCodeHeader: {
1089
+ fontSize: 13,
1090
+ color: '#475569',
1091
+ fontWeight: '600',
1092
+ marginBottom: 12,
1093
+ },
1094
+ generateCodeButton: {
1095
+ backgroundColor: '#E2E8F0',
1096
+ paddingVertical: 12,
1097
+ paddingHorizontal: 20,
1098
+ borderRadius: 10,
1099
+ width: '100%',
1100
+ alignItems: 'center',
1101
+ justifyContent: 'center',
1102
+ height: 44, // Fixed height to prevent jumping during loading state
1103
+ },
1104
+ generateCodeButtonText: {
1105
+ color: '#0F172A',
1106
+ fontWeight: '700',
1107
+ fontSize: 14,
1108
+ },
1109
+ activeCodeContainer: {
1110
+ alignItems: 'center',
1111
+ width: '100%',
1112
+ },
1113
+ activeCodeText: {
1114
+ fontSize: 32,
1115
+ fontWeight: '900',
1116
+ color: '#2DBD60',
1117
+ letterSpacing: 4,
1118
+ marginBottom: 4,
1119
+ },
1120
+ timerText: {
1121
+ fontSize: 12,
1122
+ color: '#EF4444', // Red to indicate urgency
1123
+ fontWeight: '600',
1124
+ },
989
1125
  });
990
1126
  //# sourceMappingURL=IDCardCapture.js.map