@sanctum-key/react-native-sdk 1.0.41 → 1.0.44

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanctum-key/react-native-sdk",
3
- "version": "1.0.41",
3
+ "version": "1.0.44",
4
4
  "description": "Sanctum Key React Native SDK",
5
5
  "main": "build/src/index.js",
6
6
  "types": "build/src/index.d.ts",
@@ -1 +1 @@
1
- {"version":3,"file":"IDCardCapture.d.ts","sourceRoot":"","sources":["../../../../src/components/KYCElements/IDCardCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAIpE,OAAO,EAAE,iBAAiB,EAAoI,MAAM,uBAAuB,CAAC;AAqB5L,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAkCD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA8vBtD,CAAC"}
1
+ {"version":3,"file":"IDCardCapture.d.ts","sourceRoot":"","sources":["../../../../src/components/KYCElements/IDCardCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAIpE,OAAO,EAAE,iBAAiB,EAAoI,MAAM,uBAAuB,CAAC;AAuB5L,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAkCD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAo2BtD,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo, useRef, useState } from 'react';
2
- import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator, TouchableOpacity, useWindowDimensions } from 'react-native';
2
+ import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator, TouchableOpacity, useWindowDimensions, Alert } from 'react-native';
3
3
  import { showAlert } from '../../utils/platformAlert';
4
4
  import { EnhancedCameraView } from '../EnhancedCameraView';
5
5
  import { GovernmentDocumentTypeShorted, GovernmentDocumentTypeBackend } from '../../types/KYC.types';
@@ -15,7 +15,9 @@ 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 } from '../../modules/api/KYCService';
18
+ import kycService, { authentification, clearAuthToken } from '../../modules/api/KYCService';
19
+ import { Linking } from 'react-native';
20
+ import { Camera } from 'react-native-vision-camera';
19
21
  const ISO_TO_COUNTRY_NAME = {
20
22
  'KE': 'Kenya', 'CM': 'Cameroon', 'NG': 'Nigeria', 'CA': 'Canada',
21
23
  'FR': 'France', 'GH': 'Ghana', 'ZA': 'South Africa', 'GB': 'Britain',
@@ -189,6 +191,52 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
189
191
  }
190
192
  }
191
193
  }, [value, currentSide]);
194
+ const handleStartCamera = async () => {
195
+ // Web browsers handle permissions natively when the media stream is requested
196
+ if (Platform.OS === 'web') {
197
+ setShowCamera(true);
198
+ refreshCamera();
199
+ actions.showCustomStepper(false);
200
+ return;
201
+ }
202
+ try {
203
+ // 1. Get current status
204
+ let status = await Camera.getCameraPermissionStatus();
205
+ // 2. If it's anything other than granted, request it
206
+ if (status !== 'granted') {
207
+ status = await Camera.requestCameraPermission();
208
+ }
209
+ // 3. Check final status
210
+ if (status === 'granted') {
211
+ // Permission given, start camera
212
+ setShowCamera(true);
213
+ refreshCamera();
214
+ actions.showCustomStepper(false);
215
+ }
216
+ else {
217
+ // Permission permanently denied or restricted
218
+ Alert.alert(state.currentLanguage === 'en' ? 'Camera Access Required' : 'Accès à la caméra requis', state.currentLanguage === 'en'
219
+ ? 'We need camera access to scan your ID. Please tap "Settings" to enable it.'
220
+ : 'Nous avons besoin de l\'accès à la caméra pour scanner votre pièce d\'identité. Veuillez appuyer sur "Paramètres" pour l\'activer.', [
221
+ {
222
+ text: state.currentLanguage === 'en' ? 'Cancel' : 'Annuler',
223
+ style: 'cancel'
224
+ },
225
+ {
226
+ text: state.currentLanguage === 'en' ? 'Settings' : 'Paramètres',
227
+ onPress: () => Linking.openSettings()
228
+ }
229
+ ]);
230
+ }
231
+ }
232
+ catch (error) {
233
+ console.warn('Failed to request camera permission', error);
234
+ // Fallback
235
+ setShowCamera(true);
236
+ refreshCamera();
237
+ actions.showCustomStepper(false);
238
+ }
239
+ };
192
240
  const cameraConfig = useMemo(() => {
193
241
  let instructions = selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr) : getLocalizedText(component.instructions);
194
242
  if (scanPhase === 'barcode')
@@ -397,6 +445,31 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
397
445
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: 'Failed to process barcode. Please retake the photo.' }));
398
446
  }
399
447
  };
448
+ const handleExitKYCFlow = () => {
449
+ clearAuthToken();
450
+ // 2. Clear all local camera/capture states
451
+ setCapturedImages({});
452
+ if (onValueChange)
453
+ onValueChange({}); // Clear the parent's form state
454
+ setCurrentSide('front');
455
+ setScanPhase('image');
456
+ setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '' });
457
+ scannedBarcodeRef.current = null;
458
+ setPendingCAImage(null);
459
+ setIsProcessingCapture(false);
460
+ setProcessingImagePath(null);
461
+ // 3. Restart the flow
462
+ if (actions.resetTemplate) {
463
+ // This is the best method: it wipes the context and rebuilds the initial state
464
+ actions.resetTemplate();
465
+ }
466
+ else if (actions.goToComponent && state.template?.components?.length > 0) {
467
+ actions.goToComponent(state.template.components[0].id);
468
+ }
469
+ else {
470
+ console.warn("Could not restart flow. Check your KYC context actions.");
471
+ }
472
+ };
400
473
  const handleSilentCapture = async (result) => {
401
474
  if (isProcessingCaptureRef.current)
402
475
  return;
@@ -404,7 +477,6 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
404
477
  return;
405
478
  if (isAnalyzingRef.current)
406
479
  return;
407
- // USE REFS TO AVOID IOS NATIVE STALE CLOSURE
408
480
  const side = currentSideRef.current;
409
481
  const captured = capturedImagesRef.current;
410
482
  const docType = selectedDocTypeRef.current;
@@ -497,40 +569,51 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
497
569
  }
498
570
  catch (error) {
499
571
  clearTimeout(watchdog);
500
- const rawMessage = (error?.message || '').toLowerCase();
572
+ const rawMessage = (typeof error === 'string' ? error : (error?.message || '')).toLowerCase();
573
+ const statusCode = error?.response?.status || error?.status;
574
+ if (statusCode === 401 ||
575
+ statusCode === 403 ||
576
+ rawMessage.includes('401') ||
577
+ rawMessage.includes('unauthorized') ||
578
+ rawMessage.includes('token') ||
579
+ rawMessage.includes('unauthenticated')) {
580
+ setShowCamera(false);
581
+ setIsProcessingCapture(false);
582
+ setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: '' }));
583
+ // Use React Native's native Alert to wait for the user's tap
584
+ Alert.alert(state.currentLanguage === 'en' ? 'Session Expired' : 'Session Expirée', state.currentLanguage === 'en'
585
+ ? 'Your secure session has expired. Please restart the process.'
586
+ : 'Votre session sécurisée a expiré. Veuillez recommencer.', [
587
+ {
588
+ text: state.currentLanguage === 'en' ? 'Restart' : 'Recommencer',
589
+ onPress: () => handleExitKYCFlow() // Redirects ONLY when they click the button
590
+ }
591
+ ], { cancelable: false } // Forces them to click the button
592
+ );
593
+ return;
594
+ }
501
595
  let hintMessage = '';
596
+ // 2. Business Logic Errors
502
597
  if (rawMessage.includes('pays sélectionné') || rawMessage.includes('country mismatch')) {
503
- hintMessage = state.currentLanguage === 'en'
504
- ? 'Does not match the selected country.'
505
- : 'Ne correspond pas au pays sélectionné.';
598
+ hintMessage = state.currentLanguage === 'en' ? 'Does not match the selected country.' : 'Ne correspond pas au pays sélectionné.';
506
599
  }
507
600
  else if (rawMessage.includes('impossible') || rawMessage.includes('unreadable')) {
508
- hintMessage = state.currentLanguage === 'en'
509
- ? 'Move to better lighting.'
510
- : 'Améliorez l\'éclairage.';
601
+ hintMessage = state.currentLanguage === 'en' ? 'Move to better lighting and ensure card is in frame.' : 'Améliorez l\'éclairage et assurez-vous que la carte est bien cadrée.';
511
602
  }
512
603
  else if (rawMessage.includes('card_not_fully_in_frame') || rawMessage.includes('frame')) {
513
- hintMessage = state.currentLanguage === 'en'
514
- ? 'Move closer to the document.'
515
- : 'Rapprochez-vous du document.';
604
+ hintMessage = state.currentLanguage === 'en' ? 'Move closer to the document.' : 'Rapprochez-vous du document.';
516
605
  }
517
606
  else if (rawMessage.includes('ne correspond pas au recto')) {
518
- hintMessage = state.currentLanguage === 'en'
519
- ? 'Please scan the same ID.'
520
- : 'Veuillez scanner la même pièce.';
607
+ hintMessage = state.currentLanguage === 'en' ? 'Please scan the same ID.' : 'Veuillez scanner la même pièce.';
521
608
  }
522
- // 2. If it's not a known logical error, assume it's a network/API failure
609
+ // 3. Fallback to Network/API failure
523
610
  else {
524
611
  const isNetworkError = error.isAxiosError || error.message === 'Network Error' || error.code === 'ECONNABORTED' || rawMessage.includes('network') || !error.response;
525
612
  if (isNetworkError) {
526
- hintMessage = state.currentLanguage === 'en'
527
- ? 'Poor connection. Retrying...'
528
- : 'Connexion faible. Nouvelle tentative...';
613
+ hintMessage = state.currentLanguage === 'en' ? 'Poor connection. Retrying...' : 'Connexion faible. Nouvelle tentative...';
529
614
  }
530
615
  else {
531
- hintMessage = state.currentLanguage === 'en'
532
- ? 'Analyzing...' // Generic fallback if an unknown API error occurs
533
- : 'Analyse en cours...';
616
+ hintMessage = state.currentLanguage === 'en' ? 'Analyzing...' : 'Analyse en cours...';
534
617
  }
535
618
  }
536
619
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: hintMessage }));
@@ -684,7 +767,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
684
767
  </View>
685
768
  <Button title={state.currentLanguage === "en" ? "Continue on Phone" : "Continuer sur le téléphone"} onPress={() => setShowQRModal(true)} variant="primary" size="large" fullWidth/>
686
769
  </>) : (<>
687
- <Button title={state.currentLanguage === "en" ? "Start Scanning" : "Commencer la numérisation"} onPress={() => { setShowCamera(true); refreshCamera(); actions.showCustomStepper(false); }} variant="primary" size="large" fullWidth/>
770
+ <Button title={state.currentLanguage === "en" ? "Start Scanning" : "Commencer la numérisation"} onPress={handleStartCamera} variant="primary" size="large" fullWidth/>
688
771
  {Platform.OS === 'web' && (<Button title={state.currentLanguage === "en" ? "Continue on Phone" : "Continuer sur le téléphone"} onPress={() => setShowQRModal(true)} variant="outline" size="large" fullWidth/>)}
689
772
  </>)}
690
773
  </View>)}