@sanctum-key/react-native-sdk 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/package.json +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +113 -119
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +13 -63
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/utils/cropByObb.d.ts +2 -7
- package/build/src/utils/cropByObb.d.ts.map +1 -1
- package/build/src/utils/cropByObb.js +104 -40
- package/build/src/utils/cropByObb.js.map +1 -1
- package/package.json +1 -1
- package/src/components/KYCElements/IDCardCapture.tsx +566 -573
- package/src/modules/api/CardAuthentification.ts +21 -89
- package/src/utils/cropByObb.ts +134 -41
package/build/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IDCardCapture.d.ts","sourceRoot":"","sources":["../../../../src/components/KYCElements/IDCardCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAI5D,OAAO,EAAE,iBAAiB,EAAoI,MAAM,uBAAuB,CAAC;AAc5L,UAAU,cAAc;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAAE;AAC3F,UAAU,kBAAkB;IAAG,SAAS,EAAE,iBAAiB,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAAE;AAExO,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,
|
|
1
|
+
{"version":3,"file":"IDCardCapture.d.ts","sourceRoot":"","sources":["../../../../src/components/KYCElements/IDCardCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAI5D,OAAO,EAAE,iBAAiB,EAAoI,MAAM,uBAAuB,CAAC;AAc5L,UAAU,cAAc;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAAE;AAC3F,UAAU,kBAAkB;IAAG,SAAS,EAAE,iBAAiB,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAAE;AAExO,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAmftD,CAAC"}
|
|
@@ -157,8 +157,6 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
157
157
|
setProcessingImagePath(capturePath);
|
|
158
158
|
try {
|
|
159
159
|
let imagePathForUpload = capturePath;
|
|
160
|
-
// 🚨 THE FIX: Ignore the backend's broken absolute pixels.
|
|
161
|
-
// Calculate the crop using the Green UI Frame percentages (0.0 to 1.0)
|
|
162
160
|
const overlayBbox = cameraConfig.overlay.bbox;
|
|
163
161
|
const uiCropBbox = {
|
|
164
162
|
minX: overlayBbox.xMin / 100,
|
|
@@ -168,11 +166,12 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
168
166
|
};
|
|
169
167
|
try {
|
|
170
168
|
// Apply the crop using the UI percentages and a tight 2% tolerance
|
|
169
|
+
// NOTE: If you integrated the Kotlin centered crop function earlier, replace this with cropToCenterScanArea
|
|
171
170
|
imagePathForUpload = await cropImageWithBBoxWithTolerance(capturePath, uiCropBbox, 0.02);
|
|
172
171
|
}
|
|
173
172
|
catch (e) {
|
|
174
173
|
console.warn("Crop failed, falling back to original image", e);
|
|
175
|
-
imagePathForUpload = capturePath;
|
|
174
|
+
imagePathForUpload = capturePath;
|
|
176
175
|
}
|
|
177
176
|
const base64 = await pathToBase64(imagePathForUpload);
|
|
178
177
|
const newImages = {
|
|
@@ -260,8 +259,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
260
259
|
await autoCapture(result.path, verifiedResult);
|
|
261
260
|
}
|
|
262
261
|
catch (error) {
|
|
263
|
-
|
|
264
|
-
console.error("Backend Verification Error:", error);
|
|
262
|
+
console.log("Backend Verification Error:", error);
|
|
265
263
|
// 2. Define a map of technical keywords to user-friendly messages
|
|
266
264
|
const rawMessage = (error?.message || '').toLowerCase();
|
|
267
265
|
let userFriendlyMessage = 'Verification failed. Please try again.';
|
|
@@ -271,7 +269,8 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
271
269
|
else if (rawMessage.includes('country mismatch') || rawMessage.includes('pays sélectionné')) {
|
|
272
270
|
userFriendlyMessage = 'The document does not match your selected country.';
|
|
273
271
|
}
|
|
274
|
-
else if (rawMessage.includes('
|
|
272
|
+
else if (rawMessage.includes('card_not_fully_in_frame')) {
|
|
273
|
+
// 🚨 REMOVED 'too far' CHECK FROM HERE
|
|
275
274
|
userFriendlyMessage = 'Move closer to the document and ensure all corners are visible.';
|
|
276
275
|
}
|
|
277
276
|
// 3. Set ONLY the friendly message to the UI state
|
|
@@ -294,48 +293,48 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
294
293
|
}, [showCamera]);
|
|
295
294
|
if (!countrySelectionData || !selectedDocumentType) {
|
|
296
295
|
return (<View style={styles.root}>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
296
|
+
<View style={styles.previewContainer}>
|
|
297
|
+
<Text style={styles.title}>{getLocalizedText(component.labels)}</Text>
|
|
298
|
+
<Text style={styles.description}>
|
|
299
|
+
{state.currentLanguage === "en" ? "Please complete the country and document selection first." : "Veuillez d'abord compléter la sélection du pays et du document."}
|
|
300
|
+
</Text>
|
|
301
|
+
</View>
|
|
302
|
+
</View>);
|
|
304
303
|
}
|
|
305
304
|
// --- CAMERA RENDER ---
|
|
306
305
|
if (showCamera) {
|
|
307
306
|
const isBusy = isProcessingCapture;
|
|
308
307
|
if (isRebootingCamera) {
|
|
309
308
|
return (<View style={[styles.root, { justifyContent: 'center', alignItems: 'center', backgroundColor: '#000' }]}>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
309
|
+
<ActivityIndicator size="large" color="#2DBD60"/>
|
|
310
|
+
<Text style={{ color: 'white', marginTop: 20, fontSize: 16 }}>Initializing Camera...</Text>
|
|
311
|
+
</View>);
|
|
313
312
|
}
|
|
314
313
|
return (<View style={styles.root}>
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
314
|
+
<View style={[styles.cameraWrapper, { flex: 1 }]}>
|
|
315
|
+
|
|
316
|
+
<View style={styles.headerContainer}>
|
|
317
|
+
<Text style={styles.headerTitle}>
|
|
318
|
+
{selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : ''}
|
|
319
|
+
</Text>
|
|
320
|
+
<View style={styles.stepBadge}>
|
|
321
|
+
<Text style={styles.stepText}>
|
|
322
|
+
{t('kyc.idCardCapture.captureTitle', { side: currentSide === 'front' ? locale === 'en' ? 'Front' : 'Recto' : locale === 'en' ? 'Back' : 'Verso' })}
|
|
323
|
+
</Text>
|
|
324
|
+
</View>
|
|
325
|
+
</View>
|
|
326
|
+
|
|
327
|
+
<View style={[styles.cameraFeedContainer, { flex: 1, backgroundColor: '#000' }]}>
|
|
328
|
+
|
|
329
|
+
{/* WEB ONLY: Flip Camera Top Button */}
|
|
330
|
+
{Platform.OS === 'web' && (<View style={styles.webTopControls}>
|
|
331
|
+
<TouchableOpacity style={styles.webFlipButton} onPress={toggleCameraLens}>
|
|
332
|
+
<Text style={styles.webFlipText}>🔄 Flip Lens</Text>
|
|
333
|
+
</TouchableOpacity>
|
|
334
|
+
</View>)}
|
|
336
335
|
|
|
337
|
-
|
|
338
|
-
|
|
336
|
+
<EnhancedCameraView key={`${currentSide}-${cameraKey}`} showCamera={true} isProcessing={isBusy} cameraType={cameraConfig.cameraType} style={StyleSheet.absoluteFillObject} onError={handleError} onSilentCapture={handleSilentCapture} silentCaptureResult={silentCaptureResult} overlayComponent={<>
|
|
337
|
+
<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} language={state.currentLanguage} stepperProps={{
|
|
339
338
|
back: () => {
|
|
340
339
|
if (currentSide === 'back') {
|
|
341
340
|
setCurrentSide('front');
|
|
@@ -357,86 +356,83 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
357
356
|
selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '',
|
|
358
357
|
step: state.currentComponentIndex + 1, totalSteps: state.template.components.length, side: currentSide,
|
|
359
358
|
}}/>
|
|
360
|
-
|
|
359
|
+
</>}/>
|
|
361
360
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
361
|
+
{!isBusy && silentCaptureResult.isAnalyzing && (<View style={styles.topAnalyzingPillContainer}>
|
|
362
|
+
<View style={styles.topAnalyzingPill}>
|
|
363
|
+
<ActivityIndicator size="small" color="white"/>
|
|
364
|
+
<Text style={styles.analyzingPillText}>
|
|
365
|
+
{state.currentLanguage === 'en' ? 'Scanning...' : 'Analyse...'}
|
|
366
|
+
</Text>
|
|
367
|
+
</View>
|
|
368
|
+
</View>)}
|
|
370
369
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
370
|
+
{isBusy && (<View style={[StyleSheet.absoluteFillObject, { zIndex: 9999 }]}>
|
|
371
|
+
{processingImagePath && (<Image source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }} style={StyleSheet.absoluteFillObject} resizeMode="cover"/>)}
|
|
372
|
+
<View style={styles.processingOverlay}>
|
|
373
|
+
<ActivityIndicator size="large" color="#2DBD60"/>
|
|
374
|
+
<Text style={styles.processingText}>
|
|
375
|
+
{state.currentLanguage === 'en' ? 'Perfect!\nProcessing Document...' : 'Parfait!\nTraitement du document...'}
|
|
376
|
+
</Text>
|
|
377
|
+
</View>
|
|
378
|
+
</View>)}
|
|
380
379
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
380
|
+
{/* 🚨 THE ESCAPE HATCH: Branching UI for Web vs Mobile */}
|
|
381
|
+
{!isBusy && Platform.OS === 'web' ? (<View style={styles.webBottomControlBar}>
|
|
382
|
+
{silentCaptureResult.error ? (<View style={styles.floatingErrorBanner}>
|
|
383
|
+
<Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text>
|
|
384
|
+
</View>) : <View style={{ height: 10 }}/>}
|
|
386
385
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
386
|
+
<View style={styles.webActionButtonsRow}>
|
|
387
|
+
<TouchableOpacity style={styles.webSecondaryButton} onPress={() => setShowCamera(false)}>
|
|
388
|
+
<Text style={styles.webSecondaryButtonText}>Cancel</Text>
|
|
389
|
+
</TouchableOpacity>
|
|
390
|
+
<TouchableOpacity style={styles.webPrimaryButton} onPress={refreshCamera}>
|
|
391
|
+
<Text style={styles.webPrimaryButtonText}>↻ Refresh Camera</Text>
|
|
392
|
+
</TouchableOpacity>
|
|
393
|
+
</View>
|
|
394
|
+
</View>) : !isBusy ? (<View style={styles.escapeHatchContainer}>
|
|
395
|
+
<TouchableOpacity style={styles.fallbackRefreshButton} onPress={refreshCamera}>
|
|
396
|
+
<Text style={styles.fallbackRefreshText}>↻ Refresh Camera</Text>
|
|
397
|
+
</TouchableOpacity>
|
|
398
|
+
<TouchableOpacity style={[styles.fallbackRefreshButton, { marginTop: 15, backgroundColor: 'rgba(220, 38, 38, 0.8)', borderColor: '#DC2626' }]} onPress={() => setShowCamera(false)}>
|
|
399
|
+
<Text style={styles.fallbackRefreshText}>Cancel / Go Back</Text>
|
|
400
|
+
</TouchableOpacity>
|
|
401
|
+
</View>) : null}
|
|
403
402
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
403
|
+
{silentCaptureResult.error && !isBusy && Platform.OS !== 'web' ? (<View style={[styles.floatingErrorBanner, { zIndex: 10000 }]}>
|
|
404
|
+
<Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text>
|
|
405
|
+
</View>) : null}
|
|
407
406
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
407
|
+
</View>
|
|
408
|
+
</View>
|
|
409
|
+
</View>);
|
|
411
410
|
}
|
|
412
411
|
// --- PREVIEW RENDER ---
|
|
413
412
|
return (<View style={styles.root}>
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
<View style={styles.imagePreviewWrapper}>
|
|
433
|
-
{capturedImages[currentSide]?.dir ? (<Image source={{ uri: capturedImages[currentSide].dir }} style={styles.previewImage}/>) : silentCaptureResult.path ? (<Image source={{ uri: silentCaptureResult.path }} style={styles.previewImage}/>) : null}
|
|
434
|
-
</View>
|
|
413
|
+
<View style={styles.previewContainer}>
|
|
414
|
+
<ScrollView style={styles.previewItemContainer} showsVerticalScrollIndicator={false}>
|
|
415
|
+
<View key={currentSide} style={styles.sideContainer}>
|
|
416
|
+
<Text style={styles.sideTitle}>
|
|
417
|
+
{t('kyc.idCardCapture.captureTitle', { side: currentSide === 'front' ? locale === 'en' ? 'Front' : 'Recto' : locale === 'en' ? 'Back' : 'Verso' })}
|
|
418
|
+
</Text>
|
|
419
|
+
<Text style={{ fontSize: 14, color: '#666', textAlign: 'center', marginBottom: 16, lineHeight: 22 }}>
|
|
420
|
+
{getLocalizedText(component.instructions)}
|
|
421
|
+
</Text>
|
|
422
|
+
|
|
423
|
+
<View style={{ alignItems: 'center', justifyContent: 'center', flexDirection: "column", gap: 16 }}>
|
|
424
|
+
|
|
425
|
+
{/* 🚨 REMOVED THE ORANGE WARNING BANNER ENTIRELY */}
|
|
426
|
+
|
|
427
|
+
{/* 🚨 PREVIEW CONTAINER WITH PLATFORM OVERRIDES */}
|
|
428
|
+
<View style={styles.imagePreviewWrapper}>
|
|
429
|
+
{capturedImages[currentSide]?.dir ? (<Image source={{ uri: capturedImages[currentSide].dir }} style={styles.previewImage}/>) : silentCaptureResult.path ? (<Image source={{ uri: silentCaptureResult.path }} style={styles.previewImage}/>) : null}
|
|
430
|
+
</View>
|
|
435
431
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
432
|
+
{!capturedImages[currentSide]?.dir && (<Button title={state.currentLanguage === "en" ? "Start Scanning" : "Commencer la numérisation"} onPress={() => { setShowCamera(true); refreshCamera(); actions.showCustomStepper(false); }} variant="primary" size="large" fullWidth/>)}
|
|
433
|
+
{capturedImages[currentSide]?.dir && (<>
|
|
434
|
+
<Button title={t('kyc.idCardCapture.retakeButton')} onPress={() => retakePicture(currentSide)} variant="outline" size="medium" fullWidth/>
|
|
435
|
+
<Button title={t('common.next')} onPress={() => {
|
|
440
436
|
if (!selectedDocumentType) {
|
|
441
437
|
showAlert('Error', 'Document type not selected');
|
|
442
438
|
return;
|
|
@@ -453,12 +449,12 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
453
449
|
setProcessingImagePath(null);
|
|
454
450
|
}
|
|
455
451
|
}} variant="primary" size="large" fullWidth/>
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
452
|
+
</>)}
|
|
453
|
+
</View>
|
|
454
|
+
</View>
|
|
455
|
+
</ScrollView>
|
|
456
|
+
</View>
|
|
457
|
+
</View>);
|
|
462
458
|
};
|
|
463
459
|
const styles = StyleSheet.create({
|
|
464
460
|
root: {
|
|
@@ -510,8 +506,8 @@ const styles = StyleSheet.create({
|
|
|
510
506
|
imagePreviewWrapper: {
|
|
511
507
|
width: '100%', borderRadius: 12, padding: 1, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.18, shadowRadius: 8, elevation: 8, backgroundColor: '#f0f0f0',
|
|
512
508
|
...Platform.select({
|
|
513
|
-
web: { aspectRatio: 1.59, height: 'auto' },
|
|
514
|
-
default: { height: 220 }
|
|
509
|
+
web: { aspectRatio: 1.59, height: 'auto' },
|
|
510
|
+
default: { height: 220 }
|
|
515
511
|
})
|
|
516
512
|
},
|
|
517
513
|
previewImage: { width: '100%', height: '100%', borderRadius: 12, resizeMode: 'cover' },
|
|
@@ -524,8 +520,6 @@ const styles = StyleSheet.create({
|
|
|
524
520
|
floatingErrorText: { color: '#991B1B', fontSize: 14, fontWeight: '700', textAlign: 'center' },
|
|
525
521
|
processingOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center', zIndex: 9999 },
|
|
526
522
|
processingText: { color: '#FFF', fontSize: 18, fontWeight: 'bold', marginTop: 16, textAlign: 'center' },
|
|
527
|
-
warningBanner: { backgroundColor: '#FF9500', padding: 12, borderRadius: 8, width: '100%', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, elevation: 4 },
|
|
528
|
-
warningText: { color: 'white', fontWeight: 'bold', textAlign: 'center', fontSize: 16 },
|
|
529
523
|
errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
|
|
530
524
|
topAnalyzingPillContainer: { position: 'absolute', top: Platform.OS === 'android' ? 60 : 50, left: 0, right: 0, alignItems: 'center', zIndex: 100 },
|
|
531
525
|
topAnalyzingPill: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.6)', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 20, gap: 8 },
|