@sanctum-key/react-native-sdk 1.0.24 → 1.0.26

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 (73) hide show
  1. package/build/package.json +5 -2
  2. package/build/src/components/EnhancedCameraView.d.ts +7 -1
  3. package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
  4. package/build/src/components/EnhancedCameraView.js +88 -30
  5. package/build/src/components/EnhancedCameraView.js.map +1 -1
  6. package/build/src/components/EnhancedCameraView.web.d.ts +5 -1
  7. package/build/src/components/EnhancedCameraView.web.d.ts.map +1 -1
  8. package/build/src/components/EnhancedCameraView.web.js +80 -295
  9. package/build/src/components/EnhancedCameraView.web.js.map +1 -1
  10. package/build/src/components/KYCElements/CameraCapture.js +1 -1
  11. package/build/src/components/KYCElements/CameraCapture.js.map +1 -1
  12. package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
  13. package/build/src/components/KYCElements/CountrySelection.js +38 -218
  14. package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
  15. package/build/src/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
  16. package/build/src/components/KYCElements/CountrySelectionTemplate.js +5 -17
  17. package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
  18. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  19. package/build/src/components/KYCElements/IDCardCapture.js +509 -95
  20. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  21. package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
  22. package/build/src/components/KYCElements/VerificationProgressTemplate.js +93 -51
  23. package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
  24. package/build/src/components/OverLay/type.d.ts +1 -0
  25. package/build/src/components/OverLay/type.d.ts.map +1 -1
  26. package/build/src/components/OverLay/type.js.map +1 -1
  27. package/build/src/components/WebBarcodeScanner.d.ts +9 -0
  28. package/build/src/components/WebBarcodeScanner.d.ts.map +1 -0
  29. package/build/src/components/WebBarcodeScanner.js +152 -0
  30. package/build/src/components/WebBarcodeScanner.js.map +1 -0
  31. package/build/src/config/KYCConfigs.js +2 -2
  32. package/build/src/config/KYCConfigs.js.map +1 -1
  33. package/build/src/config/region_mapping.json +0 -14
  34. package/build/src/modules/api/CardAuthentification.d.ts +2 -0
  35. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  36. package/build/src/modules/api/CardAuthentification.js +22 -39
  37. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  38. package/build/src/modules/api/KYCService.d.ts +7 -2
  39. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  40. package/build/src/modules/api/KYCService.js +163 -57
  41. package/build/src/modules/api/KYCService.js.map +1 -1
  42. package/build/src/types/KYC.types.d.ts +1 -0
  43. package/build/src/types/KYC.types.d.ts.map +1 -1
  44. package/build/src/types/KYC.types.js.map +1 -1
  45. package/build/src/utils/aamvaParser.d.ts +62 -0
  46. package/build/src/utils/aamvaParser.d.ts.map +1 -0
  47. package/build/src/utils/aamvaParser.js +91 -0
  48. package/build/src/utils/aamvaParser.js.map +1 -0
  49. package/build/src/utils/cropByObb.d.ts.map +1 -1
  50. package/build/src/utils/cropByObb.js +4 -3
  51. package/build/src/utils/cropByObb.js.map +1 -1
  52. package/build/src/web/WebKYCEntry.d.ts +1 -1
  53. package/build/src/web/WebKYCEntry.d.ts.map +1 -1
  54. package/build/src/web/WebKYCEntry.js +105 -239
  55. package/build/src/web/WebKYCEntry.js.map +1 -1
  56. package/package.json +5 -2
  57. package/src/components/EnhancedCameraView.tsx +116 -42
  58. package/src/components/EnhancedCameraView.web.tsx +98 -338
  59. package/src/components/KYCElements/CameraCapture.tsx +1 -1
  60. package/src/components/KYCElements/CountrySelection.tsx +60 -240
  61. package/src/components/KYCElements/CountrySelectionTemplate.tsx +7 -20
  62. package/src/components/KYCElements/IDCardCapture.tsx +672 -151
  63. package/src/components/KYCElements/VerificationProgressTemplate.tsx +112 -65
  64. package/src/components/OverLay/type.ts +1 -1
  65. package/src/components/WebBarcodeScanner.tsx +273 -0
  66. package/src/config/KYCConfigs.ts +2 -2
  67. package/src/config/region_mapping.json +0 -14
  68. package/src/modules/api/CardAuthentification.ts +41 -56
  69. package/src/modules/api/KYCService.ts +236 -82
  70. package/src/types/KYC.types.ts +1 -0
  71. package/src/utils/aamvaParser.ts +102 -0
  72. package/src/utils/cropByObb.ts +5 -3
  73. package/src/web/WebKYCEntry.tsx +125 -301
@@ -1,7 +1,7 @@
1
1
  import axios from 'axios';
2
2
  import { GovernmentDocumentType, GovernmentDocumentTypeShorted, OrientationVideoResponse } from '../../types/KYC.types';
3
3
  import { CheckTemplateTypeResponse } from '../../components/OverLay/type';
4
- import { SessionResponse, VerificationResult, VerificationSessionRequest } from './types';
4
+ import { SessionResponse, VerificationSessionRequest } from './types';
5
5
  import { KycEnvironment } from '../../types/env.types';
6
6
  import { logger } from '../../utils/logger';
7
7
  import { Platform } from 'react-native';
@@ -17,6 +17,22 @@ const appendFileToFormData = async (formData: FormData, key: string, uri: string
17
17
  }
18
18
  };
19
19
 
20
+ const documentTypeMap: Record<string, string> = {
21
+ passport: "passport",
22
+ driversLicense: "driving_licence",
23
+ healthInsuranceCard: "health_insurance_card",
24
+ residencePermit: "permanent_residence_card",
25
+ identityCard: "identity_card",
26
+
27
+ nationalId: "identity_card", // We saw this in your earlier logs!
28
+ national_id: "identity_card",
29
+
30
+ drivers_licence: "driving_licence", // Plural 'drivers' to singular 'driving'
31
+ drivers_license: "driving_licence", // American spelling 'license'
32
+ driving_license: "driving_licence", // American spelling 'license'
33
+ permanent_residence: "permanent_residence_card", // Missing the word 'card'
34
+ };
35
+
20
36
  let cachedToken: string | null = null;
21
37
  let tokenExpiryTime: number | null = null;
22
38
  let activeAuthRequest: Promise<string> | null = null;
@@ -63,15 +79,9 @@ export class KYCService {
63
79
  private faceServiceURL = 'https://face-infera.sanctumkey.com';
64
80
  private textExtractionServiceURL = 'https://text-infera.sanctumkey.com';
65
81
  private mrzServiceURL = 'https://mrz-infera.sanctumkey.com';
66
- private barcodeServiceURL = 'https://kyc-engine.SanctumKey.net';
82
+ private barcodeServiceURL = 'https://face-infera.sanctumkey.com';
67
83
  private orientationServiceURL = 'http://18.188.180.154:8080';
68
84
 
69
- // private faceServiceURL = 'https://kyc-engine.transfergratis.net:8000';
70
- // private textExtractionServiceURL = 'https://kyc-engine.transfergratis.net:8006';
71
- // private mrzServiceURL = 'https://kyc-engine.transfergratis.net:8002';
72
- // private barcodeServiceURL = 'https://kyc-engine.transfergratis.net:8000';
73
- // private orientationServiceURL = 'http://18.188.180.154:8080';
74
-
75
85
  constructor(baseURL: string, apiKey: string) {
76
86
  this.baseURL = baseURL;
77
87
  this.apiKey = apiKey;
@@ -288,7 +298,7 @@ export class KYCService {
288
298
  docRegion: string;
289
299
  token: string;
290
300
  postfix: string;
291
- issuingSubdivision?: string; // 🚨 Added parameter
301
+ issuingSubdivision?: string;
292
302
  },
293
303
  env: KycEnvironment = 'PRODUCTION'
294
304
  ): Promise<any> {
@@ -304,7 +314,6 @@ export class KYCService {
304
314
  };
305
315
  }
306
316
 
307
- // 🚨 Destructure issuingSubdivision
308
317
  const { fileUri, docType, docRegion, token, postfix, issuingSubdivision } = params;
309
318
  const formData = new FormData();
310
319
 
@@ -315,7 +324,6 @@ export class KYCService {
315
324
  // Build the base URL
316
325
  let url = `${this.mrzServiceURL}/get_template_version/?doc_type=${encodeURIComponent(docTypeShorted)}&doc_region=${encodeURIComponent(docRegion)}&postfix=${postfix}`;
317
326
 
318
- // 🚨 Append the subdivision strictly for Canada/regional docs
319
327
  if (issuingSubdivision) {
320
328
  url += `&issuing_subdivision=${encodeURIComponent(issuingSubdivision)}`;
321
329
  }
@@ -339,8 +347,8 @@ export class KYCService {
339
347
  fileUri: string;
340
348
  docType: string;
341
349
  docRegion: string;
342
- templatePath: string; // 🚨 Required by API
343
- issuingSubdivision?: string; // 🚨 Required for non-passports
350
+ templatePath: string;
351
+ issuingSubdivision?: string;
344
352
  token: string;
345
353
  postfix?: string
346
354
  }): Promise<any> {
@@ -348,19 +356,18 @@ export class KYCService {
348
356
 
349
357
  // 1. Prepare Form Data
350
358
  const formData = new FormData();
351
- // Prefixing with 'front' or 'back' as strictly required by the docs
352
359
  await appendFileToFormData(formData, 'file', fileUri, `id_card_${postfix}.jpg`, 'image/jpeg');
353
360
 
354
- // 2. Build the URL with ALL required parameters
355
- let queryParams = `doc_type=${encodeURIComponent(docType)}&doc_region=${encodeURIComponent(docRegion)}&template_path=${encodeURIComponent(templatePath)}`;
361
+ let queryParams = `doc_type=${encodeURIComponent(docType)}&doc_region=${encodeURIComponent(docRegion)}&template_path=${encodeURIComponent(templatePath)}&postfix=${postfix}&cleaned=true`;
362
+
356
363
  if (issuingSubdivision) {
357
364
  queryParams += `&issuing_subdivision=${encodeURIComponent(issuingSubdivision)}`;
358
365
  }
366
+ logger.log(`URL PARAMS: ${queryParams}`)
359
367
 
360
368
  const url = `${this.textExtractionServiceURL}/extract_doc_information/?${queryParams}`;
361
369
 
362
370
  try {
363
- // 3. Start the Job
364
371
  const startRes = await axios.post(url, formData, {
365
372
  headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${token}` },
366
373
  timeout: 60000,
@@ -388,14 +395,13 @@ export class KYCService {
388
395
 
389
396
  if (statusData.status === 'completed') {
390
397
  logger.log('Extraction completed successfully', JSON.stringify(truncateFields(statusData.result)));
391
- return statusData.result; // 👈 Returns the actual extracted data
398
+ return statusData.result;
392
399
  }
393
400
 
394
401
  if (statusData.status === 'failed') {
395
402
  throw new Error(statusData.error || "Document extraction failed during processing.");
396
403
  }
397
404
 
398
- // If 'processing', loop continues...
399
405
  }
400
406
 
401
407
  throw new Error("Document extraction timed out waiting for results.");
@@ -407,8 +413,7 @@ export class KYCService {
407
413
  }
408
414
  }
409
415
 
410
- // STEP 2 - barcode extraction
411
- async extractBarcode(params: { fileUri: string; token: string; postfix?: string }, env: KycEnvironment = 'PRODUCTION'): Promise<any> {
416
+ async extractBarcode(params: { fileUri: string; token: string; docType: string; postfix?: string }, env: KycEnvironment = 'PRODUCTION'): Promise<any> {
412
417
  // SANDBOX mode
413
418
  if (env === 'SANDBOX') {
414
419
  console.log("SANDBOX mode: Skipping AI barcode extraction");
@@ -419,35 +424,85 @@ export class KYCService {
419
424
  };
420
425
  }
421
426
 
422
- const { fileUri, token, postfix = 'back' } = params;
427
+ const { fileUri, token, docType, postfix = 'back' } = params;
428
+ logger.log(docType)
423
429
  const formData = new FormData();
424
430
 
425
- // ✅ FIX: Dynamic filename
426
- await appendFileToFormData(formData, 'file', fileUri, `id_card_${postfix}.jpg`, 'image/jpeg');
431
+ await appendFileToFormData(formData, 'file', fileUri, `id_${postfix}_photo.jpg`, 'image/jpeg');
432
+
433
+ const url = `${this.barcodeServiceURL}/decode_barcode/?doc_type=${encodeURIComponent(docType)}`;
427
434
 
428
- const url = `${this.barcodeServiceURL}/decode_barcode/`;
429
435
  const attempt = async () => {
430
436
  try {
431
437
  const res = await axios.post(url, formData, {
432
- headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${token}`, },
438
+ headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${token}` },
433
439
  timeout: 60000,
434
440
  });
435
- logger.log('extractBarcode res', JSON.stringify(res.data, null, 2));
436
- if (Object.keys(res.data).length === 0) throw new Error('No data found');
441
+
442
+ logger.log('📦 Raw extractBarcode response:', JSON.stringify(res.data, null, 2));
443
+
444
+
437
445
  return res.data;
438
446
  } catch (e: any) {
439
- throw new Error(e?.message || 'Erreur de détection du barcode');
447
+ throw new Error(e?.message || 'Erreur de détection du code-barres');
440
448
  }
441
449
  };
450
+
442
451
  try {
443
452
  return await attempt();
444
453
  } catch (e) {
445
- logger.error('Error extracting Barcode text:', JSON.stringify(e));
454
+ logger.error('❌ Error extracting Barcode text:', JSON.stringify(e));
446
455
  throw e;
447
456
  }
448
457
  }
449
458
 
450
- // STEP 3 - MRZ TEXT EXTRACTION
459
+ async formatBarcodeData(params: { textInput: string; token: string }, env: KycEnvironment = 'PRODUCTION'): Promise<any> {
460
+ // SANDBOX mode
461
+ if (env === 'SANDBOX') {
462
+ console.log("SANDBOX mode: Skipping AI barcode formatting");
463
+ logger.log("SANDBOX mode: Returning mock formatted barcode response");
464
+ return {
465
+ barcode_type: "SANDBOX_MOCK",
466
+ first_name: "JOHN",
467
+ last_name: "DOE",
468
+ card_number: "000000000000"
469
+ };
470
+ }
471
+
472
+ const { textInput, token } = params;
473
+
474
+ const formData = new URLSearchParams();
475
+ formData.append('text_input', textInput);
476
+
477
+ const url = `${this.barcodeServiceURL}/format_barcode_data/`;
478
+
479
+ const attempt = async () => {
480
+ try {
481
+ const res = await axios.post(url, formData.toString(), {
482
+ headers: {
483
+ 'Content-Type': 'application/x-www-form-urlencoded',
484
+ 'Authorization': `Bearer ${token}`
485
+ },
486
+ timeout: 60000,
487
+ });
488
+
489
+ logger.log('📦 Raw formatBarcodeData response:', JSON.stringify(res.data, null, 2));
490
+
491
+ return res.data;
492
+ } catch (e: any) {
493
+ throw new Error(e?.message || 'Erreur de formatage du code-barres');
494
+ }
495
+ };
496
+
497
+ try {
498
+ return await attempt();
499
+ } catch (e) {
500
+ logger.error('❌ Error formatting Barcode text:', JSON.stringify(e));
501
+ throw e;
502
+ }
503
+ }
504
+
505
+
451
506
  async extractMrzText(
452
507
  params: {
453
508
  fileUri: string;
@@ -500,8 +555,6 @@ export class KYCService {
500
555
  let response;
501
556
 
502
557
  if (Platform.OS === 'web') {
503
- // 🚨 THE FIX: Completely bypass React Native Web's networking polyfills.
504
- // We explicitly grab the browser's native APIs so the bundler cannot interfere.
505
558
  const nativeFetch = window.fetch;
506
559
  const NativeFormData = window.FormData;
507
560
 
@@ -642,57 +695,127 @@ export class KYCService {
642
695
  }
643
696
  }
644
697
 
698
+
645
699
  async verificationSession(payload: VerificationSessionRequest): Promise<any> {
646
- try {
647
- const { session_id, step, data, templateId, action, apiKey } = payload;
648
-
649
- // 1. Safe Auth Resolution
650
- const token = apiKey ? undefined : await authentification();
651
-
652
- if (!apiKey && !token) {
653
- throw new Error("Authentication failed: No API key or Token available.");
654
- }
700
+ try {
701
+ const { session_id, step, data, templateId, action, apiKey } = payload;
702
+ const token = apiKey ? undefined : await authentification();
703
+
704
+ if (!apiKey && !token) {
705
+ throw new Error("Authentication failed: No API key or Token available.");
706
+ }
655
707
 
708
+ // 1. Original Shallow Clone (Preserves file paths perfectly)
709
+ const normalizedData = { ...data };
656
710
 
657
- const payloadData = {
658
- action: action,
659
- data: {
660
- ...data,
661
- step: step,
662
- templateId: templateId,
663
- ...(action === "location_permission" ? { permissionGranted: true } : {}),
664
- },
665
- session_id: session_id,
666
- timestamp: new Date().toISOString(),
711
+ logger.log("🔍 [DocType Check] Raw incoming data.metadata.document_type:", normalizedData?.metadata?.document_type);
712
+ logger.log("🔍 [DocType Check] Raw incoming data.documentType:", normalizedData?.documentType);
713
+
714
+ // Enhanced fallback to guarantee we find the document type
715
+ const rawDocType =
716
+ normalizedData?.metadata?.document_type ||
717
+ normalizedData?.documentType ||
718
+ normalizedData?.documents?.documentType;
719
+
720
+ if (rawDocType) {
721
+ const originalDocType = rawDocType;
722
+ const mappedDocType = documentTypeMap[originalDocType] ?? originalDocType;
723
+
724
+ normalizedData.metadata = {
725
+ ...normalizedData.metadata,
726
+ document_type: mappedDocType,
667
727
  };
728
+
729
+ normalizedData.documentType = mappedDocType;
668
730
 
669
- const url = `${KYCConfig.getBackendUrl()}/verification/api/kyc/sessions/${session_id}/steps/${step}/`;
731
+ if (normalizedData.documents) {
732
+ normalizedData.documents = {
733
+ ...normalizedData.documents,
734
+ documentType: mappedDocType
735
+ };
670
736
 
671
- // 3. Clean Logging
672
- const logPayload = truncateFields({ payloadData, session_id, step });
673
- logger.log('verificationSession payload', JSON.stringify({
674
- url,
675
- apiKey: apiKey ? '***HIDDEN***' : 'None', // Don't log the raw API key!
676
- token: token ? '***HIDDEN***' : 'None',
677
- payload: logPayload
678
- }, null, 2));
737
+ const isSingleSided = ['passport', 'visa'].includes(mappedDocType.toLowerCase());
738
+ if (isSingleSided && !normalizedData.documents.back) {
739
+ normalizedData.documents.back = {};
740
+ }
679
741
 
680
- // 4. API Execution
681
- const res = await axios.post<SessionResponse>(url, payloadData, {
682
- headers: {
683
- 'Content-Type': 'application/json',
684
- ...(apiKey ? { 'Authorization': `ApiKey ${apiKey}` } : { 'Authorization': `Bearer ${token}` })
742
+ if (normalizedData.documents.front?.mrz && typeof normalizedData.documents.front.mrz === 'string') {
743
+ try {
744
+ normalizedData.documents.front = {
745
+ ...normalizedData.documents.front,
746
+ mrz: JSON.parse(normalizedData.documents.front.mrz)
747
+ };
748
+ } catch (e) {
749
+ logger.log("Warning: Could not parse front MRZ JSON");
685
750
  }
686
- });
687
-
688
- logger.log('verificationSession res', JSON.stringify(truncateFields(res.data), null, 2));
689
- return res.data;
751
+ }
690
752
 
691
- } catch (error) {
692
- logger.error('Error validating component:', JSON.stringify(errorMessage(error), null, 2));
693
- throw new Error(errorMessage(error));
753
+ if (normalizedData.documents.back?.mrz && typeof normalizedData.documents.back.mrz === 'string') {
754
+ try {
755
+ normalizedData.documents.back = {
756
+ ...normalizedData.documents.back,
757
+ mrz: JSON.parse(normalizedData.documents.back.mrz)
758
+ };
759
+ } catch (e) {
760
+ logger.log("Warning: Could not parse back MRZ JSON");
761
+ }
762
+ }
763
+ }
694
764
  }
765
+
766
+ const payloadData = {
767
+ action,
768
+ data: {
769
+ ...normalizedData,
770
+ step,
771
+ templateId,
772
+ },
773
+ session_id,
774
+ timestamp: new Date().toISOString(),
775
+ };
776
+
777
+ const url = `${KYCConfig.getBackendUrl()}/verification/api/kyc/sessions/${session_id}/steps/${step}/`;
778
+ const logPayload = truncateFields({ payloadData, session_id, step });
779
+
780
+ logger.log(
781
+ "verificationSession payload",
782
+ JSON.stringify(
783
+ {
784
+ url,
785
+ apiKey: apiKey ? "***HIDDEN***" : "None",
786
+ token: token ? "***HIDDEN***" : "None",
787
+ payload: logPayload,
788
+ },
789
+ null,
790
+ 2
791
+ )
792
+ );
793
+
794
+ const headers: Record<string, string> = {
795
+ "Content-Type": "application/json",
796
+ ...(apiKey ? { Authorization: `ApiKey ${apiKey}` } : {}),
797
+ ...(token && !apiKey ? { Authorization: `Bearer ${token}` } : {})
798
+ };
799
+
800
+ const res = await axios.post<SessionResponse>(url, payloadData, { headers });
801
+
802
+ logger.log(
803
+ "verificationSession res",
804
+ JSON.stringify(truncateFields(res.data), null, 2)
805
+ );
806
+ return res.data;
807
+
808
+ } catch (error: any) {
809
+ const parsedError = errorMessage(error);
810
+ const finalErrorMessage = parsedError || error?.message || "Unknown validation error";
811
+
812
+ logger.error(
813
+ "Error validating component:",
814
+ JSON.stringify(finalErrorMessage, null, 2)
815
+ );
816
+ throw new Error(typeof finalErrorMessage === 'string' ? finalErrorMessage : JSON.stringify(finalErrorMessage));
695
817
  }
818
+ }
696
819
 
697
820
  /** Send email verification code. POST /api/v1/accounts/email/send/ */
698
821
  async sendEmailVerificationCode(
@@ -734,16 +857,49 @@ export class KYCService {
734
857
  return res.data;
735
858
  }
736
859
 
737
- async getVerificationResult(session_id: string): Promise<VerificationResult> {
860
+ async getVerificationResult(session_id: string, apiKey?: string): Promise<any> {
738
861
  try {
739
- const token = await authentification();
862
+
863
+ const token = apiKey ? undefined : await authentification();
864
+
740
865
  const url = `${KYCConfig.getBackendUrl()}/verification/api/kyc/result/?session_id=${session_id}`;
741
- const res = await axios.get<VerificationResult>(url,
742
- { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` } });
743
- logger.log('getVerificationResult res', JSON.stringify(truncateFields(res.data), null, 2));
866
+
867
+ const controller = new AbortController();
868
+ const timeoutId = setTimeout(() => {
869
+ logger.log("⏳ 8 seconds passed. Force-killing hanging Axios request...");
870
+ controller.abort();
871
+ }, 8000);
872
+
873
+ const headers: Record<string, string> = {
874
+ 'Content-Type': 'application/json',
875
+ };
876
+
877
+ if (apiKey) {
878
+ headers["Authorization"] = `ApiKey ${apiKey}`;
879
+ } else if (token) {
880
+ headers["Authorization"] = `Bearer ${token}`;
881
+ }
882
+
883
+ logger.log("3. Firing Axios GET to:", url);
884
+
885
+ const res = await axios.get(url, {
886
+ headers,
887
+ signal: controller.signal
888
+ });
889
+
890
+ // Clear the timeout if the request succeeds!
891
+ clearTimeout(timeoutId);
892
+
744
893
  return res.data;
745
- } catch (error) {
746
- logger.error('Error getting verification result:', JSON.stringify(errorMessage(error), null, 2));
894
+
895
+ } catch (error: any) {
896
+ // Catch the AbortController kill signal and format it as a timeout
897
+ if (axios.isCancel(error) || error.name === 'AbortError' || error.name === 'CanceledError') {
898
+ logger.error('Axios request was forcefully aborted due to timeout');
899
+ throw new Error('Timeout exceeded');
900
+ }
901
+
902
+ logger.error('Error getting verification result:', JSON.stringify(errorMessage(error) || error.message, null, 2));
747
903
  throw error;
748
904
  }
749
905
  }
@@ -852,7 +1008,6 @@ async validateBackTemplate(params: {
852
1008
 
853
1009
  const { region, docType, frontTemplateName, backTemplateName, subRegion, token } = params;
854
1010
 
855
- // 🚨 THE FIX: Format the payload as URL Query Parameters
856
1011
  const queryParams = new URLSearchParams({
857
1012
  region: region,
858
1013
  doc_type: docType,
@@ -957,7 +1112,6 @@ export const authentification = async (): Promise<string> => {
957
1112
  } catch (error: any) {
958
1113
  cachedToken = null; // Clear bad cache
959
1114
 
960
- // 🚨 FIX: Extract Keycloak's exact error message so it doesn't log as {}
961
1115
  const safeErrorMessage =
962
1116
  error?.response?.data?.error_description ||
963
1117
  error?.response?.data?.error ||
@@ -505,6 +505,7 @@ export interface ISilentCaptureResult {
505
505
  success: boolean;
506
506
  path?: string;
507
507
  mrz?: string;
508
+ barcode?: string | any;
508
509
  error?: string;
509
510
  isAnalyzing?: boolean;
510
511
  bbox?: IBbox;
@@ -0,0 +1,102 @@
1
+ export const parseAAMVABarcode = (rawBarcode: string) => {
2
+ // If it's a short 1D barcode, just return the number
3
+ if (!rawBarcode || rawBarcode.length < 50) {
4
+ return { card_number: rawBarcode, barcode_type: "1D" };
5
+ }
6
+
7
+ const fields: Record<string, string> = {};
8
+ const additionalInfo: string[] = [];
9
+
10
+ // 1. Sanitize literal escaped newlines (just in case the SDK stringified them)
11
+ let normalized = rawBarcode.replace(/\\n/g, '\n').replace(/\\r/g, '\r');
12
+
13
+ // 2. Split by standard linebreaks AND AAMVA hidden control characters (FS, GS, RS)
14
+ const segments = normalized.split(/[\n\r\u001c\u001d\u001e]+/);
15
+
16
+ segments.forEach(segment => {
17
+ let clean = segment.trim();
18
+
19
+ // 3. Fix the Canadian/BC quirk: Subfile identifiers attached directly to the first data element
20
+ // e.g., "IDDAQ123456..." or "DLDAQ123456..."
21
+ if (clean.startsWith('DL') && clean.length > 5 && /^[A-Z]{3}/.test(clean.substring(2))) {
22
+ clean = clean.substring(2);
23
+ } else if (clean.startsWith('ID') && clean.length > 5 && /^[A-Z]{3}/.test(clean.substring(2))) {
24
+ clean = clean.substring(2);
25
+ }
26
+
27
+ // 4. Forcefully extract the 3-letter code and its data
28
+ const match = clean.match(/^([A-Z]{3})(.+)/);
29
+ if (match) {
30
+ fields[match[1]] = match[2].trim();
31
+ } else if (clean.length > 0 && !clean.startsWith('@')) {
32
+ additionalInfo.push(clean);
33
+ }
34
+ });
35
+
36
+ // Helper to format AAMVA dates (MMDDYYYY or YYYYMMDD) into YYYY-MM-DD
37
+ const formatDate = (rawDate: string | undefined) => {
38
+ if (!rawDate || rawDate.length !== 8) return "NONE";
39
+
40
+ const firstFour = parseInt(rawDate.substring(0, 4), 10);
41
+ // If the first 4 digits are a valid year, it's YYYYMMDD
42
+ if (firstFour > 1900 && firstFour < 2100) {
43
+ return `${rawDate.substring(0, 4)}-${rawDate.substring(4, 6)}-${rawDate.substring(6, 8)}`;
44
+ }
45
+ // Otherwise, it's MMDDYYYY
46
+ return `${rawDate.substring(4, 8)}-${rawDate.substring(0, 2)}-${rawDate.substring(2, 4)}`;
47
+ };
48
+
49
+ // Helper to safely fetch a field or its fallback code
50
+ const getVal = (code: string, fallbackCode?: string) => {
51
+ const val = fields[code] || (fallbackCode ? fields[fallbackCode] : undefined);
52
+ return (val && val.trim().length > 0) ? val.trim() : "NONE";
53
+ };
54
+
55
+ // Standardize gender codes
56
+ let gender = getVal("DBC");
57
+ if (gender === "1") gender = "M";
58
+ if (gender === "2") gender = "F";
59
+
60
+ return {
61
+ barcode_type: normalized.includes("ANSI") ? "ANSI" : "UNKNOWN",
62
+ restriction_codes: getVal("DAR", "DCD"),
63
+ compliance_type: getVal("DDA"),
64
+
65
+ // --- Document Dates ---
66
+ issue_date: formatDate(fields["DBD"]),
67
+ expiry_date: formatDate(fields["DBA"]),
68
+ birth_date: formatDate(fields["DBB"]),
69
+ under_18_until: formatDate(fields["DBH"]),
70
+ under_21_until: formatDate(fields["DBI"]),
71
+
72
+ // --- Demographics ---
73
+ last_name: getVal("DCS", "DCA"),
74
+ first_name: getVal("DAC", "DCT"),
75
+ middle_name: getVal("DAD", "DCU"),
76
+ name_suffix: getVal("DCU"),
77
+ customer_full_name: getVal("DAA"),
78
+ gender: gender,
79
+ eye_color: getVal("DAY"),
80
+ hair_color: getVal("DAZ"),
81
+ height: getVal("DAU"),
82
+ weight: getVal("DAW", "DAX"),
83
+
84
+ // --- Location ---
85
+ street_address: getVal("DAG"),
86
+ street_address_2: getVal("DAH"),
87
+ city: getVal("DAI"),
88
+ province: getVal("DAJ"),
89
+ postal_code: getVal("DAK"),
90
+ country: getVal("DCG") !== "NONE" ? getVal("DCG") : "CAN",
91
+
92
+ // --- ID Numbers & Security ---
93
+ card_number: getVal("DAQ"),
94
+ document_discriminator: getVal("DCF"),
95
+ inventory_control_number: getVal("DCK", "DCB"),
96
+
97
+ // The raw dump in case you ever need an obscure field
98
+ raw_fields: fields,
99
+
100
+ additional_info: additionalInfo.length > 0 ? additionalInfo : ["NONE"]
101
+ };
102
+ };
@@ -147,11 +147,13 @@ async function normalizeOrientationAndGetDimensions(
147
147
  });
148
148
  }
149
149
 
150
- // Native: re-encode to bake EXIF rotation into actual pixels
151
150
  const result = await ImageManipulator.manipulateAsync(
152
151
  uri,
153
- [], // No transforms — just re-encode to normalize
154
- { compress: 1, format: ImageManipulator.SaveFormat.JPEG }
152
+ [],
153
+ {
154
+ compress: 1,
155
+ format: ImageManipulator.SaveFormat.JPEG
156
+ }
155
157
  );
156
158
  return { normalizedUri: result.uri, width: result.width, height: result.height };
157
159
  }