@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,21 +15,88 @@ 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
|
const ISO_TO_COUNTRY_NAME = {
|
|
23
24
|
'KE': 'Kenya', 'CM': 'Cameroon', 'NG': 'Nigeria', 'CA': 'Canada',
|
|
24
25
|
'FR': 'France', 'GH': 'Ghana', 'ZA': 'South Africa', 'GB': 'Britain',
|
|
25
26
|
'CI': 'Ivory Coast', 'SN': 'Senegal', 'TG': 'Togo', 'ML': 'Mali'
|
|
26
27
|
};
|
|
28
|
+
const BARCODE_RESPONSE_TEXT_KEYS = [
|
|
29
|
+
'barcode_data',
|
|
30
|
+
'raw_barcode',
|
|
31
|
+
'raw_data',
|
|
32
|
+
'decoded_data',
|
|
33
|
+
'decoded_text',
|
|
34
|
+
'barcode',
|
|
35
|
+
'text',
|
|
36
|
+
'value',
|
|
37
|
+
'data',
|
|
38
|
+
'detail',
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* The image decoder has returned a few slightly different response envelopes
|
|
42
|
+
* over time. Keep that API variation out of the capture flow and only accept
|
|
43
|
+
* values from fields that are known to contain the raw barcode payload.
|
|
44
|
+
*/
|
|
45
|
+
const getDecodedBarcodeText = (response, depth = 0) => {
|
|
46
|
+
if (typeof response === 'string') {
|
|
47
|
+
return response.trim().length > 0 ? response : null;
|
|
48
|
+
}
|
|
49
|
+
if (!response || typeof response !== 'object' || depth > 3)
|
|
50
|
+
return null;
|
|
51
|
+
if (Array.isArray(response)) {
|
|
52
|
+
for (const item of response) {
|
|
53
|
+
const value = getDecodedBarcodeText(item, depth + 1);
|
|
54
|
+
if (value)
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const record = response;
|
|
60
|
+
for (const key of BARCODE_RESPONSE_TEXT_KEYS) {
|
|
61
|
+
const value = record[key];
|
|
62
|
+
if (typeof value === 'string' && value.trim().length > 0)
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
for (const key of ['result', 'data', 'payload', 'barcodes', 'results']) {
|
|
66
|
+
const nested = record[key];
|
|
67
|
+
if (nested && typeof nested === 'object') {
|
|
68
|
+
const value = getDecodedBarcodeText(nested, depth + 1);
|
|
69
|
+
if (value)
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
};
|
|
75
|
+
const isUsableBarcodeText = (value, allowShortBarcode) => {
|
|
76
|
+
if (typeof value !== 'string')
|
|
77
|
+
return false;
|
|
78
|
+
const trimmed = value.trim();
|
|
79
|
+
const isDecoderError = /^(no (?:bar)?code|barcode (?:not|was not)|unable to decode|failed to decode|error\b)/i.test(trimmed);
|
|
80
|
+
return !isDecoderError && trimmed.length > 0 && (allowShortBarcode || value.length > 50);
|
|
81
|
+
};
|
|
27
82
|
const SHORT_TO_LONG_REGION = {
|
|
28
83
|
"AB": "Alberta", "BC": "British Columbia", "MB": "Manitoba", "NB": "New Brunswick",
|
|
29
84
|
"NL": "Newfoundland and Labrador", "NT": "Northwest Territories", "NS": "Nova Scotia",
|
|
30
85
|
"NU": "Nunavut", "ON": "Ontario", "PE": "Prince Edward Island", "QC": "Quebec",
|
|
31
86
|
"SK": "Saskatchewan", "YT": "Yukon"
|
|
32
87
|
};
|
|
88
|
+
const DOCUMENT_TYPE_MAPPING = {
|
|
89
|
+
nationalId: 'national_id',
|
|
90
|
+
national_id: 'national_id',
|
|
91
|
+
identity_card: 'national_id',
|
|
92
|
+
passport: 'passport',
|
|
93
|
+
driversLicense: 'drivers_licence',
|
|
94
|
+
drivers_licence: 'drivers_licence',
|
|
95
|
+
residencePermit: 'permanent_residence',
|
|
96
|
+
permanent_residence: 'permanent_residence',
|
|
97
|
+
healthInsuranceCard: 'health_insurance_card',
|
|
98
|
+
health_insurance_card: 'health_insurance_card',
|
|
99
|
+
};
|
|
33
100
|
const ExpiringCodeTimer = ({ initialCode, language }) => {
|
|
34
101
|
const [timeLeft, setTimeLeft] = useState(300);
|
|
35
102
|
useEffect(() => {
|
|
@@ -62,6 +129,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
62
129
|
const [isProcessingCapture, setIsProcessingCapture] = useState(false);
|
|
63
130
|
const [processingImagePath, setProcessingImagePath] = useState(null);
|
|
64
131
|
const [cameraKey, setCameraKey] = useState(0);
|
|
132
|
+
const cameraGenerationRef = useRef(0);
|
|
65
133
|
const [isRebootingCamera, setIsRebootingCamera] = useState(false);
|
|
66
134
|
const [cameraType, setCameraType] = useState('back');
|
|
67
135
|
const [showQRModal, setShowQRModal] = useState(false);
|
|
@@ -71,6 +139,13 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
71
139
|
const [scanPhase, setScanPhase] = useState('image');
|
|
72
140
|
const [pendingCAImage, setPendingCAImage] = useState(null);
|
|
73
141
|
const scannedBarcodeRef = useRef(null);
|
|
142
|
+
const barcodeImageDecodeSessionRef = useRef(null);
|
|
143
|
+
const barcodeServiceDecodeSessionRef = useRef(null);
|
|
144
|
+
const barcodeServiceAttemptsRef = useRef({ session: null, count: 0 });
|
|
145
|
+
const barcodeFinalizingRef = useRef(false);
|
|
146
|
+
const barcodeCompletedRef = useRef(false);
|
|
147
|
+
const barcodeScanSessionRef = useRef(0);
|
|
148
|
+
const nativeBarcodeDecoderWarningShownRef = useRef(false);
|
|
74
149
|
const [generatedCode, setGeneratedCode] = useState(null);
|
|
75
150
|
const [isGeneratingCode, setIsGeneratingCode] = useState(false);
|
|
76
151
|
const { actions, state, env, apiKey } = useTemplateKYCFlowContext();
|
|
@@ -84,19 +159,13 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
84
159
|
const countrySelectionComponent = state.template.components.find(c => c.type === 'country_selection');
|
|
85
160
|
return countrySelectionComponent ? state.componentData[countrySelectionComponent.id] : null;
|
|
86
161
|
}, [state.template.components, state.componentData]);
|
|
87
|
-
const documentTypeMapping = {
|
|
88
|
-
'nationalId': 'national_id', 'national_id': 'national_id', 'identity_card': 'national_id',
|
|
89
|
-
'passport': 'passport', 'driversLicense': 'drivers_licence', 'drivers_licence': 'drivers_licence',
|
|
90
|
-
'residencePermit': 'permanent_residence', 'permanent_residence': 'permanent_residence',
|
|
91
|
-
'healthInsuranceCard': 'health_insurance_card', 'health_insurance_card': 'health_insurance_card',
|
|
92
|
-
};
|
|
93
162
|
const selectedDocumentType = useMemo(() => {
|
|
94
163
|
if (!countrySelectionData?.documentType)
|
|
95
164
|
return null;
|
|
96
165
|
const backendDocType = countrySelectionData.documentType;
|
|
97
|
-
const mappedType =
|
|
166
|
+
const mappedType = DOCUMENT_TYPE_MAPPING[backendDocType] || backendDocType;
|
|
98
167
|
return { type: mappedType, region: countrySelectionData.region || 'root' };
|
|
99
|
-
}, [countrySelectionData
|
|
168
|
+
}, [countrySelectionData]);
|
|
100
169
|
const countryData = useMemo(() => countrySelectionData, [countrySelectionData]);
|
|
101
170
|
// ==========================================
|
|
102
171
|
// 🚨 CRITICAL FIX: REF MIRRORS FOR IOS BRIDGE
|
|
@@ -160,26 +229,52 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
160
229
|
}, [countryData?.code, selectedDocumentType?.type]);
|
|
161
230
|
const targetBarcodeTypes = useMemo(() => {
|
|
162
231
|
const docType = selectedDocumentType?.type;
|
|
163
|
-
if (docType === 'health_insurance_card')
|
|
164
|
-
return [
|
|
232
|
+
if (docType === 'health_insurance_card') {
|
|
233
|
+
return [
|
|
234
|
+
'pdf-417', 'code-128', 'code-39', 'code-93', 'codabar',
|
|
235
|
+
// Vision Camera's Android implementation rejects `itf-14`; its `itf`
|
|
236
|
+
// reader already covers ITF-14 payloads.
|
|
237
|
+
'ean-13', 'ean-8', 'itf', 'upc-a', 'upc-e',
|
|
238
|
+
];
|
|
239
|
+
}
|
|
165
240
|
return ['pdf-417'];
|
|
166
|
-
}, [selectedDocumentType]);
|
|
241
|
+
}, [selectedDocumentType?.type]);
|
|
167
242
|
const refreshCamera = () => {
|
|
243
|
+
const nextCameraGeneration = cameraGenerationRef.current + 1;
|
|
244
|
+
cameraGenerationRef.current = nextCameraGeneration;
|
|
245
|
+
barcodeScanSessionRef.current += 1;
|
|
246
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
247
|
+
barcodeFinalizingRef.current = false;
|
|
248
|
+
barcodeCompletedRef.current = false;
|
|
249
|
+
scannedBarcodeRef.current = null;
|
|
250
|
+
isAnalyzingRef.current = false;
|
|
251
|
+
isProcessingCaptureRef.current = false;
|
|
252
|
+
setIsProcessingCapture(false);
|
|
253
|
+
setProcessingImagePath(null);
|
|
168
254
|
setIsRebootingCamera(true);
|
|
169
|
-
if (
|
|
255
|
+
if (scanPhaseRef.current === 'barcode') {
|
|
256
|
+
const activeSide = currentSideRef.current;
|
|
257
|
+
scanPhaseRef.current = 'image';
|
|
170
258
|
setScanPhase('image');
|
|
171
259
|
setPendingCAImage(null);
|
|
172
|
-
|
|
173
|
-
|
|
260
|
+
const nextImages = { ...capturedImagesRef.current };
|
|
261
|
+
delete nextImages[activeSide];
|
|
262
|
+
capturedImagesRef.current = nextImages;
|
|
263
|
+
setCapturedImages(nextImages);
|
|
174
264
|
if (value) {
|
|
175
265
|
const newValue = { ...value };
|
|
176
|
-
delete newValue[
|
|
266
|
+
delete newValue[activeSide];
|
|
177
267
|
onValueChange(newValue);
|
|
178
268
|
}
|
|
179
269
|
setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '', templatePath: '', mrz: '', bbox: undefined });
|
|
180
270
|
}
|
|
271
|
+
else {
|
|
272
|
+
setSilentCaptureResult(prev => ({ ...prev, success: false, isAnalyzing: false, error: '' }));
|
|
273
|
+
}
|
|
181
274
|
setTimeout(() => {
|
|
182
|
-
|
|
275
|
+
if (cameraGenerationRef.current !== nextCameraGeneration)
|
|
276
|
+
return;
|
|
277
|
+
setCameraKey(nextCameraGeneration);
|
|
183
278
|
setIsRebootingCamera(false);
|
|
184
279
|
}, 1000);
|
|
185
280
|
};
|
|
@@ -190,6 +285,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
190
285
|
useEffect(() => {
|
|
191
286
|
if (value && Object.keys(value).length > 0 && JSON.stringify(value) !== JSON.stringify(capturedImages)) {
|
|
192
287
|
const updatedImages = value;
|
|
288
|
+
capturedImagesRef.current = updatedImages;
|
|
193
289
|
setCapturedImages(updatedImages);
|
|
194
290
|
const currentImageData = updatedImages[currentSide];
|
|
195
291
|
if (currentImageData?.dir) {
|
|
@@ -232,12 +328,124 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
232
328
|
actions.showCustomStepper(false);
|
|
233
329
|
}
|
|
234
330
|
};
|
|
331
|
+
const startBackCapture = () => {
|
|
332
|
+
sdkLogger.info('Preparing camera for back document capture');
|
|
333
|
+
barcodeScanSessionRef.current += 1;
|
|
334
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
335
|
+
barcodeFinalizingRef.current = false;
|
|
336
|
+
barcodeCompletedRef.current = false;
|
|
337
|
+
scannedBarcodeRef.current = null;
|
|
338
|
+
isAnalyzingRef.current = false;
|
|
339
|
+
isProcessingCaptureRef.current = false;
|
|
340
|
+
currentSideRef.current = 'back';
|
|
341
|
+
scanPhaseRef.current = 'image';
|
|
342
|
+
const nextImages = { ...capturedImagesRef.current };
|
|
343
|
+
delete nextImages.back;
|
|
344
|
+
capturedImagesRef.current = nextImages;
|
|
345
|
+
setCapturedImages(nextImages);
|
|
346
|
+
setBboxBySide(prev => {
|
|
347
|
+
const next = { ...prev };
|
|
348
|
+
delete next.back;
|
|
349
|
+
return next;
|
|
350
|
+
});
|
|
351
|
+
if (value && Object.prototype.hasOwnProperty.call(value, 'back')) {
|
|
352
|
+
const nextValue = { ...value };
|
|
353
|
+
delete nextValue.back;
|
|
354
|
+
onValueChange(nextValue);
|
|
355
|
+
}
|
|
356
|
+
setSilentCaptureResult({
|
|
357
|
+
success: false,
|
|
358
|
+
isAnalyzing: false,
|
|
359
|
+
path: '',
|
|
360
|
+
error: '',
|
|
361
|
+
templatePath: '',
|
|
362
|
+
mrz: '',
|
|
363
|
+
barcode: undefined,
|
|
364
|
+
bbox: undefined,
|
|
365
|
+
});
|
|
366
|
+
setPendingCAImage(null);
|
|
367
|
+
setProcessingImagePath(null);
|
|
368
|
+
setIsProcessingCapture(false);
|
|
369
|
+
setIsRebootingCamera(false);
|
|
370
|
+
setCurrentSide('back');
|
|
371
|
+
cameraGenerationRef.current += 1;
|
|
372
|
+
setCameraKey(cameraGenerationRef.current);
|
|
373
|
+
setShowCamera(true);
|
|
374
|
+
actions.showCustomStepper(false);
|
|
375
|
+
};
|
|
376
|
+
const cancelCurrentCapture = () => {
|
|
377
|
+
cameraGenerationRef.current += 1;
|
|
378
|
+
barcodeScanSessionRef.current += 1;
|
|
379
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
380
|
+
barcodeFinalizingRef.current = false;
|
|
381
|
+
barcodeCompletedRef.current = false;
|
|
382
|
+
scannedBarcodeRef.current = null;
|
|
383
|
+
isAnalyzingRef.current = false;
|
|
384
|
+
isProcessingCaptureRef.current = false;
|
|
385
|
+
scanPhaseRef.current = 'image';
|
|
386
|
+
const savedImage = capturedImagesRef.current[currentSideRef.current];
|
|
387
|
+
setScanPhase('image');
|
|
388
|
+
setPendingCAImage(null);
|
|
389
|
+
setIsProcessingCapture(false);
|
|
390
|
+
setProcessingImagePath(null);
|
|
391
|
+
setIsRebootingCamera(false);
|
|
392
|
+
setShowCamera(false);
|
|
393
|
+
setSilentCaptureResult(savedImage?.dir
|
|
394
|
+
? {
|
|
395
|
+
path: savedImage.dir,
|
|
396
|
+
success: true,
|
|
397
|
+
isAnalyzing: false,
|
|
398
|
+
error: '',
|
|
399
|
+
mrz: savedImage.mrz || '',
|
|
400
|
+
templatePath: savedImage.templatePath || '',
|
|
401
|
+
bbox: bboxBySide[currentSideRef.current],
|
|
402
|
+
}
|
|
403
|
+
: { path: '', success: false, isAnalyzing: false, error: '', templatePath: '', mrz: '', bbox: undefined });
|
|
404
|
+
};
|
|
405
|
+
const returnToFrontReview = () => {
|
|
406
|
+
cameraGenerationRef.current += 1;
|
|
407
|
+
barcodeScanSessionRef.current += 1;
|
|
408
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
409
|
+
barcodeFinalizingRef.current = false;
|
|
410
|
+
barcodeCompletedRef.current = false;
|
|
411
|
+
scannedBarcodeRef.current = null;
|
|
412
|
+
isAnalyzingRef.current = false;
|
|
413
|
+
isProcessingCaptureRef.current = false;
|
|
414
|
+
currentSideRef.current = 'front';
|
|
415
|
+
scanPhaseRef.current = 'image';
|
|
416
|
+
const frontImage = capturedImagesRef.current.front;
|
|
417
|
+
setScanPhase('image');
|
|
418
|
+
setPendingCAImage(null);
|
|
419
|
+
setCurrentSide('front');
|
|
420
|
+
setShowCamera(false);
|
|
421
|
+
setIsProcessingCapture(false);
|
|
422
|
+
setProcessingImagePath(null);
|
|
423
|
+
setIsRebootingCamera(false);
|
|
424
|
+
setSilentCaptureResult(frontImage?.dir
|
|
425
|
+
? {
|
|
426
|
+
path: frontImage.dir,
|
|
427
|
+
success: true,
|
|
428
|
+
isAnalyzing: false,
|
|
429
|
+
error: '',
|
|
430
|
+
mrz: frontImage.mrz || '',
|
|
431
|
+
templatePath: frontImage.templatePath || '',
|
|
432
|
+
bbox: bboxBySide.front,
|
|
433
|
+
}
|
|
434
|
+
: { path: '', success: false, isAnalyzing: false, error: '', templatePath: '', mrz: '', bbox: undefined });
|
|
435
|
+
};
|
|
235
436
|
const cameraConfig = useMemo(() => {
|
|
236
437
|
let instructions = selectedDocumentType
|
|
237
438
|
? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr)
|
|
238
439
|
: getLocalizedText(component.instructions);
|
|
239
|
-
if (scanPhase === 'barcode')
|
|
440
|
+
if (scanPhase === 'barcode') {
|
|
240
441
|
instructions = '';
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
const stabilityHint = locale === 'en'
|
|
445
|
+
? 'Position the entire ID, then hold steady. Capture starts automatically.'
|
|
446
|
+
: 'Positionnez la pièce entière, puis restez immobile. La capture démarre automatiquement.';
|
|
447
|
+
instructions = instructions ? `${instructions}\n${stabilityHint}` : stabilityHint;
|
|
448
|
+
}
|
|
241
449
|
const isLandscape = screenWidth > screenHeight;
|
|
242
450
|
let xMin, xMax, yMin, yMax;
|
|
243
451
|
if (Platform.OS === 'web') {
|
|
@@ -272,21 +480,38 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
272
480
|
};
|
|
273
481
|
}, [selectedDocumentType, locale, component.instructions, cameraType, screenWidth, screenHeight, scanPhase]);
|
|
274
482
|
const retakePicture = (sideToRetake) => {
|
|
483
|
+
barcodeScanSessionRef.current += 1;
|
|
484
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
485
|
+
barcodeFinalizingRef.current = false;
|
|
486
|
+
barcodeCompletedRef.current = false;
|
|
487
|
+
scannedBarcodeRef.current = null;
|
|
488
|
+
isAnalyzingRef.current = false;
|
|
489
|
+
isProcessingCaptureRef.current = false;
|
|
490
|
+
currentSideRef.current = sideToRetake;
|
|
491
|
+
scanPhaseRef.current = 'image';
|
|
492
|
+
setCurrentSide(sideToRetake);
|
|
275
493
|
setIsProcessingCapture(false);
|
|
276
494
|
setProcessingImagePath(null);
|
|
277
|
-
scannedBarcodeRef.current = null;
|
|
278
495
|
setScanPhase('image');
|
|
279
496
|
setPendingCAImage(null);
|
|
280
497
|
setSilentCaptureResult({ path: '', success: false, isAnalyzing: false, error: '', templatePath: '', mrz: '', bbox: undefined });
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
setCapturedImages(
|
|
498
|
+
const nextImages = { ...capturedImagesRef.current };
|
|
499
|
+
delete nextImages[sideToRetake];
|
|
500
|
+
capturedImagesRef.current = nextImages;
|
|
501
|
+
setCapturedImages(nextImages);
|
|
502
|
+
setBboxBySide(prev => {
|
|
503
|
+
const next = { ...prev };
|
|
504
|
+
delete next[sideToRetake];
|
|
505
|
+
return next;
|
|
506
|
+
});
|
|
285
507
|
if (value) {
|
|
286
508
|
const newValue = { ...value };
|
|
287
509
|
delete newValue[sideToRetake];
|
|
288
510
|
onValueChange(newValue);
|
|
289
511
|
}
|
|
512
|
+
setShowCamera(true);
|
|
513
|
+
refreshCamera();
|
|
514
|
+
actions.showCustomStepper(false);
|
|
290
515
|
};
|
|
291
516
|
const getCurrentSideVerification = (side, countryKey, cData) => {
|
|
292
517
|
const rawDocType = cData?.documentType;
|
|
@@ -339,9 +564,10 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
339
564
|
const found = mapping[regionKey].find((item) => item.py_file === pyName);
|
|
340
565
|
return found?.[side] || null;
|
|
341
566
|
};
|
|
342
|
-
const autoCapture = async (capturePath, verified) => {
|
|
343
|
-
if (
|
|
567
|
+
const autoCapture = async (capturePath, verified, captureSide = currentSideRef.current, captureGeneration = cameraGenerationRef.current) => {
|
|
568
|
+
if (isProcessingCaptureRef.current)
|
|
344
569
|
return;
|
|
570
|
+
isProcessingCaptureRef.current = true;
|
|
345
571
|
setIsProcessingCapture(true);
|
|
346
572
|
const safeCapturePath = (Platform.OS === 'ios' && !capturePath.startsWith('file://')) ? `file://${capturePath}` : capturePath;
|
|
347
573
|
setProcessingImagePath(safeCapturePath);
|
|
@@ -374,11 +600,14 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
374
600
|
}
|
|
375
601
|
const safeCroppedPath = (Platform.OS === 'ios' && !imagePathForUpload.startsWith('file://')) ? `file://${imagePathForUpload}` : imagePathForUpload;
|
|
376
602
|
const base64 = await pathToBase64(safeCroppedPath);
|
|
377
|
-
|
|
603
|
+
if (cameraGenerationRef.current !== captureGeneration)
|
|
604
|
+
return;
|
|
605
|
+
const side = captureSide;
|
|
378
606
|
const newImages = {
|
|
379
607
|
...capturedImagesRef.current,
|
|
380
608
|
[side]: { dir: safeCroppedPath, file: base64, mrz: verified.mrz || "", templatePath: verified.templatePath },
|
|
381
609
|
};
|
|
610
|
+
capturedImagesRef.current = newImages;
|
|
382
611
|
setCapturedImages(newImages);
|
|
383
612
|
sdkLogger.info(`User successfully captured ${side} document`);
|
|
384
613
|
if (verified.country && verified.documentType) {
|
|
@@ -387,20 +616,27 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
387
616
|
onValueChange({ ...newImages, country: verified.country, documentType: resolvedDocumentType, ...(verified.region ? { region: verified.region } : {}) });
|
|
388
617
|
}
|
|
389
618
|
setTimeout(() => {
|
|
619
|
+
if (cameraGenerationRef.current !== captureGeneration)
|
|
620
|
+
return;
|
|
390
621
|
setShowCamera(false);
|
|
391
622
|
actions.showCustomStepper(true);
|
|
392
623
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
|
|
624
|
+
isProcessingCaptureRef.current = false;
|
|
393
625
|
setIsProcessingCapture(false);
|
|
394
626
|
setProcessingImagePath(null);
|
|
627
|
+
scanPhaseRef.current = 'image';
|
|
395
628
|
setScanPhase('image');
|
|
396
629
|
setPendingCAImage(null);
|
|
397
630
|
}, 600);
|
|
398
631
|
}
|
|
399
632
|
catch (e) {
|
|
633
|
+
if (cameraGenerationRef.current !== captureGeneration)
|
|
634
|
+
return;
|
|
400
635
|
sdkLogger.error(`Auto-capture formatting/saving failed`, { error: e.message });
|
|
401
636
|
const friendlyError = state.currentLanguage === 'en' ? 'Unable to process document. Please try again.' : 'Impossible de traiter le document. Veuillez réessayer.';
|
|
402
637
|
showAlert('Error', friendlyError);
|
|
403
638
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false }));
|
|
639
|
+
isProcessingCaptureRef.current = false;
|
|
404
640
|
setIsProcessingCapture(false);
|
|
405
641
|
setProcessingImagePath(null);
|
|
406
642
|
}
|
|
@@ -433,13 +669,25 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
433
669
|
return method.includes('BARCODE') || method.includes('2D');
|
|
434
670
|
});
|
|
435
671
|
};
|
|
436
|
-
const processCanadianBarcode = async (barcodeString) => {
|
|
437
|
-
if (
|
|
672
|
+
const processCanadianBarcode = async (barcodeString, pendingImage = pendingCAImage) => {
|
|
673
|
+
if (scanPhaseRef.current !== 'barcode' ||
|
|
674
|
+
isProcessingCaptureRef.current ||
|
|
675
|
+
barcodeFinalizingRef.current ||
|
|
676
|
+
barcodeCompletedRef.current ||
|
|
677
|
+
!pendingImage)
|
|
438
678
|
return;
|
|
679
|
+
const docType = selectedDocTypeRef.current;
|
|
680
|
+
const cData = countryDataRef.current;
|
|
681
|
+
if (!docType || !isUsableBarcodeText(barcodeString, docType.type === 'health_insurance_card'))
|
|
682
|
+
return;
|
|
683
|
+
const scanSession = barcodeScanSessionRef.current;
|
|
684
|
+
const captureGeneration = cameraGenerationRef.current;
|
|
685
|
+
const isStaleBarcode = () => (barcodeScanSessionRef.current !== scanSession ||
|
|
686
|
+
cameraGenerationRef.current !== captureGeneration ||
|
|
687
|
+
scanPhaseRef.current !== 'barcode');
|
|
688
|
+
barcodeFinalizingRef.current = true;
|
|
439
689
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: true, error: '' }));
|
|
440
690
|
try {
|
|
441
|
-
const docType = selectedDocTypeRef.current;
|
|
442
|
-
const cData = countryDataRef.current;
|
|
443
691
|
const isHIC = docType.type === 'health_insurance_card';
|
|
444
692
|
let parsedDictionary;
|
|
445
693
|
if (isHIC && barcodeString.length < 50) {
|
|
@@ -449,28 +697,166 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
449
697
|
const token = await authentification();
|
|
450
698
|
parsedDictionary = await kycService.formatBarcodeData({ textInput: barcodeString, token }, env);
|
|
451
699
|
}
|
|
700
|
+
if (isStaleBarcode())
|
|
701
|
+
return;
|
|
452
702
|
if (!parsedDictionary)
|
|
453
703
|
throw new Error("Could not parse the barcode data.");
|
|
704
|
+
barcodeCompletedRef.current = true;
|
|
454
705
|
const verifiedResult = {
|
|
455
|
-
path:
|
|
706
|
+
path: pendingImage.path, templatePath: pendingImage.templatePath, bbox: pendingImage.bbox, success: true,
|
|
456
707
|
mrz: JSON.stringify(parsedDictionary), barcode: barcodeString, isAnalyzing: false,
|
|
457
708
|
country: cData?.code, documentType: docType.type, region: docType.region !== 'root' ? docType.region : undefined,
|
|
458
709
|
};
|
|
459
710
|
setSilentCaptureResult(verifiedResult);
|
|
460
|
-
if (
|
|
461
|
-
const safeBbox =
|
|
711
|
+
if (pendingImage.bbox) {
|
|
712
|
+
const safeBbox = pendingImage.bbox;
|
|
462
713
|
setBboxBySide(prev => ({ ...prev, ['back']: safeBbox }));
|
|
463
714
|
}
|
|
464
|
-
await autoCapture(
|
|
715
|
+
await autoCapture(pendingImage.path, verifiedResult, 'back', captureGeneration);
|
|
465
716
|
}
|
|
466
717
|
catch (error) {
|
|
467
|
-
|
|
468
|
-
|
|
718
|
+
if (isStaleBarcode())
|
|
719
|
+
return;
|
|
720
|
+
barcodeCompletedRef.current = false;
|
|
469
721
|
scannedBarcodeRef.current = null;
|
|
470
|
-
|
|
722
|
+
sdkLogger.warn('Decoded barcode could not be formatted; continuing to scan', { error: error?.message });
|
|
723
|
+
setSilentCaptureResult(prev => ({
|
|
724
|
+
...prev,
|
|
725
|
+
isAnalyzing: false,
|
|
726
|
+
success: false,
|
|
727
|
+
error: state.currentLanguage === 'en'
|
|
728
|
+
? 'Barcode detected but not readable yet. Hold it steady and try again.'
|
|
729
|
+
: 'Code-barres détecté mais pas encore lisible. Maintenez-le stable et réessayez.',
|
|
730
|
+
}));
|
|
731
|
+
}
|
|
732
|
+
finally {
|
|
733
|
+
if (barcodeScanSessionRef.current === scanSession) {
|
|
734
|
+
barcodeFinalizingRef.current = false;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
/**
|
|
739
|
+
* Live scanners favor speed and can receive a relatively small analysis
|
|
740
|
+
* frame. This fallback sends the camera's full-resolution still image to the
|
|
741
|
+
* dedicated decoder, which is much more reliable for high-density PDF417.
|
|
742
|
+
*/
|
|
743
|
+
const decodeBarcodeFromImage = async (imagePath, pendingImage = pendingCAImage, allowServiceFallback = true) => {
|
|
744
|
+
if (scanPhaseRef.current !== 'barcode' ||
|
|
745
|
+
!pendingImage ||
|
|
746
|
+
barcodeImageDecodeSessionRef.current === barcodeScanSessionRef.current ||
|
|
747
|
+
barcodeFinalizingRef.current ||
|
|
748
|
+
barcodeCompletedRef.current ||
|
|
749
|
+
isProcessingCaptureRef.current)
|
|
750
|
+
return;
|
|
751
|
+
const docType = selectedDocTypeRef.current;
|
|
752
|
+
if (!docType)
|
|
753
|
+
return;
|
|
754
|
+
const scanSession = barcodeScanSessionRef.current;
|
|
755
|
+
barcodeImageDecodeSessionRef.current = scanSession;
|
|
756
|
+
sdkLogger.info('Attempting high-resolution barcode decode', {
|
|
757
|
+
platform: Platform.OS,
|
|
758
|
+
session: scanSession,
|
|
759
|
+
});
|
|
760
|
+
try {
|
|
761
|
+
if (Platform.OS === 'android' && SanctumKeySdkModule.decodeBarcodeFromImage) {
|
|
762
|
+
try {
|
|
763
|
+
const localBarcodeText = await SanctumKeySdkModule.decodeBarcodeFromImage(imagePath, targetBarcodeTypes);
|
|
764
|
+
if (scanSession !== barcodeScanSessionRef.current || scanPhaseRef.current !== 'barcode')
|
|
765
|
+
return;
|
|
766
|
+
if (isUsableBarcodeText(localBarcodeText, docType.type === 'health_insurance_card')) {
|
|
767
|
+
scannedBarcodeRef.current = localBarcodeText;
|
|
768
|
+
sdkLogger.info('Barcode decoded by enhanced on-device fallback', {
|
|
769
|
+
length: localBarcodeText.length,
|
|
770
|
+
});
|
|
771
|
+
await processCanadianBarcode(localBarcodeText, pendingImage);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
sdkLogger.info('On-device decoders did not find a usable barcode in this frame', {
|
|
775
|
+
session: scanSession,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
catch (localError) {
|
|
779
|
+
sdkLogger.info('Enhanced on-device barcode fallback could not decode this frame', {
|
|
780
|
+
error: String(errorMessage(localError) || 'Unknown local decoder error'),
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
else if (Platform.OS === 'android' && !nativeBarcodeDecoderWarningShownRef.current) {
|
|
785
|
+
nativeBarcodeDecoderWarningShownRef.current = true;
|
|
786
|
+
sdkLogger.warn('Enhanced on-device barcode decoder is unavailable; rebuild the Android app.');
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
finally {
|
|
790
|
+
if (barcodeImageDecodeSessionRef.current === scanSession) {
|
|
791
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (scanSession !== barcodeScanSessionRef.current || scanPhaseRef.current !== 'barcode')
|
|
795
|
+
return;
|
|
796
|
+
if (!allowServiceFallback) {
|
|
797
|
+
sdkLogger.info('Verified back image did not yield a barcode locally; waiting for a close-up frame');
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
// Do not let a slow network request block local decoding of subsequent
|
|
801
|
+
// high-resolution frames. At most two service calls are made per session.
|
|
802
|
+
if (barcodeServiceDecodeSessionRef.current === scanSession)
|
|
803
|
+
return;
|
|
804
|
+
if (barcodeServiceAttemptsRef.current.session !== scanSession) {
|
|
805
|
+
barcodeServiceAttemptsRef.current = { session: scanSession, count: 0 };
|
|
806
|
+
}
|
|
807
|
+
if (barcodeServiceAttemptsRef.current.count >= 2)
|
|
808
|
+
return;
|
|
809
|
+
barcodeServiceAttemptsRef.current.count += 1;
|
|
810
|
+
barcodeServiceDecodeSessionRef.current = scanSession;
|
|
811
|
+
try {
|
|
812
|
+
const token = await authentification();
|
|
813
|
+
if (scanSession !== barcodeScanSessionRef.current || scanPhaseRef.current !== 'barcode')
|
|
814
|
+
return;
|
|
815
|
+
const backendDocType = GovernmentDocumentTypeShorted[docType.type] || docType.type;
|
|
816
|
+
const response = await kycService.extractBarcode({
|
|
817
|
+
fileUri: imagePath,
|
|
818
|
+
token,
|
|
819
|
+
docType: backendDocType,
|
|
820
|
+
postfix: 'back',
|
|
821
|
+
timeoutMs: 15000,
|
|
822
|
+
}, env);
|
|
823
|
+
if (scanSession !== barcodeScanSessionRef.current || scanPhaseRef.current !== 'barcode')
|
|
824
|
+
return;
|
|
825
|
+
const barcodeText = getDecodedBarcodeText(response);
|
|
826
|
+
if (isUsableBarcodeText(barcodeText, docType.type === 'health_insurance_card')) {
|
|
827
|
+
scannedBarcodeRef.current = barcodeText;
|
|
828
|
+
sdkLogger.info('Barcode decoded by the high-resolution service fallback', { length: barcodeText.length });
|
|
829
|
+
await processCanadianBarcode(barcodeText, pendingImage);
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
sdkLogger.info('Barcode service returned no usable data; local scanning remains active', {
|
|
833
|
+
session: scanSession,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
catch (error) {
|
|
838
|
+
sdkLogger.info('Barcode service fallback failed; local scanning remains active', {
|
|
839
|
+
error: String(errorMessage(error) || 'Unknown barcode service error'),
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
finally {
|
|
843
|
+
if (barcodeServiceDecodeSessionRef.current === scanSession) {
|
|
844
|
+
barcodeServiceDecodeSessionRef.current = null;
|
|
845
|
+
}
|
|
471
846
|
}
|
|
472
847
|
};
|
|
473
848
|
const handleExitKYCFlow = () => {
|
|
849
|
+
cameraGenerationRef.current += 1;
|
|
850
|
+
barcodeScanSessionRef.current += 1;
|
|
851
|
+
barcodeImageDecodeSessionRef.current = null;
|
|
852
|
+
barcodeFinalizingRef.current = false;
|
|
853
|
+
barcodeCompletedRef.current = false;
|
|
854
|
+
scannedBarcodeRef.current = null;
|
|
855
|
+
isAnalyzingRef.current = false;
|
|
856
|
+
isProcessingCaptureRef.current = false;
|
|
857
|
+
currentSideRef.current = 'front';
|
|
858
|
+
scanPhaseRef.current = 'image';
|
|
859
|
+
capturedImagesRef.current = {};
|
|
474
860
|
clearAuthToken();
|
|
475
861
|
// 1. Clear local UI and camera states immediately
|
|
476
862
|
setCapturedImages({});
|
|
@@ -479,7 +865,6 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
479
865
|
setCurrentSide('front');
|
|
480
866
|
setScanPhase('image');
|
|
481
867
|
setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '' });
|
|
482
|
-
scannedBarcodeRef.current = null;
|
|
483
868
|
setPendingCAImage(null);
|
|
484
869
|
setIsProcessingCapture(false);
|
|
485
870
|
setProcessingImagePath(null);
|
|
@@ -497,11 +882,16 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
497
882
|
console.warn("Could not restart flow. Ensure restartSession is added to your KYC context actions.");
|
|
498
883
|
}
|
|
499
884
|
};
|
|
500
|
-
const handleSilentCapture = async (result) => {
|
|
885
|
+
const handleSilentCapture = async (result, frameGeneration = cameraGenerationRef.current) => {
|
|
886
|
+
if (cameraGenerationRef.current !== frameGeneration)
|
|
887
|
+
return;
|
|
501
888
|
if (isProcessingCaptureRef.current)
|
|
502
889
|
return;
|
|
503
|
-
if (scanPhaseRef.current === 'barcode')
|
|
890
|
+
if (scanPhaseRef.current === 'barcode') {
|
|
891
|
+
if (result.success && result.path)
|
|
892
|
+
void decodeBarcodeFromImage(result.path);
|
|
504
893
|
return;
|
|
894
|
+
}
|
|
505
895
|
if (isAnalyzingRef.current)
|
|
506
896
|
return;
|
|
507
897
|
// 🚨 SYNCHRONOUS LOCK: Block all subsequent camera frames immediately
|
|
@@ -510,6 +900,9 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
510
900
|
const captured = capturedImagesRef.current;
|
|
511
901
|
const docType = selectedDocTypeRef.current;
|
|
512
902
|
const cData = countryDataRef.current;
|
|
903
|
+
const isStaleFrame = () => (cameraGenerationRef.current !== frameGeneration ||
|
|
904
|
+
currentSideRef.current !== side ||
|
|
905
|
+
scanPhaseRef.current !== 'image');
|
|
513
906
|
if (result.success && result.path) {
|
|
514
907
|
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
|
|
515
908
|
const safeCapturePath = Platform.OS === 'ios' && !result.path.startsWith('file://') ? `file://${result.path}` : result.path;
|
|
@@ -523,7 +916,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
523
916
|
}
|
|
524
917
|
const issuingSubdivision = docType.region !== 'root' ? docType.region : undefined;
|
|
525
918
|
const watchdog = setTimeout(() => {
|
|
526
|
-
if (isAnalyzingRef.current) {
|
|
919
|
+
if (!isStaleFrame() && isAnalyzingRef.current) {
|
|
527
920
|
isAnalyzingRef.current = false; // 🚨 UNLOCK inside watchdog
|
|
528
921
|
setSilentCaptureResult((prev) => ({
|
|
529
922
|
...prev,
|
|
@@ -542,6 +935,10 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
542
935
|
postfix: side,
|
|
543
936
|
issuingSubdivision: issuingSubdivision
|
|
544
937
|
}, env);
|
|
938
|
+
if (isStaleFrame()) {
|
|
939
|
+
clearTimeout(watchdog);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
545
942
|
templateResponse = templateType;
|
|
546
943
|
if (templateType.template_path)
|
|
547
944
|
templatePath = templateType.template_path;
|
|
@@ -555,6 +952,10 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
555
952
|
}
|
|
556
953
|
try {
|
|
557
954
|
const crop = await cropByObb(safeCapturePath, templateType.card_obb);
|
|
955
|
+
if (isStaleFrame()) {
|
|
956
|
+
clearTimeout(watchdog);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
558
959
|
templateBbox = crop.bbox;
|
|
559
960
|
}
|
|
560
961
|
catch { }
|
|
@@ -596,6 +997,10 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
596
997
|
frontTemplatePath: frontTemplatePath
|
|
597
998
|
}, env);
|
|
598
999
|
}
|
|
1000
|
+
if (isStaleFrame()) {
|
|
1001
|
+
clearTimeout(watchdog);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
599
1004
|
const bbox = verificationRes?.bbox || templateBbox;
|
|
600
1005
|
const verifiedMrz = verificationRes?.mrz ? JSON.stringify(verificationRes.mrz) : "";
|
|
601
1006
|
if (isBarcodeRequired(side, cData, docType)) {
|
|
@@ -611,6 +1016,10 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
611
1016
|
const token = await authentification();
|
|
612
1017
|
parsedDictionary = await kycService.formatBarcodeData({ textInput: barcodeData, token }, env);
|
|
613
1018
|
}
|
|
1019
|
+
if (isStaleFrame()) {
|
|
1020
|
+
clearTimeout(watchdog);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
614
1023
|
const verifiedResult = {
|
|
615
1024
|
path: safeCapturePath, templatePath: templatePath, bbox: bbox, success: true,
|
|
616
1025
|
mrz: JSON.stringify(parsedDictionary), barcode: barcodeData, isAnalyzing: false,
|
|
@@ -621,15 +1030,25 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
621
1030
|
if (bbox)
|
|
622
1031
|
setBboxBySide(prev => ({ ...prev, ['back']: bbox }));
|
|
623
1032
|
isAnalyzingRef.current = false; // 🚨 UNLOCK on success
|
|
624
|
-
await autoCapture(safeCapturePath, verifiedResult);
|
|
1033
|
+
await autoCapture(safeCapturePath, verifiedResult, side, frameGeneration);
|
|
625
1034
|
return;
|
|
626
1035
|
}
|
|
627
|
-
sdkLogger.info(
|
|
1036
|
+
sdkLogger.info('Verification passed. Starting barcode scan phase.', {
|
|
1037
|
+
platform: Platform.OS,
|
|
1038
|
+
formats: targetBarcodeTypes,
|
|
1039
|
+
});
|
|
628
1040
|
clearTimeout(watchdog);
|
|
629
|
-
|
|
1041
|
+
const pendingImage = { path: safeCapturePath, templateResponse, templatePath, bbox };
|
|
1042
|
+
barcodeScanSessionRef.current += 1;
|
|
1043
|
+
barcodeCompletedRef.current = false;
|
|
1044
|
+
scanPhaseRef.current = 'barcode';
|
|
1045
|
+
setPendingCAImage(pendingImage);
|
|
630
1046
|
setScanPhase('barcode');
|
|
631
1047
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
|
|
632
1048
|
isAnalyzingRef.current = false; // 🚨 UNLOCK before phase shift
|
|
1049
|
+
if (Platform.OS === 'android') {
|
|
1050
|
+
void decodeBarcodeFromImage(safeCapturePath, pendingImage, false);
|
|
1051
|
+
}
|
|
633
1052
|
return;
|
|
634
1053
|
}
|
|
635
1054
|
const verifiedResult = {
|
|
@@ -641,10 +1060,12 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
641
1060
|
if (bbox)
|
|
642
1061
|
setBboxBySide(prev => ({ ...prev, [side]: bbox }));
|
|
643
1062
|
isAnalyzingRef.current = false; // 🚨 UNLOCK on final success
|
|
644
|
-
await autoCapture(safeCapturePath, verifiedResult);
|
|
1063
|
+
await autoCapture(safeCapturePath, verifiedResult, side, frameGeneration);
|
|
645
1064
|
}
|
|
646
1065
|
catch (error) {
|
|
647
1066
|
clearTimeout(watchdog);
|
|
1067
|
+
if (isStaleFrame())
|
|
1068
|
+
return;
|
|
648
1069
|
const rawMessage = (typeof error === 'string' ? error : (error?.message || '')).toLowerCase();
|
|
649
1070
|
const statusCode = error?.response?.status || error?.status;
|
|
650
1071
|
if (statusCode === 401 || statusCode === 403 ||
|
|
@@ -692,11 +1113,12 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
692
1113
|
useEffect(() => {
|
|
693
1114
|
if (scanPhase === 'barcode' && pendingCAImage && silentCaptureResult.barcode) {
|
|
694
1115
|
const bData = silentCaptureResult.barcode;
|
|
695
|
-
|
|
1116
|
+
const allowShortBarcode = selectedDocumentType?.type === 'health_insurance_card';
|
|
1117
|
+
if (isUsableBarcodeText(bData, allowShortBarcode)) {
|
|
696
1118
|
processCanadianBarcode(bData);
|
|
697
1119
|
}
|
|
698
1120
|
}
|
|
699
|
-
}, [scanPhase, pendingCAImage, silentCaptureResult.barcode]);
|
|
1121
|
+
}, [scanPhase, pendingCAImage, silentCaptureResult.barcode, selectedDocumentType?.type]);
|
|
700
1122
|
const handleError = (event) => {
|
|
701
1123
|
const errString = `${event.message} ${event.name} ${event.code}`.toLowerCase();
|
|
702
1124
|
let friendlyMessage = state.currentLanguage === 'en' ? 'Camera error.' : 'Erreur de caméra.';
|
|
@@ -711,8 +1133,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
711
1133
|
}
|
|
712
1134
|
sdkLogger.error("Hardware Camera Error encountered", { ...event, friendlyMessage }); // 🚨 LOG
|
|
713
1135
|
showAlert(state.currentLanguage === 'en' ? 'Camera Error' : 'Erreur', friendlyMessage);
|
|
714
|
-
|
|
715
|
-
setIsProcessingCapture(false);
|
|
1136
|
+
cancelCurrentCapture();
|
|
716
1137
|
};
|
|
717
1138
|
useEffect(() => { actions.showCustomStepper(!showCamera); }, [showCamera]);
|
|
718
1139
|
// Render logic remains identical below...
|
|
@@ -744,12 +1165,27 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
744
1165
|
{Platform.OS === 'web' && (<View style={styles.webTopControls}>
|
|
745
1166
|
<TouchableOpacity style={styles.webFlipButton} onPress={toggleCameraLens}><Text style={styles.webFlipText}>🔄 Flip Lens</Text></TouchableOpacity>
|
|
746
1167
|
</View>)}
|
|
747
|
-
<EnhancedCameraView key={`${currentSide}-${cameraKey}`} showCamera={true} isProcessing={isBusy} cameraType={cameraConfig.cameraType} style={{ ...StyleSheet.absoluteFillObject, ...(Platform.OS === 'web' ? { width: '100%', height: '100%', left: 0, top: 0 } : {}) }} onError={handleError} onSilentCapture={
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1168
|
+
<EnhancedCameraView key={`${currentSide}-${scanPhase}-${cameraKey}`} showCamera={true} isProcessing={isBusy} cameraType={cameraConfig.cameraType} style={{ ...StyleSheet.absoluteFillObject, ...(Platform.OS === 'web' ? { width: '100%', height: '100%', left: 0, top: 0 } : {}) }} onError={handleError} onSilentCapture={(result) => {
|
|
1169
|
+
if (cameraGenerationRef.current !== cameraKey ||
|
|
1170
|
+
currentSideRef.current !== currentSide ||
|
|
1171
|
+
scanPhaseRef.current !== scanPhase) {
|
|
1172
|
+
sdkLogger.info('Discarded a stale camera frame after capture-mode transition', {
|
|
1173
|
+
frameGeneration: cameraKey,
|
|
1174
|
+
activeGeneration: cameraGenerationRef.current,
|
|
1175
|
+
frameSide: currentSide,
|
|
1176
|
+
activeSide: currentSideRef.current,
|
|
1177
|
+
framePhase: scanPhase,
|
|
1178
|
+
activePhase: scanPhaseRef.current,
|
|
1179
|
+
});
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
void handleSilentCapture(result, cameraKey);
|
|
1183
|
+
}} silentCaptureResult={silentCaptureResult} captureStabilizationDelayMs={scanPhase === 'barcode' ? 3000 : 4000} isScanningBarcode={currentSide === 'back' && scanPhase === 'barcode'} barcodeTypes={targetBarcodeTypes} onBarcodeScanned={(barcodeString) => {
|
|
1184
|
+
const allowShortBarcode = selectedDocumentType?.type === 'health_insurance_card';
|
|
1185
|
+
if (!isUsableBarcodeText(barcodeString, allowShortBarcode))
|
|
751
1186
|
return;
|
|
752
1187
|
if (scannedBarcodeRef.current !== barcodeString) {
|
|
1188
|
+
sdkLogger.info('Barcode detected by live camera scanner', { length: barcodeString.length });
|
|
753
1189
|
scannedBarcodeRef.current = barcodeString;
|
|
754
1190
|
if (scanPhase === 'barcode' && pendingCAImage) {
|
|
755
1191
|
processCanadianBarcode(barcodeString);
|
|
@@ -761,22 +1197,10 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
761
1197
|
}} overlayComponent={<IdCardOverlay xMin={cameraConfig.overlay.bbox.xMin} yMin={cameraConfig.overlay.bbox.yMin} xMax={cameraConfig.overlay.bbox.xMax} yMax={cameraConfig.overlay.bbox.yMax} instructions={cameraConfig.overlay.guideText} cornerOpacity={cameraConfig.overlay.bbox.cornerRadius || 0} isSuccess={silentCaptureResult.success || scanPhase === 'barcode'} language={state.currentLanguage} stepperProps={{
|
|
762
1198
|
back: () => {
|
|
763
1199
|
if (currentSide === 'back') {
|
|
764
|
-
|
|
765
|
-
setScanPhase('image');
|
|
766
|
-
setPendingCAImage(null);
|
|
767
|
-
setCurrentSide('front');
|
|
768
|
-
setShowCamera(false);
|
|
769
|
-
setIsProcessingCapture(false);
|
|
770
|
-
setProcessingImagePath(null);
|
|
771
|
-
if (capturedImages['front']?.dir) {
|
|
772
|
-
const frontImage = capturedImages['front'];
|
|
773
|
-
setSilentCaptureResult((prev) => ({ ...prev, path: frontImage.dir, success: true, isAnalyzing: false, error: '', mrz: frontImage.mrz || '', templatePath: frontImage.templatePath || '', bbox: bboxBySide['front'] }));
|
|
774
|
-
}
|
|
775
|
-
else {
|
|
776
|
-
setSilentCaptureResult((prev) => ({ ...prev, path: '', success: false, isAnalyzing: false, error: '', templatePath: '' }));
|
|
777
|
-
}
|
|
1200
|
+
returnToFrontReview();
|
|
778
1201
|
}
|
|
779
1202
|
else {
|
|
1203
|
+
cancelCurrentCapture();
|
|
780
1204
|
actions.previousComponent();
|
|
781
1205
|
}
|
|
782
1206
|
},
|
|
@@ -787,7 +1211,11 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
787
1211
|
<View style={styles.barcodeIconContainer}><Text style={styles.barcodeIcon}>✓</Text></View>
|
|
788
1212
|
<View style={styles.barcodeTextContainer}>
|
|
789
1213
|
<Text style={styles.barcodePromptTitle}>{state.currentLanguage === 'en' ? 'Image Verified' : 'Image Vérifiée'}</Text>
|
|
790
|
-
<Text style={styles.barcodePromptDesc}>
|
|
1214
|
+
<Text style={styles.barcodePromptDesc}>
|
|
1215
|
+
{state.currentLanguage === 'en'
|
|
1216
|
+
? 'Proceed to scan the barcode. Move closer but ensure it is fully visible in the frame.'
|
|
1217
|
+
: 'Procédez au scan du code-barres. Rapprochez-vous, mais assurez-vous qu\'il soit entièrement visible dans le cadre.'}
|
|
1218
|
+
</Text>
|
|
791
1219
|
</View>
|
|
792
1220
|
</View>
|
|
793
1221
|
</View>)}
|
|
@@ -807,12 +1235,12 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
807
1235
|
{!isBusy && Platform.OS === 'web' ? (<View style={styles.webBottomControlBar}>
|
|
808
1236
|
{silentCaptureResult.error && <View style={styles.floatingErrorBanner}><Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text></View>}
|
|
809
1237
|
<View style={styles.webActionButtonsRow}>
|
|
810
|
-
<TouchableOpacity style={styles.webSecondaryButton} onPress={
|
|
1238
|
+
<TouchableOpacity style={styles.webSecondaryButton} onPress={cancelCurrentCapture}><Text style={styles.webSecondaryButtonText}>Cancel</Text></TouchableOpacity>
|
|
811
1239
|
<TouchableOpacity style={styles.webPrimaryButton} onPress={refreshCamera}><Text style={styles.webPrimaryButtonText}>↻ Refresh Camera</Text></TouchableOpacity>
|
|
812
1240
|
</View>
|
|
813
1241
|
</View>) : !isBusy ? (<View style={styles.escapeHatchContainer}>
|
|
814
1242
|
<TouchableOpacity style={styles.fallbackRefreshButton} onPress={refreshCamera}><Text style={styles.fallbackRefreshText}>↻ Refresh Camera</Text></TouchableOpacity>
|
|
815
|
-
<TouchableOpacity style={[styles.fallbackRefreshButton, { marginTop: 15, backgroundColor: 'rgba(220, 38, 38, 0.8)', borderColor: '#DC2626' }]} onPress={
|
|
1243
|
+
<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>
|
|
816
1244
|
</View>) : null}
|
|
817
1245
|
{silentCaptureResult.error && !isBusy && Platform.OS !== 'web' ? (<View style={[styles.floatingErrorBanner, { zIndex: 10000 }]}><Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text></View>) : null}
|
|
818
1246
|
</View>
|
|
@@ -856,14 +1284,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
856
1284
|
}
|
|
857
1285
|
else {
|
|
858
1286
|
sdkLogger.info("User finished front verification, proceeding to back");
|
|
859
|
-
|
|
860
|
-
refreshCamera();
|
|
861
|
-
setCurrentSide('back');
|
|
862
|
-
setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '', templatePath: undefined });
|
|
863
|
-
setIsProcessingCapture(false);
|
|
864
|
-
setProcessingImagePath(null);
|
|
865
|
-
setScanPhase('image');
|
|
866
|
-
setPendingCAImage(null);
|
|
1287
|
+
startBackCapture();
|
|
867
1288
|
}
|
|
868
1289
|
}} variant="primary" size="large" fullWidth/>
|
|
869
1290
|
</>)}
|