@sanctum-key/react-native-sdk 1.0.53 → 1.0.54
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/README.md +7 -2
- package/android/build/.transforms/3db9b15041d4285a820fc84462995ea1/results.bin +1 -0
- package/android/build/.transforms/3db9b15041d4285a820fc84462995ea1/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +1 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$BarcodeRegion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$1$5$1.class → SanctumKeySdkModule$definition$1$6$1.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$1$5$2.class → SanctumKeySdkModule$definition$1$6$2.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$1.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$1.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$5.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$2.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$3.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$3.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$4.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$4.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$2.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$5.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$6.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$6.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$1.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$1.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$3.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$2.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$3.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$4.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$2.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$5.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$6.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$4.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$7.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$View$1.class → SanctumKeySdkModule$definition$lambda$16$$inlined$View$1.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$lambda$4$$inlined$Prop$1.class → SanctumKeySdkModule$definition$lambda$16$lambda$15$$inlined$Prop$1.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$lambda$4$$inlined$Prop$2.class → SanctumKeySdkModule$definition$lambda$16$lambda$15$$inlined$Prop$2.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule.class +0 -0
- package/android/build.gradle +7 -0
- package/android/src/main/java/kyc/sanctumkey/com/SanctumKeySdkModule.kt +428 -1
- package/build/package.json +1 -1
- package/build/src/SanctumKeySdkModule.d.ts +1 -0
- package/build/src/SanctumKeySdkModule.d.ts.map +1 -1
- package/build/src/SanctumKeySdkModule.js.map +1 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +50 -41
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/EnhancedCameraView.web.d.ts +2 -0
- package/build/src/components/EnhancedCameraView.web.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.web.js +11 -6
- package/build/src/components/EnhancedCameraView.web.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +499 -78
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
- package/build/src/components/TemplateKYCExample.js +3 -9
- package/build/src/components/TemplateKYCExample.js.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.js +0 -2
- package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
- package/build/src/components/WebBarcodeScanner.d.ts +2 -0
- package/build/src/components/WebBarcodeScanner.d.ts.map +1 -1
- package/build/src/components/WebBarcodeScanner.js +99 -85
- package/build/src/components/WebBarcodeScanner.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +1 -0
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +1 -1
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/build/src/modules/api/types.d.ts +1 -0
- package/build/src/modules/api/types.d.ts.map +1 -1
- package/build/src/modules/api/types.js.map +1 -1
- package/package.json +1 -1
- package/plugin/build/withVisionCamera.d.ts +1 -0
- package/plugin/build/withVisionCamera.js +13 -1
- package/plugin/src/withVisionCamera.js +18 -2
- package/plugin/src/withVisionCamera.ts +19 -2
- package/src/SanctumKeySdkModule.ts +1 -0
- package/src/components/EnhancedCameraView.tsx +52 -41
- package/src/components/EnhancedCameraView.web.tsx +15 -5
- package/src/components/KYCElements/IDCardCapture.tsx +552 -75
- package/src/components/TemplateKYCExample.tsx +6 -14
- package/src/components/TemplateKYCFlowRefactored.tsx +0 -2
- package/src/components/WebBarcodeScanner.tsx +147 -198
- package/src/modules/api/KYCService.ts +3 -3
- package/src/modules/api/types.ts +1 -0
|
@@ -15,10 +15,11 @@ import { cropByObb, getObbConfidence, OBB_CONFIDENCE_THRESHOLD } from '../../uti
|
|
|
15
15
|
import REGION_MAPPING from '../../config/region_mapping.json';
|
|
16
16
|
import QRCode from 'react-native-qrcode-svg';
|
|
17
17
|
import { cropImageWithBBoxWithTolerance } from '../../utils/cropByObb';
|
|
18
|
-
import kycService, { authentification, clearAuthToken } from '../../modules/api/KYCService';
|
|
18
|
+
import kycService, { authentification, clearAuthToken, errorMessage } from '../../modules/api/KYCService';
|
|
19
19
|
import { Linking } from 'react-native';
|
|
20
20
|
import { Camera } from 'react-native-vision-camera';
|
|
21
21
|
import { sdkLogger } from '../../utils/logger';
|
|
22
|
+
import SanctumKeySdkModule from '../../SanctumKeySdkModule';
|
|
22
23
|
|
|
23
24
|
const ISO_TO_COUNTRY_NAME: Record<string, string> = {
|
|
24
25
|
'KE': 'Kenya', 'CM': 'Cameroon', 'NG': 'Nigeria', 'CA': 'Canada',
|
|
@@ -33,6 +34,71 @@ interface IIDCardPayload {
|
|
|
33
34
|
templatePath?: string;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
interface PendingCanadianImage {
|
|
38
|
+
path: string;
|
|
39
|
+
templateResponse: any;
|
|
40
|
+
templatePath: string;
|
|
41
|
+
bbox?: IBbox;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const BARCODE_RESPONSE_TEXT_KEYS = [
|
|
45
|
+
'barcode_data',
|
|
46
|
+
'raw_barcode',
|
|
47
|
+
'raw_data',
|
|
48
|
+
'decoded_data',
|
|
49
|
+
'decoded_text',
|
|
50
|
+
'barcode',
|
|
51
|
+
'text',
|
|
52
|
+
'value',
|
|
53
|
+
'data',
|
|
54
|
+
'detail',
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The image decoder has returned a few slightly different response envelopes
|
|
59
|
+
* over time. Keep that API variation out of the capture flow and only accept
|
|
60
|
+
* values from fields that are known to contain the raw barcode payload.
|
|
61
|
+
*/
|
|
62
|
+
const getDecodedBarcodeText = (response: unknown, depth = 0): string | null => {
|
|
63
|
+
if (typeof response === 'string') {
|
|
64
|
+
return response.trim().length > 0 ? response : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!response || typeof response !== 'object' || depth > 3) return null;
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(response)) {
|
|
70
|
+
for (const item of response) {
|
|
71
|
+
const value = getDecodedBarcodeText(item, depth + 1);
|
|
72
|
+
if (value) return value;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const record = response as Record<string, unknown>;
|
|
78
|
+
for (const key of BARCODE_RESPONSE_TEXT_KEYS) {
|
|
79
|
+
const value = record[key];
|
|
80
|
+
if (typeof value === 'string' && value.trim().length > 0) return value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const key of ['result', 'data', 'payload', 'barcodes', 'results'] as const) {
|
|
84
|
+
const nested = record[key];
|
|
85
|
+
if (nested && typeof nested === 'object') {
|
|
86
|
+
const value = getDecodedBarcodeText(nested, depth + 1);
|
|
87
|
+
if (value) return value;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const isUsableBarcodeText = (value: unknown, allowShortBarcode: boolean): value is string => {
|
|
95
|
+
if (typeof value !== 'string') return false;
|
|
96
|
+
|
|
97
|
+
const trimmed = value.trim();
|
|
98
|
+
const isDecoderError = /^(no (?:bar)?code|barcode (?:not|was not)|unable to decode|failed to decode|error\b)/i.test(trimmed);
|
|
99
|
+
return !isDecoderError && trimmed.length > 0 && (allowShortBarcode || value.length > 50);
|
|
100
|
+
};
|
|
101
|
+
|
|
36
102
|
interface IDCardCaptureProps {
|
|
37
103
|
component: TemplateComponent;
|
|
38
104
|
value?: Record<string, IIDCardPayload>;
|
|
@@ -49,6 +115,19 @@ const SHORT_TO_LONG_REGION: Record<string, string> = {
|
|
|
49
115
|
"SK": "Saskatchewan", "YT": "Yukon"
|
|
50
116
|
};
|
|
51
117
|
|
|
118
|
+
const DOCUMENT_TYPE_MAPPING: Record<string, GovernmentDocumentType> = {
|
|
119
|
+
nationalId: 'national_id',
|
|
120
|
+
national_id: 'national_id',
|
|
121
|
+
identity_card: 'national_id',
|
|
122
|
+
passport: 'passport',
|
|
123
|
+
driversLicense: 'drivers_licence',
|
|
124
|
+
drivers_licence: 'drivers_licence',
|
|
125
|
+
residencePermit: 'permanent_residence',
|
|
126
|
+
permanent_residence: 'permanent_residence',
|
|
127
|
+
healthInsuranceCard: 'health_insurance_card',
|
|
128
|
+
health_insurance_card: 'health_insurance_card',
|
|
129
|
+
};
|
|
130
|
+
|
|
52
131
|
const ExpiringCodeTimer = ({ initialCode, language }: { initialCode: string, language: string }) => {
|
|
53
132
|
const [timeLeft, setTimeLeft] = useState(300);
|
|
54
133
|
useEffect(() => {
|
|
@@ -85,6 +164,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
85
164
|
const [isProcessingCapture, setIsProcessingCapture] = useState(false);
|
|
86
165
|
const [processingImagePath, setProcessingImagePath] = useState<string | null>(null);
|
|
87
166
|
const [cameraKey, setCameraKey] = useState(0);
|
|
167
|
+
const cameraGenerationRef = useRef(0);
|
|
88
168
|
const [isRebootingCamera, setIsRebootingCamera] = useState(false);
|
|
89
169
|
const [cameraType, setCameraType] = useState<'back' | 'front'>('back');
|
|
90
170
|
const [showQRModal, setShowQRModal] = useState(false);
|
|
@@ -92,8 +172,15 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
92
172
|
console.log(currentUrl)
|
|
93
173
|
const [qrPlatform, setQrPlatform] = useState<'android' | 'ios'>('android');
|
|
94
174
|
const [scanPhase, setScanPhase] = useState<'image' | 'barcode'>('image');
|
|
95
|
-
const [pendingCAImage, setPendingCAImage] = useState<
|
|
175
|
+
const [pendingCAImage, setPendingCAImage] = useState<PendingCanadianImage | null>(null);
|
|
96
176
|
const scannedBarcodeRef = useRef<string | null>(null);
|
|
177
|
+
const barcodeImageDecodeSessionRef = useRef<number | null>(null);
|
|
178
|
+
const barcodeServiceDecodeSessionRef = useRef<number | null>(null);
|
|
179
|
+
const barcodeServiceAttemptsRef = useRef<{ session: number | null; count: number }>({ session: null, count: 0 });
|
|
180
|
+
const barcodeFinalizingRef = useRef(false);
|
|
181
|
+
const barcodeCompletedRef = useRef(false);
|
|
182
|
+
const barcodeScanSessionRef = useRef(0);
|
|
183
|
+
const nativeBarcodeDecoderWarningShownRef = useRef(false);
|
|
97
184
|
const [generatedCode, setGeneratedCode] = useState<string | null>(null);
|
|
98
185
|
const [isGeneratingCode, setIsGeneratingCode] = useState(false);
|
|
99
186
|
const { actions, state, env, apiKey } = useTemplateKYCFlowContext();
|
|
@@ -113,19 +200,12 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
113
200
|
return countrySelectionComponent ? state.componentData[countrySelectionComponent.id] : null;
|
|
114
201
|
}, [state.template.components, state.componentData]);
|
|
115
202
|
|
|
116
|
-
const documentTypeMapping: Record<string, GovernmentDocumentType> = {
|
|
117
|
-
'nationalId': 'national_id', 'national_id': 'national_id', 'identity_card': 'national_id',
|
|
118
|
-
'passport': 'passport', 'driversLicense': 'drivers_licence', 'drivers_licence': 'drivers_licence',
|
|
119
|
-
'residencePermit': 'permanent_residence', 'permanent_residence': 'permanent_residence',
|
|
120
|
-
'healthInsuranceCard': 'health_insurance_card', 'health_insurance_card': 'health_insurance_card',
|
|
121
|
-
};
|
|
122
|
-
|
|
123
203
|
const selectedDocumentType = useMemo<{ type: GovernmentDocumentType; region: string } | null>(() => {
|
|
124
204
|
if (!countrySelectionData?.documentType) return null;
|
|
125
205
|
const backendDocType = countrySelectionData.documentType;
|
|
126
|
-
const mappedType =
|
|
206
|
+
const mappedType = DOCUMENT_TYPE_MAPPING[backendDocType] || backendDocType as GovernmentDocumentType;
|
|
127
207
|
return { type: mappedType, region: countrySelectionData.region || 'root' };
|
|
128
|
-
}, [countrySelectionData
|
|
208
|
+
}, [countrySelectionData]);
|
|
129
209
|
|
|
130
210
|
const countryData = useMemo(() => countrySelectionData, [countrySelectionData]);
|
|
131
211
|
|
|
@@ -187,22 +267,47 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
187
267
|
|
|
188
268
|
const targetBarcodeTypes = useMemo<any[]>(() => {
|
|
189
269
|
const docType = selectedDocumentType?.type;
|
|
190
|
-
if (docType === 'health_insurance_card')
|
|
270
|
+
if (docType === 'health_insurance_card') {
|
|
271
|
+
return [
|
|
272
|
+
'pdf-417', 'code-128', 'code-39', 'code-93', 'codabar',
|
|
273
|
+
// Vision Camera's Android implementation rejects `itf-14`; its `itf`
|
|
274
|
+
// reader already covers ITF-14 payloads.
|
|
275
|
+
'ean-13', 'ean-8', 'itf', 'upc-a', 'upc-e',
|
|
276
|
+
];
|
|
277
|
+
}
|
|
191
278
|
return ['pdf-417'];
|
|
192
|
-
}, [selectedDocumentType]);
|
|
279
|
+
}, [selectedDocumentType?.type]);
|
|
193
280
|
|
|
194
281
|
const refreshCamera = () => {
|
|
282
|
+
const nextCameraGeneration = cameraGenerationRef.current + 1;
|
|
283
|
+
cameraGenerationRef.current = nextCameraGeneration;
|
|
284
|
+
barcodeScanSessionRef.current += 1;
|
|
285
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
286
|
+
barcodeFinalizingRef.current = false;
|
|
287
|
+
barcodeCompletedRef.current = false;
|
|
288
|
+
scannedBarcodeRef.current = null;
|
|
289
|
+
isAnalyzingRef.current = false;
|
|
290
|
+
isProcessingCaptureRef.current = false;
|
|
291
|
+
setIsProcessingCapture(false);
|
|
292
|
+
setProcessingImagePath(null);
|
|
195
293
|
setIsRebootingCamera(true);
|
|
196
|
-
if (
|
|
294
|
+
if (scanPhaseRef.current === 'barcode') {
|
|
295
|
+
const activeSide = currentSideRef.current;
|
|
296
|
+
scanPhaseRef.current = 'image';
|
|
197
297
|
setScanPhase('image');
|
|
198
298
|
setPendingCAImage(null);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
299
|
+
const nextImages = { ...capturedImagesRef.current };
|
|
300
|
+
delete nextImages[activeSide];
|
|
301
|
+
capturedImagesRef.current = nextImages;
|
|
302
|
+
setCapturedImages(nextImages);
|
|
303
|
+
if (value) { const newValue = { ...value }; delete newValue[activeSide]; onValueChange(newValue); }
|
|
202
304
|
setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '', templatePath: '', mrz: '', bbox: undefined });
|
|
305
|
+
} else {
|
|
306
|
+
setSilentCaptureResult(prev => ({ ...prev, success: false, isAnalyzing: false, error: '' }));
|
|
203
307
|
}
|
|
204
308
|
setTimeout(() => {
|
|
205
|
-
|
|
309
|
+
if (cameraGenerationRef.current !== nextCameraGeneration) return;
|
|
310
|
+
setCameraKey(nextCameraGeneration);
|
|
206
311
|
setIsRebootingCamera(false);
|
|
207
312
|
}, 1000);
|
|
208
313
|
};
|
|
@@ -215,6 +320,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
215
320
|
useEffect(() => {
|
|
216
321
|
if (value && Object.keys(value).length > 0 && JSON.stringify(value) !== JSON.stringify(capturedImages)) {
|
|
217
322
|
const updatedImages = value as Record<string, IIDCardPayload>;
|
|
323
|
+
capturedImagesRef.current = updatedImages;
|
|
218
324
|
setCapturedImages(updatedImages);
|
|
219
325
|
const currentImageData = updatedImages[currentSide];
|
|
220
326
|
if (currentImageData?.dir) {
|
|
@@ -261,12 +367,132 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
261
367
|
}
|
|
262
368
|
};
|
|
263
369
|
|
|
370
|
+
const startBackCapture = () => {
|
|
371
|
+
sdkLogger.info('Preparing camera for back document capture');
|
|
372
|
+
barcodeScanSessionRef.current += 1;
|
|
373
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
374
|
+
barcodeFinalizingRef.current = false;
|
|
375
|
+
barcodeCompletedRef.current = false;
|
|
376
|
+
scannedBarcodeRef.current = null;
|
|
377
|
+
isAnalyzingRef.current = false;
|
|
378
|
+
isProcessingCaptureRef.current = false;
|
|
379
|
+
currentSideRef.current = 'back';
|
|
380
|
+
scanPhaseRef.current = 'image';
|
|
381
|
+
|
|
382
|
+
const nextImages = { ...capturedImagesRef.current };
|
|
383
|
+
delete nextImages.back;
|
|
384
|
+
capturedImagesRef.current = nextImages;
|
|
385
|
+
setCapturedImages(nextImages);
|
|
386
|
+
setBboxBySide(prev => {
|
|
387
|
+
const next = { ...prev };
|
|
388
|
+
delete next.back;
|
|
389
|
+
return next;
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
if (value && Object.prototype.hasOwnProperty.call(value, 'back')) {
|
|
393
|
+
const nextValue = { ...value };
|
|
394
|
+
delete nextValue.back;
|
|
395
|
+
onValueChange(nextValue);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
setSilentCaptureResult({
|
|
399
|
+
success: false,
|
|
400
|
+
isAnalyzing: false,
|
|
401
|
+
path: '',
|
|
402
|
+
error: '',
|
|
403
|
+
templatePath: '',
|
|
404
|
+
mrz: '',
|
|
405
|
+
barcode: undefined,
|
|
406
|
+
bbox: undefined,
|
|
407
|
+
});
|
|
408
|
+
setPendingCAImage(null);
|
|
409
|
+
setProcessingImagePath(null);
|
|
410
|
+
setIsProcessingCapture(false);
|
|
411
|
+
setIsRebootingCamera(false);
|
|
412
|
+
setCurrentSide('back');
|
|
413
|
+
cameraGenerationRef.current += 1;
|
|
414
|
+
setCameraKey(cameraGenerationRef.current);
|
|
415
|
+
setShowCamera(true);
|
|
416
|
+
actions.showCustomStepper(false);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const cancelCurrentCapture = () => {
|
|
420
|
+
cameraGenerationRef.current += 1;
|
|
421
|
+
barcodeScanSessionRef.current += 1;
|
|
422
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
423
|
+
barcodeFinalizingRef.current = false;
|
|
424
|
+
barcodeCompletedRef.current = false;
|
|
425
|
+
scannedBarcodeRef.current = null;
|
|
426
|
+
isAnalyzingRef.current = false;
|
|
427
|
+
isProcessingCaptureRef.current = false;
|
|
428
|
+
scanPhaseRef.current = 'image';
|
|
429
|
+
|
|
430
|
+
const savedImage = capturedImagesRef.current[currentSideRef.current];
|
|
431
|
+
setScanPhase('image');
|
|
432
|
+
setPendingCAImage(null);
|
|
433
|
+
setIsProcessingCapture(false);
|
|
434
|
+
setProcessingImagePath(null);
|
|
435
|
+
setIsRebootingCamera(false);
|
|
436
|
+
setShowCamera(false);
|
|
437
|
+
setSilentCaptureResult(savedImage?.dir
|
|
438
|
+
? {
|
|
439
|
+
path: savedImage.dir,
|
|
440
|
+
success: true,
|
|
441
|
+
isAnalyzing: false,
|
|
442
|
+
error: '',
|
|
443
|
+
mrz: savedImage.mrz || '',
|
|
444
|
+
templatePath: savedImage.templatePath || '',
|
|
445
|
+
bbox: bboxBySide[currentSideRef.current],
|
|
446
|
+
}
|
|
447
|
+
: { path: '', success: false, isAnalyzing: false, error: '', templatePath: '', mrz: '', bbox: undefined });
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const returnToFrontReview = () => {
|
|
451
|
+
cameraGenerationRef.current += 1;
|
|
452
|
+
barcodeScanSessionRef.current += 1;
|
|
453
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
454
|
+
barcodeFinalizingRef.current = false;
|
|
455
|
+
barcodeCompletedRef.current = false;
|
|
456
|
+
scannedBarcodeRef.current = null;
|
|
457
|
+
isAnalyzingRef.current = false;
|
|
458
|
+
isProcessingCaptureRef.current = false;
|
|
459
|
+
currentSideRef.current = 'front';
|
|
460
|
+
scanPhaseRef.current = 'image';
|
|
461
|
+
|
|
462
|
+
const frontImage = capturedImagesRef.current.front;
|
|
463
|
+
setScanPhase('image');
|
|
464
|
+
setPendingCAImage(null);
|
|
465
|
+
setCurrentSide('front');
|
|
466
|
+
setShowCamera(false);
|
|
467
|
+
setIsProcessingCapture(false);
|
|
468
|
+
setProcessingImagePath(null);
|
|
469
|
+
setIsRebootingCamera(false);
|
|
470
|
+
setSilentCaptureResult(frontImage?.dir
|
|
471
|
+
? {
|
|
472
|
+
path: frontImage.dir,
|
|
473
|
+
success: true,
|
|
474
|
+
isAnalyzing: false,
|
|
475
|
+
error: '',
|
|
476
|
+
mrz: frontImage.mrz || '',
|
|
477
|
+
templatePath: frontImage.templatePath || '',
|
|
478
|
+
bbox: bboxBySide.front,
|
|
479
|
+
}
|
|
480
|
+
: { path: '', success: false, isAnalyzing: false, error: '', templatePath: '', mrz: '', bbox: undefined });
|
|
481
|
+
};
|
|
482
|
+
|
|
264
483
|
const cameraConfig = useMemo(() => {
|
|
265
484
|
let instructions = selectedDocumentType
|
|
266
485
|
? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr)
|
|
267
486
|
: getLocalizedText(component.instructions as Record<string, LocalizedText>);
|
|
268
487
|
|
|
269
|
-
if (scanPhase === 'barcode')
|
|
488
|
+
if (scanPhase === 'barcode') {
|
|
489
|
+
instructions = '';
|
|
490
|
+
} else {
|
|
491
|
+
const stabilityHint = locale === 'en'
|
|
492
|
+
? 'Position the entire ID, then hold steady. Capture starts automatically.'
|
|
493
|
+
: 'Positionnez la pièce entière, puis restez immobile. La capture démarre automatiquement.';
|
|
494
|
+
instructions = instructions ? `${instructions}\n${stabilityHint}` : stabilityHint;
|
|
495
|
+
}
|
|
270
496
|
|
|
271
497
|
const isLandscape = screenWidth > screenHeight;
|
|
272
498
|
let xMin, xMax, yMin, yMax;
|
|
@@ -306,12 +532,34 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
306
532
|
|
|
307
533
|
|
|
308
534
|
const retakePicture = (sideToRetake: 'front' | 'back') => {
|
|
309
|
-
|
|
310
|
-
|
|
535
|
+
barcodeScanSessionRef.current += 1;
|
|
536
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
537
|
+
barcodeFinalizingRef.current = false;
|
|
538
|
+
barcodeCompletedRef.current = false;
|
|
539
|
+
scannedBarcodeRef.current = null;
|
|
540
|
+
isAnalyzingRef.current = false;
|
|
541
|
+
isProcessingCaptureRef.current = false;
|
|
542
|
+
currentSideRef.current = sideToRetake;
|
|
543
|
+
scanPhaseRef.current = 'image';
|
|
544
|
+
setCurrentSide(sideToRetake);
|
|
545
|
+
setIsProcessingCapture(false);
|
|
546
|
+
setProcessingImagePath(null);
|
|
547
|
+
setScanPhase('image');
|
|
548
|
+
setPendingCAImage(null);
|
|
311
549
|
setSilentCaptureResult({ path: '', success: false, isAnalyzing: false, error: '', templatePath: '', mrz: '', bbox: undefined });
|
|
312
|
-
|
|
313
|
-
|
|
550
|
+
const nextImages = { ...capturedImagesRef.current };
|
|
551
|
+
delete nextImages[sideToRetake];
|
|
552
|
+
capturedImagesRef.current = nextImages;
|
|
553
|
+
setCapturedImages(nextImages);
|
|
554
|
+
setBboxBySide(prev => {
|
|
555
|
+
const next = { ...prev };
|
|
556
|
+
delete next[sideToRetake];
|
|
557
|
+
return next;
|
|
558
|
+
});
|
|
314
559
|
if (value) { const newValue = { ...value }; delete newValue[sideToRetake]; onValueChange(newValue); }
|
|
560
|
+
setShowCamera(true);
|
|
561
|
+
refreshCamera();
|
|
562
|
+
actions.showCustomStepper(false);
|
|
315
563
|
};
|
|
316
564
|
|
|
317
565
|
const getCurrentSideVerification = (side: string, countryKey: string, cData: any) => {
|
|
@@ -362,8 +610,14 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
362
610
|
return found?.[side] || null;
|
|
363
611
|
};
|
|
364
612
|
|
|
365
|
-
const autoCapture = async (
|
|
366
|
-
|
|
613
|
+
const autoCapture = async (
|
|
614
|
+
capturePath: string,
|
|
615
|
+
verified: ISilentCaptureResult & { region?: string },
|
|
616
|
+
captureSide: 'front' | 'back' = currentSideRef.current,
|
|
617
|
+
captureGeneration: number = cameraGenerationRef.current,
|
|
618
|
+
) => {
|
|
619
|
+
if (isProcessingCaptureRef.current) return;
|
|
620
|
+
isProcessingCaptureRef.current = true;
|
|
367
621
|
setIsProcessingCapture(true);
|
|
368
622
|
const safeCapturePath = (Platform.OS === 'ios' && !capturePath.startsWith('file://')) ? `file://${capturePath}` : capturePath;
|
|
369
623
|
setProcessingImagePath(safeCapturePath);
|
|
@@ -402,13 +656,14 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
402
656
|
|
|
403
657
|
const safeCroppedPath = (Platform.OS === 'ios' && !imagePathForUpload.startsWith('file://')) ? `file://${imagePathForUpload}` : imagePathForUpload;
|
|
404
658
|
const base64 = await pathToBase64(safeCroppedPath);
|
|
405
|
-
|
|
659
|
+
if (cameraGenerationRef.current !== captureGeneration) return;
|
|
660
|
+
const side = captureSide;
|
|
406
661
|
|
|
407
662
|
const newImages = {
|
|
408
663
|
...capturedImagesRef.current,
|
|
409
664
|
[side]: { dir: safeCroppedPath, file: base64, mrz: verified.mrz || "", templatePath: verified.templatePath },
|
|
410
665
|
};
|
|
411
|
-
|
|
666
|
+
capturedImagesRef.current = newImages;
|
|
412
667
|
setCapturedImages(newImages);
|
|
413
668
|
|
|
414
669
|
sdkLogger.info(`User successfully captured ${side} document`);
|
|
@@ -420,17 +675,22 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
420
675
|
}
|
|
421
676
|
|
|
422
677
|
setTimeout(() => {
|
|
678
|
+
if (cameraGenerationRef.current !== captureGeneration) return;
|
|
423
679
|
setShowCamera(false); actions.showCustomStepper(true);
|
|
424
680
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
|
|
681
|
+
isProcessingCaptureRef.current = false;
|
|
425
682
|
setIsProcessingCapture(false); setProcessingImagePath(null);
|
|
683
|
+
scanPhaseRef.current = 'image';
|
|
426
684
|
setScanPhase('image'); setPendingCAImage(null);
|
|
427
685
|
}, 600);
|
|
428
686
|
|
|
429
687
|
} catch (e: any) {
|
|
688
|
+
if (cameraGenerationRef.current !== captureGeneration) return;
|
|
430
689
|
sdkLogger.error(`Auto-capture formatting/saving failed`, { error: e.message });
|
|
431
690
|
const friendlyError = state.currentLanguage === 'en' ? 'Unable to process document. Please try again.' : 'Impossible de traiter le document. Veuillez réessayer.';
|
|
432
691
|
showAlert('Error', friendlyError);
|
|
433
692
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false }));
|
|
693
|
+
isProcessingCaptureRef.current = false;
|
|
434
694
|
setIsProcessingCapture(false); setProcessingImagePath(null);
|
|
435
695
|
}
|
|
436
696
|
};
|
|
@@ -463,14 +723,32 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
463
723
|
});
|
|
464
724
|
};
|
|
465
725
|
|
|
466
|
-
const processCanadianBarcode = async (barcodeString: string) => {
|
|
467
|
-
if (
|
|
726
|
+
const processCanadianBarcode = async (barcodeString: string, pendingImage: PendingCanadianImage | null = pendingCAImage) => {
|
|
727
|
+
if (
|
|
728
|
+
scanPhaseRef.current !== 'barcode' ||
|
|
729
|
+
isProcessingCaptureRef.current ||
|
|
730
|
+
barcodeFinalizingRef.current ||
|
|
731
|
+
barcodeCompletedRef.current ||
|
|
732
|
+
!pendingImage
|
|
733
|
+
) return;
|
|
734
|
+
|
|
735
|
+
const docType = selectedDocTypeRef.current;
|
|
736
|
+
const cData = countryDataRef.current;
|
|
737
|
+
if (!docType || !isUsableBarcodeText(barcodeString, docType.type === 'health_insurance_card')) return;
|
|
738
|
+
|
|
739
|
+
const scanSession = barcodeScanSessionRef.current;
|
|
740
|
+
const captureGeneration = cameraGenerationRef.current;
|
|
741
|
+
const isStaleBarcode = () => (
|
|
742
|
+
barcodeScanSessionRef.current !== scanSession ||
|
|
743
|
+
cameraGenerationRef.current !== captureGeneration ||
|
|
744
|
+
scanPhaseRef.current !== 'barcode'
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
barcodeFinalizingRef.current = true;
|
|
468
748
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: true, error: '' }));
|
|
469
749
|
|
|
470
750
|
try {
|
|
471
|
-
const
|
|
472
|
-
const cData = countryDataRef.current;
|
|
473
|
-
const isHIC = docType!.type === 'health_insurance_card';
|
|
751
|
+
const isHIC = docType.type === 'health_insurance_card';
|
|
474
752
|
let parsedDictionary;
|
|
475
753
|
|
|
476
754
|
if (isHIC && barcodeString.length < 50) {
|
|
@@ -479,27 +757,169 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
479
757
|
const token = await authentification();
|
|
480
758
|
parsedDictionary = await kycService.formatBarcodeData({ textInput: barcodeString, token }, env);
|
|
481
759
|
}
|
|
760
|
+
if (isStaleBarcode()) return;
|
|
482
761
|
if (!parsedDictionary) throw new Error("Could not parse the barcode data.");
|
|
762
|
+
|
|
763
|
+
barcodeCompletedRef.current = true;
|
|
483
764
|
|
|
484
765
|
const verifiedResult: ISilentCaptureResult & { region?: string } = {
|
|
485
|
-
path:
|
|
766
|
+
path: pendingImage.path, templatePath: pendingImage.templatePath, bbox: pendingImage.bbox, success: true,
|
|
486
767
|
mrz: JSON.stringify(parsedDictionary), barcode: barcodeString, isAnalyzing: false,
|
|
487
|
-
country: cData?.code, documentType: docType
|
|
768
|
+
country: cData?.code, documentType: docType.type, region: docType.region !== 'root' ? docType.region : undefined,
|
|
488
769
|
};
|
|
489
770
|
|
|
490
771
|
setSilentCaptureResult(verifiedResult);
|
|
491
|
-
if (
|
|
492
|
-
const safeBbox =
|
|
772
|
+
if (pendingImage.bbox) {
|
|
773
|
+
const safeBbox = pendingImage.bbox as IBbox;
|
|
493
774
|
setBboxBySide(prev => ({ ...prev, ['back']: safeBbox }));
|
|
494
775
|
}
|
|
495
|
-
await autoCapture(
|
|
776
|
+
await autoCapture(pendingImage.path, verifiedResult, 'back', captureGeneration);
|
|
777
|
+
} catch (error: any) {
|
|
778
|
+
if (isStaleBarcode()) return;
|
|
779
|
+
barcodeCompletedRef.current = false;
|
|
780
|
+
scannedBarcodeRef.current = null;
|
|
781
|
+
sdkLogger.warn('Decoded barcode could not be formatted; continuing to scan', { error: error?.message });
|
|
782
|
+
setSilentCaptureResult(prev => ({
|
|
783
|
+
...prev,
|
|
784
|
+
isAnalyzing: false,
|
|
785
|
+
success: false,
|
|
786
|
+
error: state.currentLanguage === 'en'
|
|
787
|
+
? 'Barcode detected but not readable yet. Hold it steady and try again.'
|
|
788
|
+
: 'Code-barres détecté mais pas encore lisible. Maintenez-le stable et réessayez.',
|
|
789
|
+
}));
|
|
790
|
+
} finally {
|
|
791
|
+
if (barcodeScanSessionRef.current === scanSession) {
|
|
792
|
+
barcodeFinalizingRef.current = false;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Live scanners favor speed and can receive a relatively small analysis
|
|
799
|
+
* frame. This fallback sends the camera's full-resolution still image to the
|
|
800
|
+
* dedicated decoder, which is much more reliable for high-density PDF417.
|
|
801
|
+
*/
|
|
802
|
+
const decodeBarcodeFromImage = async (
|
|
803
|
+
imagePath: string,
|
|
804
|
+
pendingImage: PendingCanadianImage | null = pendingCAImage,
|
|
805
|
+
allowServiceFallback = true,
|
|
806
|
+
) => {
|
|
807
|
+
if (
|
|
808
|
+
scanPhaseRef.current !== 'barcode' ||
|
|
809
|
+
!pendingImage ||
|
|
810
|
+
barcodeImageDecodeSessionRef.current === barcodeScanSessionRef.current ||
|
|
811
|
+
barcodeFinalizingRef.current ||
|
|
812
|
+
barcodeCompletedRef.current ||
|
|
813
|
+
isProcessingCaptureRef.current
|
|
814
|
+
) return;
|
|
815
|
+
|
|
816
|
+
const docType = selectedDocTypeRef.current;
|
|
817
|
+
if (!docType) return;
|
|
818
|
+
|
|
819
|
+
const scanSession = barcodeScanSessionRef.current;
|
|
820
|
+
barcodeImageDecodeSessionRef.current = scanSession;
|
|
821
|
+
sdkLogger.info('Attempting high-resolution barcode decode', {
|
|
822
|
+
platform: Platform.OS,
|
|
823
|
+
session: scanSession,
|
|
824
|
+
});
|
|
825
|
+
try {
|
|
826
|
+
if (Platform.OS === 'android' && SanctumKeySdkModule.decodeBarcodeFromImage) {
|
|
827
|
+
try {
|
|
828
|
+
const localBarcodeText = await SanctumKeySdkModule.decodeBarcodeFromImage(
|
|
829
|
+
imagePath,
|
|
830
|
+
targetBarcodeTypes as string[],
|
|
831
|
+
);
|
|
832
|
+
if (scanSession !== barcodeScanSessionRef.current || scanPhaseRef.current !== 'barcode') return;
|
|
833
|
+
|
|
834
|
+
if (isUsableBarcodeText(localBarcodeText, docType.type === 'health_insurance_card')) {
|
|
835
|
+
scannedBarcodeRef.current = localBarcodeText;
|
|
836
|
+
sdkLogger.info('Barcode decoded by enhanced on-device fallback', {
|
|
837
|
+
length: localBarcodeText.length,
|
|
838
|
+
});
|
|
839
|
+
await processCanadianBarcode(localBarcodeText, pendingImage);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
sdkLogger.info('On-device decoders did not find a usable barcode in this frame', {
|
|
843
|
+
session: scanSession,
|
|
844
|
+
});
|
|
845
|
+
} catch (localError: any) {
|
|
846
|
+
sdkLogger.info('Enhanced on-device barcode fallback could not decode this frame', {
|
|
847
|
+
error: String(errorMessage(localError) || 'Unknown local decoder error'),
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
} else if (Platform.OS === 'android' && !nativeBarcodeDecoderWarningShownRef.current) {
|
|
851
|
+
nativeBarcodeDecoderWarningShownRef.current = true;
|
|
852
|
+
sdkLogger.warn('Enhanced on-device barcode decoder is unavailable; rebuild the Android app.');
|
|
853
|
+
}
|
|
854
|
+
} finally {
|
|
855
|
+
if (barcodeImageDecodeSessionRef.current === scanSession) {
|
|
856
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (scanSession !== barcodeScanSessionRef.current || scanPhaseRef.current !== 'barcode') return;
|
|
861
|
+
if (!allowServiceFallback) {
|
|
862
|
+
sdkLogger.info('Verified back image did not yield a barcode locally; waiting for a close-up frame');
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Do not let a slow network request block local decoding of subsequent
|
|
867
|
+
// high-resolution frames. At most two service calls are made per session.
|
|
868
|
+
if (barcodeServiceDecodeSessionRef.current === scanSession) return;
|
|
869
|
+
if (barcodeServiceAttemptsRef.current.session !== scanSession) {
|
|
870
|
+
barcodeServiceAttemptsRef.current = { session: scanSession, count: 0 };
|
|
871
|
+
}
|
|
872
|
+
if (barcodeServiceAttemptsRef.current.count >= 2) return;
|
|
873
|
+
|
|
874
|
+
barcodeServiceAttemptsRef.current.count += 1;
|
|
875
|
+
barcodeServiceDecodeSessionRef.current = scanSession;
|
|
876
|
+
try {
|
|
877
|
+
const token = await authentification();
|
|
878
|
+
if (scanSession !== barcodeScanSessionRef.current || scanPhaseRef.current !== 'barcode') return;
|
|
879
|
+
|
|
880
|
+
const backendDocType = GovernmentDocumentTypeShorted[docType.type as keyof typeof GovernmentDocumentTypeShorted] || docType.type;
|
|
881
|
+
const response = await kycService.extractBarcode({
|
|
882
|
+
fileUri: imagePath,
|
|
883
|
+
token,
|
|
884
|
+
docType: backendDocType,
|
|
885
|
+
postfix: 'back',
|
|
886
|
+
timeoutMs: 15000,
|
|
887
|
+
}, env);
|
|
888
|
+
if (scanSession !== barcodeScanSessionRef.current || scanPhaseRef.current !== 'barcode') return;
|
|
889
|
+
|
|
890
|
+
const barcodeText = getDecodedBarcodeText(response);
|
|
891
|
+
if (isUsableBarcodeText(barcodeText, docType.type === 'health_insurance_card')) {
|
|
892
|
+
scannedBarcodeRef.current = barcodeText;
|
|
893
|
+
sdkLogger.info('Barcode decoded by the high-resolution service fallback', { length: barcodeText.length });
|
|
894
|
+
await processCanadianBarcode(barcodeText, pendingImage);
|
|
895
|
+
} else {
|
|
896
|
+
sdkLogger.info('Barcode service returned no usable data; local scanning remains active', {
|
|
897
|
+
session: scanSession,
|
|
898
|
+
});
|
|
899
|
+
}
|
|
496
900
|
} catch (error: any) {
|
|
497
|
-
|
|
498
|
-
|
|
901
|
+
sdkLogger.info('Barcode service fallback failed; local scanning remains active', {
|
|
902
|
+
error: String(errorMessage(error) || 'Unknown barcode service error'),
|
|
903
|
+
});
|
|
904
|
+
} finally {
|
|
905
|
+
if (barcodeServiceDecodeSessionRef.current === scanSession) {
|
|
906
|
+
barcodeServiceDecodeSessionRef.current = null;
|
|
907
|
+
}
|
|
499
908
|
}
|
|
500
909
|
};
|
|
501
910
|
|
|
502
911
|
const handleExitKYCFlow = () => {
|
|
912
|
+
cameraGenerationRef.current += 1;
|
|
913
|
+
barcodeScanSessionRef.current += 1;
|
|
914
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
915
|
+
barcodeFinalizingRef.current = false;
|
|
916
|
+
barcodeCompletedRef.current = false;
|
|
917
|
+
scannedBarcodeRef.current = null;
|
|
918
|
+
isAnalyzingRef.current = false;
|
|
919
|
+
isProcessingCaptureRef.current = false;
|
|
920
|
+
currentSideRef.current = 'front';
|
|
921
|
+
scanPhaseRef.current = 'image';
|
|
922
|
+
capturedImagesRef.current = {};
|
|
503
923
|
clearAuthToken();
|
|
504
924
|
// 1. Clear local UI and camera states immediately
|
|
505
925
|
setCapturedImages({});
|
|
@@ -507,7 +927,6 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
507
927
|
setCurrentSide('front');
|
|
508
928
|
setScanPhase('image');
|
|
509
929
|
setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '' });
|
|
510
|
-
scannedBarcodeRef.current = null;
|
|
511
930
|
setPendingCAImage(null);
|
|
512
931
|
setIsProcessingCapture(false);
|
|
513
932
|
setProcessingImagePath(null);
|
|
@@ -525,9 +944,16 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
525
944
|
}
|
|
526
945
|
};
|
|
527
946
|
|
|
528
|
-
const handleSilentCapture = async (
|
|
947
|
+
const handleSilentCapture = async (
|
|
948
|
+
result: { success: boolean; path?: string; error?: string; barcode?: unknown },
|
|
949
|
+
frameGeneration: number = cameraGenerationRef.current,
|
|
950
|
+
) => {
|
|
951
|
+
if (cameraGenerationRef.current !== frameGeneration) return;
|
|
529
952
|
if (isProcessingCaptureRef.current) return;
|
|
530
|
-
if (scanPhaseRef.current === 'barcode')
|
|
953
|
+
if (scanPhaseRef.current === 'barcode') {
|
|
954
|
+
if (result.success && result.path) void decodeBarcodeFromImage(result.path);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
531
957
|
if (isAnalyzingRef.current) return;
|
|
532
958
|
|
|
533
959
|
// 🚨 SYNCHRONOUS LOCK: Block all subsequent camera frames immediately
|
|
@@ -537,6 +963,11 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
537
963
|
const captured = capturedImagesRef.current;
|
|
538
964
|
const docType = selectedDocTypeRef.current;
|
|
539
965
|
const cData = countryDataRef.current;
|
|
966
|
+
const isStaleFrame = () => (
|
|
967
|
+
cameraGenerationRef.current !== frameGeneration ||
|
|
968
|
+
currentSideRef.current !== side ||
|
|
969
|
+
scanPhaseRef.current !== 'image'
|
|
970
|
+
);
|
|
540
971
|
|
|
541
972
|
if (result.success && result.path) {
|
|
542
973
|
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
|
|
@@ -555,7 +986,7 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
555
986
|
const issuingSubdivision = docType.region !== 'root' ? docType.region : undefined;
|
|
556
987
|
|
|
557
988
|
const watchdog = setTimeout(() => {
|
|
558
|
-
if (isAnalyzingRef.current) {
|
|
989
|
+
if (!isStaleFrame() && isAnalyzingRef.current) {
|
|
559
990
|
isAnalyzingRef.current = false; // 🚨 UNLOCK inside watchdog
|
|
560
991
|
setSilentCaptureResult((prev) => ({
|
|
561
992
|
...prev,
|
|
@@ -575,6 +1006,10 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
575
1006
|
postfix: side,
|
|
576
1007
|
issuingSubdivision: issuingSubdivision
|
|
577
1008
|
}, env);
|
|
1009
|
+
if (isStaleFrame()) {
|
|
1010
|
+
clearTimeout(watchdog);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
578
1013
|
|
|
579
1014
|
templateResponse = templateType;
|
|
580
1015
|
if (templateType.template_path) templatePath = templateType.template_path;
|
|
@@ -589,6 +1024,10 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
589
1024
|
}
|
|
590
1025
|
try {
|
|
591
1026
|
const crop = await cropByObb(safeCapturePath, (templateType as any).card_obb);
|
|
1027
|
+
if (isStaleFrame()) {
|
|
1028
|
+
clearTimeout(watchdog);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
592
1031
|
templateBbox = crop.bbox;
|
|
593
1032
|
} catch {}
|
|
594
1033
|
}
|
|
@@ -634,6 +1073,11 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
634
1073
|
}, env);
|
|
635
1074
|
}
|
|
636
1075
|
|
|
1076
|
+
if (isStaleFrame()) {
|
|
1077
|
+
clearTimeout(watchdog);
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
637
1081
|
const bbox = verificationRes?.bbox || templateBbox;
|
|
638
1082
|
const verifiedMrz = verificationRes?.mrz ? JSON.stringify(verificationRes.mrz) : "";
|
|
639
1083
|
|
|
@@ -650,6 +1094,10 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
650
1094
|
const token = await authentification();
|
|
651
1095
|
parsedDictionary = await kycService.formatBarcodeData({ textInput: barcodeData, token }, env);
|
|
652
1096
|
}
|
|
1097
|
+
if (isStaleFrame()) {
|
|
1098
|
+
clearTimeout(watchdog);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
653
1101
|
const verifiedResult: ISilentCaptureResult & { region?: string } = {
|
|
654
1102
|
path: safeCapturePath, templatePath: templatePath, bbox: bbox, success: true,
|
|
655
1103
|
mrz: JSON.stringify(parsedDictionary), barcode: barcodeData, isAnalyzing: false,
|
|
@@ -661,16 +1109,26 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
661
1109
|
if (bbox) setBboxBySide(prev => ({ ...prev, ['back']: bbox as IBbox }));
|
|
662
1110
|
|
|
663
1111
|
isAnalyzingRef.current = false; // 🚨 UNLOCK on success
|
|
664
|
-
await autoCapture(safeCapturePath!, verifiedResult);
|
|
1112
|
+
await autoCapture(safeCapturePath!, verifiedResult, side, frameGeneration);
|
|
665
1113
|
return;
|
|
666
1114
|
}
|
|
667
1115
|
|
|
668
|
-
sdkLogger.info(
|
|
1116
|
+
sdkLogger.info('Verification passed. Starting barcode scan phase.', {
|
|
1117
|
+
platform: Platform.OS,
|
|
1118
|
+
formats: targetBarcodeTypes,
|
|
1119
|
+
});
|
|
669
1120
|
clearTimeout(watchdog);
|
|
670
|
-
|
|
1121
|
+
const pendingImage = { path: safeCapturePath!, templateResponse, templatePath, bbox };
|
|
1122
|
+
barcodeScanSessionRef.current += 1;
|
|
1123
|
+
barcodeCompletedRef.current = false;
|
|
1124
|
+
scanPhaseRef.current = 'barcode';
|
|
1125
|
+
setPendingCAImage(pendingImage);
|
|
671
1126
|
setScanPhase('barcode');
|
|
672
1127
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
|
|
673
1128
|
isAnalyzingRef.current = false; // 🚨 UNLOCK before phase shift
|
|
1129
|
+
if (Platform.OS === 'android') {
|
|
1130
|
+
void decodeBarcodeFromImage(safeCapturePath!, pendingImage, false);
|
|
1131
|
+
}
|
|
674
1132
|
return;
|
|
675
1133
|
}
|
|
676
1134
|
|
|
@@ -684,10 +1142,11 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
684
1142
|
if (bbox) setBboxBySide(prev => ({ ...prev, [side]: bbox }));
|
|
685
1143
|
|
|
686
1144
|
isAnalyzingRef.current = false; // 🚨 UNLOCK on final success
|
|
687
|
-
await autoCapture(safeCapturePath, verifiedResult);
|
|
1145
|
+
await autoCapture(safeCapturePath, verifiedResult, side, frameGeneration);
|
|
688
1146
|
|
|
689
1147
|
} catch (error: any) {
|
|
690
1148
|
clearTimeout(watchdog);
|
|
1149
|
+
if (isStaleFrame()) return;
|
|
691
1150
|
const rawMessage = (typeof error === 'string' ? error : (error?.message || '')).toLowerCase();
|
|
692
1151
|
const statusCode = error?.response?.status || error?.status;
|
|
693
1152
|
|
|
@@ -741,11 +1200,12 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
741
1200
|
useEffect(() => {
|
|
742
1201
|
if (scanPhase === 'barcode' && pendingCAImage && silentCaptureResult.barcode) {
|
|
743
1202
|
const bData = silentCaptureResult.barcode;
|
|
744
|
-
|
|
1203
|
+
const allowShortBarcode = selectedDocumentType?.type === 'health_insurance_card';
|
|
1204
|
+
if (isUsableBarcodeText(bData, allowShortBarcode)) {
|
|
745
1205
|
processCanadianBarcode(bData);
|
|
746
1206
|
}
|
|
747
1207
|
}
|
|
748
|
-
}, [scanPhase, pendingCAImage, silentCaptureResult.barcode]);
|
|
1208
|
+
}, [scanPhase, pendingCAImage, silentCaptureResult.barcode, selectedDocumentType?.type]);
|
|
749
1209
|
|
|
750
1210
|
const handleError = (event: { message: string, name?: string, code?: string }) => {
|
|
751
1211
|
const errString = `${event.message} ${event.name} ${event.code}`.toLowerCase();
|
|
@@ -756,7 +1216,7 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
756
1216
|
|
|
757
1217
|
sdkLogger.error("Hardware Camera Error encountered", { ...event, friendlyMessage }); // 🚨 LOG
|
|
758
1218
|
showAlert(state.currentLanguage === 'en' ? 'Camera Error' : 'Erreur', friendlyMessage);
|
|
759
|
-
|
|
1219
|
+
cancelCurrentCapture();
|
|
760
1220
|
};
|
|
761
1221
|
|
|
762
1222
|
useEffect(() => { actions.showCustomStepper(!showCamera); }, [showCamera]);
|
|
@@ -799,15 +1259,35 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
799
1259
|
</View>
|
|
800
1260
|
)}
|
|
801
1261
|
<EnhancedCameraView
|
|
802
|
-
key={`${currentSide}-${cameraKey}`} showCamera={true} isProcessing={isBusy} cameraType={cameraConfig.cameraType}
|
|
1262
|
+
key={`${currentSide}-${scanPhase}-${cameraKey}`} showCamera={true} isProcessing={isBusy} cameraType={cameraConfig.cameraType}
|
|
803
1263
|
style={{ ...StyleSheet.absoluteFillObject, ...(Platform.OS === 'web' ? { width: '100%', height: '100%', left: 0, top: 0 } as any : {}) }}
|
|
804
|
-
onError={handleError}
|
|
805
|
-
|
|
1264
|
+
onError={handleError}
|
|
1265
|
+
onSilentCapture={(result) => {
|
|
1266
|
+
if (
|
|
1267
|
+
cameraGenerationRef.current !== cameraKey ||
|
|
1268
|
+
currentSideRef.current !== currentSide ||
|
|
1269
|
+
scanPhaseRef.current !== scanPhase
|
|
1270
|
+
) {
|
|
1271
|
+
sdkLogger.info('Discarded a stale camera frame after capture-mode transition', {
|
|
1272
|
+
frameGeneration: cameraKey,
|
|
1273
|
+
activeGeneration: cameraGenerationRef.current,
|
|
1274
|
+
frameSide: currentSide,
|
|
1275
|
+
activeSide: currentSideRef.current,
|
|
1276
|
+
framePhase: scanPhase,
|
|
1277
|
+
activePhase: scanPhaseRef.current,
|
|
1278
|
+
});
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
void handleSilentCapture(result, cameraKey);
|
|
1282
|
+
}}
|
|
1283
|
+
silentCaptureResult={silentCaptureResult}
|
|
1284
|
+
captureStabilizationDelayMs={scanPhase === 'barcode' ? 3000 : 4000}
|
|
1285
|
+
isScanningBarcode={currentSide === 'back' && scanPhase === 'barcode'} barcodeTypes={targetBarcodeTypes}
|
|
806
1286
|
onBarcodeScanned={(barcodeString) => {
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
if (isFullDataDoc && barcodeString.length < 50) return;
|
|
1287
|
+
const allowShortBarcode = selectedDocumentType?.type === 'health_insurance_card';
|
|
1288
|
+
if (!isUsableBarcodeText(barcodeString, allowShortBarcode)) return;
|
|
810
1289
|
if (scannedBarcodeRef.current !== barcodeString) {
|
|
1290
|
+
sdkLogger.info('Barcode detected by live camera scanner', { length: barcodeString.length });
|
|
811
1291
|
scannedBarcodeRef.current = barcodeString;
|
|
812
1292
|
if (scanPhase === 'barcode' && pendingCAImage) {
|
|
813
1293
|
processCanadianBarcode(barcodeString);
|
|
@@ -823,14 +1303,11 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
823
1303
|
stepperProps={{
|
|
824
1304
|
back: () => {
|
|
825
1305
|
if (currentSide === 'back') {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
setSilentCaptureResult((prev) => ({ ...prev, path: '', success: false, isAnalyzing: false, error: '', templatePath: '' }));
|
|
832
|
-
}
|
|
833
|
-
} else { actions.previousComponent(); }
|
|
1306
|
+
returnToFrontReview();
|
|
1307
|
+
} else {
|
|
1308
|
+
cancelCurrentCapture();
|
|
1309
|
+
actions.previousComponent();
|
|
1310
|
+
}
|
|
834
1311
|
},
|
|
835
1312
|
selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '', step: state.currentComponentIndex + 1, totalSteps: state.template.components.length, side: currentSide,
|
|
836
1313
|
}}
|
|
@@ -843,7 +1320,11 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
843
1320
|
<View style={styles.barcodeIconContainer}><Text style={styles.barcodeIcon}>✓</Text></View>
|
|
844
1321
|
<View style={styles.barcodeTextContainer}>
|
|
845
1322
|
<Text style={styles.barcodePromptTitle}>{state.currentLanguage === 'en' ? 'Image Verified' : 'Image Vérifiée'}</Text>
|
|
846
|
-
<Text style={styles.barcodePromptDesc}>
|
|
1323
|
+
<Text style={styles.barcodePromptDesc}>
|
|
1324
|
+
{state.currentLanguage === 'en'
|
|
1325
|
+
? 'Proceed to scan the barcode. Move closer but ensure it is fully visible in the frame.'
|
|
1326
|
+
: 'Procédez au scan du code-barres. Rapprochez-vous, mais assurez-vous qu\'il soit entièrement visible dans le cadre.'}
|
|
1327
|
+
</Text>
|
|
847
1328
|
</View>
|
|
848
1329
|
</View>
|
|
849
1330
|
</View>
|
|
@@ -869,14 +1350,14 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
869
1350
|
<View style={styles.webBottomControlBar}>
|
|
870
1351
|
{silentCaptureResult.error && <View style={styles.floatingErrorBanner}><Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text></View>}
|
|
871
1352
|
<View style={styles.webActionButtonsRow}>
|
|
872
|
-
<TouchableOpacity style={styles.webSecondaryButton} onPress={
|
|
1353
|
+
<TouchableOpacity style={styles.webSecondaryButton} onPress={cancelCurrentCapture}><Text style={styles.webSecondaryButtonText}>Cancel</Text></TouchableOpacity>
|
|
873
1354
|
<TouchableOpacity style={styles.webPrimaryButton} onPress={refreshCamera}><Text style={styles.webPrimaryButtonText}>↻ Refresh Camera</Text></TouchableOpacity>
|
|
874
1355
|
</View>
|
|
875
1356
|
</View>
|
|
876
1357
|
) : !isBusy ? (
|
|
877
1358
|
<View style={styles.escapeHatchContainer}>
|
|
878
1359
|
<TouchableOpacity style={styles.fallbackRefreshButton} onPress={refreshCamera}><Text style={styles.fallbackRefreshText}>↻ Refresh Camera</Text></TouchableOpacity>
|
|
879
|
-
<TouchableOpacity style={[styles.fallbackRefreshButton, { marginTop: 15, backgroundColor: 'rgba(220, 38, 38, 0.8)', borderColor: '#DC2626' }]} onPress={
|
|
1360
|
+
<TouchableOpacity style={[styles.fallbackRefreshButton, { marginTop: 15, backgroundColor: 'rgba(220, 38, 38, 0.8)', borderColor: '#DC2626' }]} onPress={cancelCurrentCapture}><Text style={styles.fallbackRefreshText}>Cancel / Go Back</Text></TouchableOpacity>
|
|
880
1361
|
</View>
|
|
881
1362
|
) : null}
|
|
882
1363
|
{silentCaptureResult.error && !isBusy && Platform.OS !== 'web' ? (
|
|
@@ -933,14 +1414,10 @@ const handleSilentCapture = async (result: { success: boolean; path?: string; er
|
|
|
933
1414
|
documentType: selectedDocumentType.type
|
|
934
1415
|
});
|
|
935
1416
|
actions.nextComponent();
|
|
1417
|
+
} else {
|
|
1418
|
+
sdkLogger.info("User finished front verification, proceeding to back");
|
|
1419
|
+
startBackCapture();
|
|
936
1420
|
}
|
|
937
|
-
else {
|
|
938
|
-
sdkLogger.info("User finished front verification, proceeding to back");
|
|
939
|
-
setShowCamera(true);
|
|
940
|
-
refreshCamera();
|
|
941
|
-
setCurrentSide('back');
|
|
942
|
-
setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '', templatePath: undefined }); setIsProcessingCapture(false); setProcessingImagePath(null); setScanPhase('image'); setPendingCAImage(null);
|
|
943
|
-
}
|
|
944
1421
|
}}
|
|
945
1422
|
variant="primary" size="large" fullWidth />
|
|
946
1423
|
</>
|
|
@@ -1234,4 +1711,4 @@ const styles = StyleSheet.create({
|
|
|
1234
1711
|
color: '#EF4444', // Red to indicate urgency
|
|
1235
1712
|
fontWeight: '600',
|
|
1236
1713
|
},
|
|
1237
|
-
});
|
|
1714
|
+
});
|