@sanctum-key/react-native-sdk 1.0.51 → 1.0.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/xml_file_paths.xml.flat +0 -0
  2. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +2 -2
  3. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +1 -1
  4. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +1 -1
  5. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +1 -1
  6. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +1 -1
  7. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +41 -41
  8. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  9. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  10. package/android/build/outputs/logs/manifest-merger-debug-report.txt +52 -52
  11. package/build/package.json +1 -1
  12. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  13. package/build/src/components/KYCElements/IDCardCapture.js +73 -66
  14. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  15. package/build/src/components/TemplateKYCExample.d.ts +2 -1
  16. package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
  17. package/build/src/components/TemplateKYCExample.js +20 -18
  18. package/build/src/components/TemplateKYCExample.js.map +1 -1
  19. package/build/src/components/TemplateKYCFlowRefactored.d.ts +1 -0
  20. package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  21. package/build/src/components/TemplateKYCFlowRefactored.js +20 -20
  22. package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
  23. package/build/src/config/KYCConfig.d.ts.map +1 -1
  24. package/build/src/config/KYCConfig.js +4 -2
  25. package/build/src/config/KYCConfig.js.map +1 -1
  26. package/build/src/hooks/useTemplateKYCFlow.d.ts +1 -0
  27. package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  28. package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
  29. package/build/src/hooks/useTemplateLoader.d.ts.map +1 -1
  30. package/build/src/hooks/useTemplateLoader.js +0 -2
  31. package/build/src/hooks/useTemplateLoader.js.map +1 -1
  32. package/build/src/modules/api/CardAuthentification.d.ts +0 -23
  33. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  34. package/build/src/modules/api/CardAuthentification.js +46 -83
  35. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  36. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  37. package/build/src/modules/api/KYCService.js +96 -38
  38. package/build/src/modules/api/KYCService.js.map +1 -1
  39. package/build/src/modules/api/SelfieVerification.d.ts.map +1 -1
  40. package/build/src/modules/api/SelfieVerification.js +12 -19
  41. package/build/src/modules/api/SelfieVerification.js.map +1 -1
  42. package/build/src/modules/api/TemplateService.d.ts.map +1 -1
  43. package/build/src/modules/api/TemplateService.js +2 -1
  44. package/build/src/modules/api/TemplateService.js.map +1 -1
  45. package/build/src/modules/api/types.d.ts +34 -27
  46. package/build/src/modules/api/types.d.ts.map +1 -1
  47. package/build/src/modules/api/types.js.map +1 -1
  48. package/package.json +1 -1
  49. package/src/components/KYCElements/IDCardCapture.tsx +192 -191
  50. package/src/components/TemplateKYCExample.tsx +29 -20
  51. package/src/components/TemplateKYCFlowRefactored.tsx +33 -31
  52. package/src/config/KYCConfig.ts +7 -5
  53. package/src/hooks/useTemplateKYCFlow.tsx +1 -0
  54. package/src/hooks/useTemplateLoader.ts +0 -2
  55. package/src/modules/api/CardAuthentification.ts +409 -445
  56. package/src/modules/api/KYCService.ts +599 -449
  57. package/src/modules/api/SelfieVerification.ts +22 -29
  58. package/src/modules/api/TemplateService.ts +3 -2
  59. package/src/modules/api/types.ts +36 -29
@@ -504,6 +504,8 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
504
504
  return;
505
505
  if (isAnalyzingRef.current)
506
506
  return;
507
+ // 🚨 SYNCHRONOUS LOCK: Block all subsequent camera frames immediately
508
+ isAnalyzingRef.current = true;
507
509
  const side = currentSideRef.current;
508
510
  const captured = capturedImagesRef.current;
509
511
  const docType = selectedDocTypeRef.current;
@@ -516,17 +518,30 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
516
518
  let templateResponse;
517
519
  if (!docType) {
518
520
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' }));
521
+ isAnalyzingRef.current = false; // 🚨 UNLOCK
519
522
  return;
520
523
  }
521
524
  const issuingSubdivision = docType.region !== 'root' ? docType.region : undefined;
522
525
  const watchdog = setTimeout(() => {
523
526
  if (isAnalyzingRef.current) {
524
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: state.currentLanguage === 'en' ? 'Taking longer than expected. Hold steady.' : 'Traitement long. Veuillez rester immobile.' }));
527
+ isAnalyzingRef.current = false; // 🚨 UNLOCK inside watchdog
528
+ setSilentCaptureResult((prev) => ({
529
+ ...prev,
530
+ isAnalyzing: false,
531
+ success: false,
532
+ error: state.currentLanguage === 'en' ? 'Taking longer than expected. Hold steady.' : 'Traitement long. Veuillez rester immobile.'
533
+ }));
525
534
  }
526
535
  }, 20000);
527
536
  try {
528
537
  if (!templatePath) {
529
- const templateType = await checkTemplateType({ path: safeCapturePath, docType: docType.type, docRegion: cData?.code || "", postfix: side, issuingSubdivision: issuingSubdivision }, env);
538
+ const templateType = await checkTemplateType({
539
+ path: safeCapturePath,
540
+ docType: docType.type,
541
+ docRegion: cData?.code || "",
542
+ postfix: side,
543
+ issuingSubdivision: issuingSubdivision
544
+ }, env);
530
545
  templateResponse = templateType;
531
546
  if (templateType.template_path)
532
547
  templatePath = templateType.template_path;
@@ -535,6 +550,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
535
550
  if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
536
551
  clearTimeout(watchdog);
537
552
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: t('kyc.idCardCapture.cardNotFullyInFrame') }));
553
+ isAnalyzingRef.current = false; // 🚨 UNLOCK
538
554
  return;
539
555
  }
540
556
  try {
@@ -551,13 +567,34 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
551
567
  if (side === 'front') {
552
568
  const matchedAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'front');
553
569
  const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || 'TD1';
554
- verificationRes = await frontVerification({ path: safeCapturePath, regionMapping: { authMethod: matchedAuthMethod ? [matchedAuthMethod] : [], mrzTypes: mrzType ? [mrzType] : [] }, selectedDocumentType: GovernmentDocumentTypeShorted[docType.type] || '', code: cData?.code || '', currentSide: side, templatePath, mrzType, issuingSubdivision: issuingSubdivision, clientSideBarcodeData: silentCaptureResult.barcode }, env);
570
+ verificationRes = await frontVerification({
571
+ path: safeCapturePath,
572
+ regionMapping: { authMethod: matchedAuthMethod ? [matchedAuthMethod] : [], mrzTypes: mrzType ? [mrzType] : [] },
573
+ selectedDocumentType: GovernmentDocumentTypeShorted[docType.type] || '',
574
+ code: cData?.code || '',
575
+ currentSide: side,
576
+ templatePath,
577
+ mrzType,
578
+ issuingSubdivision: issuingSubdivision,
579
+ clientSideBarcodeData: silentCaptureResult.barcode
580
+ }, env);
555
581
  }
556
582
  else {
557
583
  const matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
558
584
  const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
559
585
  const frontTemplatePath = captured['front']?.templatePath || '';
560
- verificationRes = await backVerification({ path: safeCapturePath, regionMapping: { authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : [], mrzTypes: backMrzType ? [backMrzType] : [] }, selectedDocumentType: GovernmentDocumentTypeShorted[docType.type] || '', code: cData?.code || '', currentSide: side, templatePath, mrzType: backMrzType, templateResponse, issuingSubdivision: issuingSubdivision, frontTemplatePath: frontTemplatePath }, env);
586
+ verificationRes = await backVerification({
587
+ path: safeCapturePath,
588
+ regionMapping: { authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : [], mrzTypes: backMrzType ? [backMrzType] : [] },
589
+ selectedDocumentType: GovernmentDocumentTypeShorted[docType.type] || '',
590
+ code: cData?.code || '',
591
+ currentSide: side,
592
+ templatePath,
593
+ mrzType: backMrzType,
594
+ templateResponse,
595
+ issuingSubdivision: issuingSubdivision,
596
+ frontTemplatePath: frontTemplatePath
597
+ }, env);
561
598
  }
562
599
  const bbox = verificationRes?.bbox || templateBbox;
563
600
  const verifiedMrz = verificationRes?.mrz ? JSON.stringify(verificationRes.mrz) : "";
@@ -574,113 +611,83 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
574
611
  const token = await authentification();
575
612
  parsedDictionary = await kycService.formatBarcodeData({ textInput: barcodeData, token }, env);
576
613
  }
577
- const verifiedResult = { path: safeCapturePath, templatePath: templatePath, bbox: bbox, success: true, mrz: JSON.stringify(parsedDictionary), barcode: barcodeData, isAnalyzing: false, country: cData?.code, documentType: docType.type, region: docType.region !== 'root' ? docType.region : undefined };
614
+ const verifiedResult = {
615
+ path: safeCapturePath, templatePath: templatePath, bbox: bbox, success: true,
616
+ mrz: JSON.stringify(parsedDictionary), barcode: barcodeData, isAnalyzing: false,
617
+ country: cData?.code, documentType: docType.type, region: docType.region !== 'root' ? docType.region : undefined
618
+ };
578
619
  clearTimeout(watchdog);
579
620
  setSilentCaptureResult(verifiedResult);
580
621
  if (bbox)
581
622
  setBboxBySide(prev => ({ ...prev, ['back']: bbox }));
623
+ isAnalyzingRef.current = false; // 🚨 UNLOCK on success
582
624
  await autoCapture(safeCapturePath, verifiedResult);
583
625
  return;
584
626
  }
585
- sdkLogger.info(`Verification passed. Waiting for barcode scan phase.`); // 🚨 LOG
627
+ sdkLogger.info(`Verification passed. Waiting for barcode scan phase.`);
586
628
  clearTimeout(watchdog);
587
629
  setPendingCAImage({ path: safeCapturePath, templateResponse, templatePath, bbox });
588
630
  setScanPhase('barcode');
589
631
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
632
+ isAnalyzingRef.current = false; // 🚨 UNLOCK before phase shift
590
633
  return;
591
634
  }
592
- const verifiedResult = { path: safeCapturePath, templatePath, bbox, success: true, mrz: verifiedMrz, isAnalyzing: false, country: cData?.code, documentType: docType.type, region: issuingSubdivision };
635
+ const verifiedResult = {
636
+ path: safeCapturePath, templatePath, bbox, success: true, mrz: verifiedMrz, isAnalyzing: false,
637
+ country: cData?.code, documentType: docType.type, region: issuingSubdivision
638
+ };
593
639
  clearTimeout(watchdog);
594
640
  setSilentCaptureResult(verifiedResult);
595
641
  if (bbox)
596
642
  setBboxBySide(prev => ({ ...prev, [side]: bbox }));
643
+ isAnalyzingRef.current = false; // 🚨 UNLOCK on final success
597
644
  await autoCapture(safeCapturePath, verifiedResult);
598
645
  }
599
646
  catch (error) {
600
647
  clearTimeout(watchdog);
601
648
  const rawMessage = (typeof error === 'string' ? error : (error?.message || '')).toLowerCase();
602
649
  const statusCode = error?.response?.status || error?.status;
603
- if (statusCode === 401 ||
604
- statusCode === 403 ||
605
- rawMessage.includes('401') ||
606
- rawMessage.includes('unauthorized') ||
607
- rawMessage.includes('token') ||
608
- rawMessage.includes('unauthenticated')) {
650
+ if (statusCode === 401 || statusCode === 403 ||
651
+ rawMessage.includes('401') || rawMessage.includes('unauthorized') ||
652
+ rawMessage.includes('token') || rawMessage.includes('unauthenticated')) {
609
653
  sdkLogger.error(`Secure API Session Expired during AI extraction`);
610
654
  setShowCamera(false);
611
655
  setIsProcessingCapture(false);
612
656
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: '' }));
613
- // Use React Native's native Alert to wait for the user's tap
614
- Alert.alert(state.currentLanguage === 'en' ? 'Session Expired' : 'Session Expirée', state.currentLanguage === 'en'
615
- ? 'Your secure session has expired. Please restart the process.'
616
- : 'Votre session sécurisée a expiré. Veuillez recommencer.', [
617
- {
618
- text: state.currentLanguage === 'en' ? 'Restart' : 'Recommencer',
619
- onPress: () => handleExitKYCFlow() // Redirects ONLY when they click the button
620
- }
621
- ], { cancelable: false } // Forces them to click the button
622
- );
657
+ Alert.alert(state.currentLanguage === 'en' ? 'Session Expired' : 'Session Expirée', state.currentLanguage === 'en' ? 'Your secure session has expired. Please restart the process.' : 'Votre session sécurisée a expiré. Veuillez recommencer.', [{ text: state.currentLanguage === 'en' ? 'Restart' : 'Recommencer', onPress: () => handleExitKYCFlow() }], { cancelable: false });
658
+ isAnalyzingRef.current = false; // 🚨 UNLOCK on session expiry
623
659
  return;
624
660
  }
625
661
  let hintMessage = '';
626
- // 2. Business Logic Errors
627
- if (rawMessage.includes('card_not_fully_in_frame') ||
628
- rawMessage.includes('non entièrement visible') ||
629
- rawMessage.includes('aucun visage détecté') ||
630
- rawMessage.includes('stabiliser la caméra')) {
631
- hintMessage = state.currentLanguage === 'en'
632
- ? 'Ensure the entire card and your face are clearly inside the frame.'
633
- : 'Assurez-vous que toute la carte et le visage sont clairement dans le cadre.';
662
+ if (rawMessage.includes('card_not_fully_in_frame') || rawMessage.includes('non entièrement visible') || rawMessage.includes('aucun visage détecté') || rawMessage.includes('stabiliser la caméra')) {
663
+ hintMessage = state.currentLanguage === 'en' ? 'Ensure the entire card and your face are clearly inside the frame.' : 'Assurez-vous que toute la carte et le visage sont clairement dans le cadre.';
634
664
  }
635
- // 2. Document/Country Mismatch
636
- // Catches: 'pays sélectionné', 'LPIPS'
637
665
  else if (rawMessage.includes('pays sélectionné')) {
638
- hintMessage = state.currentLanguage === 'en'
639
- ? 'This document does not match the selected country.'
640
- : 'Ce document ne correspond pas au pays sélectionné.';
666
+ hintMessage = state.currentLanguage === 'en' ? 'This document does not match the selected country.' : 'Ce document ne correspond pas au pays sélectionné.';
641
667
  }
642
- // 3. Front/Back Document Mismatch
643
- // Catches: 'ne correspond pas au recto', 'document différent'
644
668
  else if (rawMessage.includes('ne correspond pas au recto') || rawMessage.includes('document différent')) {
645
- hintMessage = state.currentLanguage === 'en'
646
- ? 'Please scan the back of the exact same ID.'
647
- : 'Veuillez scanner le verso de la même pièce d\'identité.';
669
+ hintMessage = state.currentLanguage === 'en' ? 'Please scan the back of the exact same ID.' : 'Veuillez scanner le verso de la même pièce d\'identité.';
648
670
  }
649
- // 4. Data Extraction Failures (OCR & MRZ)
650
- // Catches: 'impossible de lire', 'impossible d'extraire', 'mrz illisible', 'lecture mrz invalide'
651
- else if (rawMessage.includes('impossible de lire') ||
652
- rawMessage.includes('impossible d\'extraire') ||
653
- rawMessage.includes('mrz') ||
654
- rawMessage.includes('extraction') ||
655
- rawMessage.includes('illisible') ||
656
- rawMessage.includes('invalide')) {
657
- hintMessage = state.currentLanguage === 'en'
658
- ? 'Cannot read document text. Avoid glare, improve lighting, and hold steady.'
659
- : 'Impossible de lire le texte. Évitez les reflets, améliorez l\'éclairage et restez immobile.';
671
+ else if (rawMessage.includes('impossible de lire') || rawMessage.includes('impossible d\'extraire') || rawMessage.includes('mrz') || rawMessage.includes('extraction') || rawMessage.includes('illisible') || rawMessage.includes('invalide')) {
672
+ hintMessage = state.currentLanguage === 'en' ? 'Cannot read document text. Avoid glare, improve lighting, and hold steady.' : 'Impossible de lire le texte. Évitez les reflets, améliorez l\'éclairage et restez immobile.';
660
673
  }
661
- // 5. Server Infrastructure & Downtime
662
- // Catches: Cloudflare tunnel errors, 502/503 Gateway timeouts
663
- else if (rawMessage.includes('cloudflare') ||
664
- rawMessage.includes('tunnel') ||
665
- rawMessage.includes('502') ||
666
- rawMessage.includes('503')) {
667
- hintMessage = state.currentLanguage === 'en'
668
- ? 'Server is temporarily offline. Please try again in a few minutes.'
669
- : 'Le serveur est temporairement hors ligne. Veuillez réessayer dans quelques minutes.';
674
+ else if (rawMessage.includes('cloudflare') || rawMessage.includes('tunnel') || rawMessage.includes('502') || rawMessage.includes('503')) {
675
+ hintMessage = state.currentLanguage === 'en' ? 'Server is temporarily offline. Please try again in a few minutes.' : 'Le serveur est temporairement hors ligne. Veuillez réessayer dans quelques minutes.';
670
676
  }
671
- // 6. True Network Errors vs. Fallback Processing
672
677
  else {
673
- // Strictly detect Axios network drops without swallowing unknown custom errors
674
678
  const isNetworkError = error?.isAxiosError || rawMessage === 'network error' || error?.code === 'econnaborted' || rawMessage.includes('network');
675
679
  if (isNetworkError) {
676
- hintMessage = state.currentLanguage === 'en'
677
- ? 'Poor connection. Please check your internet.'
678
- : 'Connexion faible. Veuillez vérifier votre internet.';
680
+ hintMessage = state.currentLanguage === 'en' ? 'Poor connection. Please check your internet.' : 'Connexion faible. Veuillez vérifier votre internet.';
679
681
  }
680
682
  }
681
683
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: hintMessage }));
684
+ isAnalyzingRef.current = false; // 🚨 UNLOCK at end of catch block
682
685
  }
683
686
  }
687
+ else {
688
+ // 🚨 UNLOCK if the camera frame was invalid or empty
689
+ isAnalyzingRef.current = false;
690
+ }
684
691
  };
685
692
  useEffect(() => {
686
693
  if (scanPhase === 'barcode' && pendingCAImage && silentCaptureResult.barcode) {