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

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 (128) hide show
  1. package/README.md +7 -2
  2. package/android/build/.transforms/3db9b15041d4285a820fc84462995ea1/results.bin +1 -0
  3. package/android/build/.transforms/3db9b15041d4285a820fc84462995ea1/transformed/classes/classes_dex/classes.dex +0 -0
  4. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  5. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  6. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  7. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  8. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  9. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  10. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  11. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  12. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  13. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  14. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  15. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  16. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  17. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
  18. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  19. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  20. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  21. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  22. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  23. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  24. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  25. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  26. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  27. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  28. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  29. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +1 -1
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  56. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  57. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$BarcodeRegion.class +0 -0
  58. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$Companion.class +0 -0
  59. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$1$5$1.class → SanctumKeySdkModule$definition$1$6$1.class} +0 -0
  60. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$1$5$2.class → SanctumKeySdkModule$definition$1$6$2.class} +0 -0
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$3.class +0 -0
  70. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$4.class +0 -0
  71. 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
  72. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$6.class +0 -0
  73. 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
  74. 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
  75. 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
  76. 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
  77. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule.class +0 -0
  78. package/android/build.gradle +7 -0
  79. package/android/src/main/java/kyc/sanctumkey/com/SanctumKeySdkModule.kt +428 -1
  80. package/build/package.json +3 -1
  81. package/build/src/SanctumKeySdkModule.d.ts +1 -0
  82. package/build/src/SanctumKeySdkModule.d.ts.map +1 -1
  83. package/build/src/SanctumKeySdkModule.js.map +1 -1
  84. package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
  85. package/build/src/components/EnhancedCameraView.js +50 -41
  86. package/build/src/components/EnhancedCameraView.js.map +1 -1
  87. package/build/src/components/EnhancedCameraView.web.d.ts +2 -0
  88. package/build/src/components/EnhancedCameraView.web.d.ts.map +1 -1
  89. package/build/src/components/EnhancedCameraView.web.js +11 -6
  90. package/build/src/components/EnhancedCameraView.web.js.map +1 -1
  91. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  92. package/build/src/components/KYCElements/IDCardCapture.js +499 -78
  93. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  94. package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
  95. package/build/src/components/TemplateKYCExample.js +3 -9
  96. package/build/src/components/TemplateKYCExample.js.map +1 -1
  97. package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  98. package/build/src/components/TemplateKYCFlowRefactored.js +0 -2
  99. package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
  100. package/build/src/components/WebBarcodeScanner.d.ts +2 -0
  101. package/build/src/components/WebBarcodeScanner.d.ts.map +1 -1
  102. package/build/src/components/WebBarcodeScanner.js +99 -85
  103. package/build/src/components/WebBarcodeScanner.js.map +1 -1
  104. package/build/src/modules/api/KYCService.d.ts +1 -0
  105. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  106. package/build/src/modules/api/KYCService.js +1 -1
  107. package/build/src/modules/api/KYCService.js.map +1 -1
  108. package/build/src/modules/api/types.d.ts +1 -0
  109. package/build/src/modules/api/types.d.ts.map +1 -1
  110. package/build/src/modules/api/types.js.map +1 -1
  111. package/build/src/web/WebKYCEntry.d.ts.map +1 -1
  112. package/build/src/web/WebKYCEntry.js +50 -32
  113. package/build/src/web/WebKYCEntry.js.map +1 -1
  114. package/package.json +3 -1
  115. package/plugin/build/withVisionCamera.d.ts +1 -0
  116. package/plugin/build/withVisionCamera.js +13 -1
  117. package/plugin/src/withVisionCamera.js +18 -2
  118. package/plugin/src/withVisionCamera.ts +19 -2
  119. package/src/SanctumKeySdkModule.ts +1 -0
  120. package/src/components/EnhancedCameraView.tsx +52 -41
  121. package/src/components/EnhancedCameraView.web.tsx +15 -5
  122. package/src/components/KYCElements/IDCardCapture.tsx +552 -75
  123. package/src/components/TemplateKYCExample.tsx +6 -14
  124. package/src/components/TemplateKYCFlowRefactored.tsx +0 -2
  125. package/src/components/WebBarcodeScanner.tsx +147 -198
  126. package/src/modules/api/KYCService.ts +3 -3
  127. package/src/modules/api/types.ts +1 -0
  128. package/src/web/WebKYCEntry.tsx +89 -50
@@ -87,40 +87,32 @@ export function TemplateKYCExample({
87
87
 
88
88
  if ((data.status === 'success' || data.status === 'in_progress') && data.result) {
89
89
 
90
- // 1. Grab the backend data directly from the result (no docKey needed)
91
90
  const backendData = data.result.data;
92
91
 
93
92
  if (backendData) {
94
93
  const personalInfo = backendData.user_data?.personal_information || {};
95
-
96
- // Ensure we capture the session ID from whichever prop was provided
97
94
  const activeSessionId = kyc_session_id || existingSessionId || backendData.session_id || "";
95
+
96
+
97
+ const docType = (backendData as any).document_type || (backendData as any).documentType;
98
98
 
99
- // 2. Strict mapping to your expected format with improved fallbacks
100
99
  finalPayload = {
101
100
  data: {
101
+ document_type: docType,
102
102
  verification_id: backendData.verification_id,
103
103
  verification_status: backendData.verification_status,
104
104
  session_id: activeSessionId,
105
105
  user_data: {
106
106
  personal_information: {
107
- // Safely fallback to given_name and surname if first/last are empty strings
108
107
  first_name: personalInfo.first_name || personalInfo.given_name || undefined,
109
108
  last_name: personalInfo.last_name || personalInfo.surname || undefined,
110
109
  given_name: personalInfo.given_name || undefined,
111
110
  surname: personalInfo.surname || undefined,
112
-
113
111
  address: personalInfo.address || undefined,
114
- city: personalInfo.city || undefined,
115
- postal_code: personalInfo.postal_code || undefined,
116
112
  expiry_date: personalInfo.expiry_date || undefined,
117
113
  issue_date: personalInfo.issue_date || undefined,
118
-
119
- // Fallback to birth_date if date_of_birth is missing
120
114
  date_of_birth: personalInfo.date_of_birth || personalInfo.birth_date || undefined,
121
115
  birth_date: personalInfo.birth_date || undefined,
122
-
123
- country: personalInfo.country || undefined,
124
116
  document_number: personalInfo.document_number || undefined,
125
117
  nationality: personalInfo.nationality || undefined,
126
118
  sex: personalInfo.sex || undefined,
@@ -129,7 +121,7 @@ export function TemplateKYCExample({
129
121
  }
130
122
  };
131
123
 
132
- sdkLogger.info("Wrapper formatted completion payload successfully", {
124
+ sdkLogger.info("Wrapper formatteddddd completion payload successfully", {
133
125
  data: finalPayload
134
126
  });
135
127
  }
@@ -137,7 +129,7 @@ export function TemplateKYCExample({
137
129
  sdkLogger.warn("Wrapper received completion without a success result object", { status: data.status });
138
130
  }
139
131
 
140
- onComplete(finalPayload as any);
132
+ onComplete(finalPayload);
141
133
  };
142
134
 
143
135
  const handleCancel = () => {
@@ -169,8 +169,6 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
169
169
  onComplete={(data) => {
170
170
  sdkLogger.info("KYC Flow Completed", { referenceId: data?.result?.referenceId });
171
171
  sdkLogger.flush();
172
- logger.log('this is the data going to the TG side',data)
173
- console.log('this is the data going to the TG side',data)
174
172
  if (onComplete) onComplete(data);
175
173
  }}
176
174
  onError={(err) => {
@@ -1,4 +1,5 @@
1
1
  import React, { useCallback, useEffect, useRef } from 'react';
2
+ import type { CodeType } from 'react-native-vision-camera';
2
3
  import {
3
4
  MultiFormatReader,
4
5
  BarcodeFormat,
@@ -13,261 +14,209 @@ interface WebBarcodeScannerProps {
13
14
  videoRef: React.RefObject<HTMLVideoElement | null>;
14
15
  active: boolean;
15
16
  onBarcodeScanned: (value: string) => void;
17
+ formats?: CodeType[];
16
18
  }
17
19
 
20
+ interface RoiProfile {
21
+ width: number;
22
+ height: number;
23
+ contrast: number;
24
+ }
25
+
26
+ const ROI_PROFILES: RoiProfile[] = [
27
+ // Full frame catches barcodes before the user has centered them.
28
+ { width: 1, height: 1, contrast: 1 },
29
+ // Most ID barcodes sit in the wide center strip shown by the overlay.
30
+ { width: 0.98, height: 0.64, contrast: 1 },
31
+ // A tighter, contrast-enhanced pass helps very dense or low-contrast PDF417.
32
+ { width: 0.94, height: 0.46, contrast: 1.55 },
33
+ ];
34
+
35
+ const ROTATIONS = [180, 90, -90];
36
+ const DEFAULT_FORMATS: CodeType[] = ['pdf-417'];
37
+
38
+ const ZXING_FORMATS: Partial<Record<CodeType, BarcodeFormat>> = {
39
+ 'pdf-417': BarcodeFormat.PDF_417,
40
+ 'code-128': BarcodeFormat.CODE_128,
41
+ 'code-39': BarcodeFormat.CODE_39,
42
+ 'code-93': BarcodeFormat.CODE_93,
43
+ codabar: BarcodeFormat.CODABAR,
44
+ 'ean-13': BarcodeFormat.EAN_13,
45
+ 'ean-8': BarcodeFormat.EAN_8,
46
+ itf: BarcodeFormat.ITF,
47
+ 'itf-14': BarcodeFormat.ITF,
48
+ 'upc-a': BarcodeFormat.UPC_A,
49
+ 'upc-e': BarcodeFormat.UPC_E,
50
+ qr: BarcodeFormat.QR_CODE,
51
+ aztec: BarcodeFormat.AZTEC,
52
+ 'data-matrix': BarcodeFormat.DATA_MATRIX,
53
+ 'gs1-data-bar': BarcodeFormat.RSS_14,
54
+ 'gs1-data-bar-limited': BarcodeFormat.RSS_14,
55
+ 'gs1-data-bar-expanded': BarcodeFormat.RSS_EXPANDED,
56
+ };
57
+
18
58
  export const WebBarcodeScanner: React.FC<WebBarcodeScannerProps> = ({
19
59
  videoRef,
20
60
  active,
21
61
  onBarcodeScanned,
62
+ formats = DEFAULT_FORMATS,
22
63
  }) => {
23
64
  const readerRef = useRef<MultiFormatReader | null>(null);
24
-
25
65
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
26
-
27
- const lastValueRef = useRef<string | null>(null);
28
-
66
+ const rotationCanvasRef = useRef<HTMLCanvasElement | null>(null);
67
+ const lastValueRef = useRef<{ value: string | null; emittedAt: number }>({ value: null, emittedAt: 0 });
68
+ const attemptRef = useRef(0);
69
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
29
70
  const stoppedRef = useRef(false);
30
-
31
71
  const callbackRef = useRef(onBarcodeScanned);
32
72
 
33
73
  useEffect(() => {
34
74
  callbackRef.current = onBarcodeScanned;
35
75
  }, [onBarcodeScanned]);
36
76
 
37
- const decodeBitmap = useCallback(
38
- (canvas: HTMLCanvasElement) => {
39
- if (!readerRef.current) {
40
- return null;
77
+ const decodeBitmap = useCallback((canvas: HTMLCanvasElement) => {
78
+ if (!readerRef.current || canvas.width === 0 || canvas.height === 0) return null;
79
+
80
+ const source = new HTMLCanvasElementLuminanceSource(canvas);
81
+ const attempts = [
82
+ new BinaryBitmap(new HybridBinarizer(source)),
83
+ new BinaryBitmap(new GlobalHistogramBinarizer(source)),
84
+ ];
85
+
86
+ for (const bitmap of attempts) {
87
+ try {
88
+ const result = readerRef.current.decodeWithState(bitmap);
89
+ const text = result?.getText();
90
+ if (text) return text;
91
+ } catch {
92
+ // Try the next binarization strategy/rotation.
41
93
  }
94
+ }
42
95
 
43
- const source = new HTMLCanvasElementLuminanceSource(canvas);
44
-
45
- const attempts = [
46
- new BinaryBitmap(
47
- new HybridBinarizer(source)
48
- ),
49
- new BinaryBitmap(
50
- new GlobalHistogramBinarizer(source)
51
- ),
52
- ];
53
-
54
- for (const bitmap of attempts) {
55
- try {
56
- const result =
57
- readerRef.current.decodeWithState(bitmap);
58
-
59
- if (result?.getText()) {
60
- return result.getText();
61
- }
62
- } catch {
63
- // continue
64
- }
65
- }
66
-
67
- return null;
68
- },
69
- []
70
- );
71
-
72
- const drawROI = useCallback(
73
- (
74
- video: HTMLVideoElement,
75
- canvas: HTMLCanvasElement
76
- ) => {
77
- const ctx = canvas.getContext('2d', {
78
- willReadFrequently: true,
79
- });
80
-
81
- if (!ctx) {
82
- return false;
83
- }
84
-
85
- const roi = 0.8;
86
-
87
- const sw = Math.floor(
88
- video.videoWidth * roi
89
- );
90
-
91
- const sh = Math.floor(
92
- video.videoHeight * roi
93
- );
94
-
95
- const sx = Math.floor(
96
- (video.videoWidth - sw) / 2
97
- );
96
+ return null;
97
+ }, []);
98
+
99
+ const drawROI = useCallback((video: HTMLVideoElement, canvas: HTMLCanvasElement, profile: RoiProfile) => {
100
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
101
+ if (!ctx) return false;
102
+
103
+ const sourceWidth = Math.max(1, Math.floor(video.videoWidth * profile.width));
104
+ const sourceHeight = Math.max(1, Math.floor(video.videoHeight * profile.height));
105
+ const sourceX = Math.floor((video.videoWidth - sourceWidth) / 2);
106
+ const sourceY = Math.floor((video.videoHeight - sourceHeight) / 2);
107
+
108
+ // Nearest-neighbour enlargement gives ZXing more pixels per narrow module
109
+ // on sub-1080p browser streams without allocating an unbounded canvas.
110
+ const desiredScale = sourceWidth < 1600 ? Math.min(1.75, 1600 / sourceWidth) : 1;
111
+ const boundedScale = Math.min(desiredScale, 2800 / Math.max(sourceWidth, sourceHeight));
112
+ const outputWidth = Math.max(1, Math.floor(sourceWidth * boundedScale));
113
+ const outputHeight = Math.max(1, Math.floor(sourceHeight * boundedScale));
114
+
115
+ if (canvas.width !== outputWidth || canvas.height !== outputHeight) {
116
+ canvas.width = outputWidth;
117
+ canvas.height = outputHeight;
118
+ }
98
119
 
99
- const sy = Math.floor(
100
- (video.videoHeight - sh) / 2
101
- );
120
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
121
+ ctx.clearRect(0, 0, outputWidth, outputHeight);
122
+ ctx.imageSmoothingEnabled = false;
123
+ ctx.filter = profile.contrast === 1 ? 'none' : `grayscale(1) contrast(${profile.contrast})`;
124
+ ctx.drawImage(
125
+ video,
126
+ sourceX,
127
+ sourceY,
128
+ sourceWidth,
129
+ sourceHeight,
130
+ 0,
131
+ 0,
132
+ outputWidth,
133
+ outputHeight,
134
+ );
135
+ ctx.filter = 'none';
136
+ return true;
137
+ }, []);
102
138
 
103
- if (
104
- canvas.width !== sw ||
105
- canvas.height !== sh
106
- ) {
107
- canvas.width = sw;
108
- canvas.height = sh;
109
- }
139
+ const rotateCanvas = useCallback((sourceCanvas: HTMLCanvasElement, degrees: number) => {
140
+ if (!rotationCanvasRef.current) rotationCanvasRef.current = document.createElement('canvas');
110
141
 
111
- ctx.drawImage(
112
- video,
113
- sx,
114
- sy,
115
- sw,
116
- sh,
117
- 0,
118
- 0,
119
- sw,
120
- sh
121
- );
122
-
123
- return true;
124
- },
125
- []
126
- );
127
-
128
- // Add a persistent rotation canvas ref at the top
129
- const rotationCanvasRef = useRef<HTMLCanvasElement | null>(null);
130
-
131
- // Update rotateCanvas to re-use the persistent canvas
132
- const rotateCanvas = useCallback(
133
- (sourceCanvas: HTMLCanvasElement, degrees: number) => {
134
- if (!rotationCanvasRef.current) {
135
- rotationCanvasRef.current = document.createElement('canvas');
136
- }
137
142
  const rotated = rotationCanvasRef.current;
138
- const ctx = rotated.getContext('2d');
139
- if (!ctx) return null;
143
+ const swapDimensions = Math.abs(degrees) === 90;
144
+ rotated.width = swapDimensions ? sourceCanvas.height : sourceCanvas.width;
145
+ rotated.height = swapDimensions ? sourceCanvas.width : sourceCanvas.height;
140
146
 
141
- const radians = (degrees * Math.PI) / 180;
142
- if (degrees === 90 || degrees === -90) {
143
- rotated.width = sourceCanvas.height;
144
- rotated.height = sourceCanvas.width;
145
- } else {
146
- rotated.width = sourceCanvas.width;
147
- rotated.height = sourceCanvas.height;
148
- }
147
+ const ctx = rotated.getContext('2d', { willReadFrequently: true });
148
+ if (!ctx) return null;
149
149
 
150
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
150
151
  ctx.clearRect(0, 0, rotated.width, rotated.height);
151
152
  ctx.translate(rotated.width / 2, rotated.height / 2);
152
- ctx.rotate(radians);
153
+ ctx.rotate((degrees * Math.PI) / 180);
153
154
  ctx.drawImage(sourceCanvas, -sourceCanvas.width / 2, -sourceCanvas.height / 2);
154
155
  return rotated;
155
- },
156
- []
157
- );
158
-
159
-
156
+ }, []);
160
157
 
161
158
  const tryDecode = useCallback(() => {
162
159
  const video = videoRef.current;
163
-
164
160
  const canvas = canvasRef.current;
161
+ if (!video || !canvas || video.readyState < 2 || video.videoWidth === 0 || video.videoHeight === 0) return null;
165
162
 
166
- if (
167
- !video ||
168
- !canvas ||
169
- video.readyState < 2
170
- ) {
171
- return null;
172
- }
163
+ const attempt = attemptRef.current++;
164
+ const profile = ROI_PROFILES[attempt % ROI_PROFILES.length];
165
+ if (!drawROI(video, canvas, profile)) return null;
173
166
 
174
- const ok = drawROI(video, canvas);
167
+ const directValue = decodeBitmap(canvas);
168
+ if (directValue) return directValue;
175
169
 
176
- if (!ok) {
177
- return null;
178
- }
170
+ // Spread rotation work over successive frames to keep the UI responsive.
171
+ const rotation = ROTATIONS[attempt % ROTATIONS.length];
172
+ const rotated = rotateCanvas(canvas, rotation);
173
+ return rotated ? decodeBitmap(rotated) : null;
174
+ }, [decodeBitmap, drawROI, rotateCanvas, videoRef]);
179
175
 
180
- let value = decodeBitmap(canvas);
176
+ const tick = useCallback(() => {
177
+ if (stoppedRef.current) return;
181
178
 
179
+ const value = tryDecode();
182
180
  if (value) {
183
- return value;
184
- }
185
-
186
- const rotations = [90, -90, 180];
187
-
188
- for (const rotation of rotations) {
189
- const rotated = rotateCanvas(
190
- canvas,
191
- rotation
192
- );
193
-
194
- if (!rotated) {
195
- continue;
196
- }
197
-
198
- value = decodeBitmap(rotated);
199
-
200
- if (value) {
201
- return value;
181
+ const now = Date.now();
182
+ const previous = lastValueRef.current;
183
+ if (value !== previous.value || now - previous.emittedAt > 1500) {
184
+ lastValueRef.current = { value, emittedAt: now };
185
+ callbackRef.current(value);
202
186
  }
203
187
  }
204
188
 
205
- return null;
206
- }, [
207
- videoRef,
208
- drawROI,
209
- decodeBitmap,
210
- rotateCanvas,
211
- ]);
212
-
213
- const tick = useCallback(() => {
214
- if (stoppedRef.current) return;
215
-
216
- const value = tryDecode();
217
- if (value && value !== lastValueRef.current) {
218
- lastValueRef.current = value;
219
- callbackRef.current(value);
220
- // If you want it to stop scanning on success, leave 'return;'
221
- // but ensure your parent component handles unmounting the scanner!
222
- return;
223
- }
224
-
225
- setTimeout(tick, 100);
189
+ timerRef.current = setTimeout(tick, 180);
226
190
  }, [tryDecode]);
227
191
 
228
192
  useEffect(() => {
229
- if (!active) {
230
- return;
231
- }
193
+ if (!active) return;
232
194
 
233
195
  stoppedRef.current = false;
196
+ attemptRef.current = 0;
197
+ lastValueRef.current = { value: null, emittedAt: 0 };
198
+ if (!canvasRef.current) canvasRef.current = document.createElement('canvas');
234
199
 
235
- lastValueRef.current = null;
236
-
237
- if (!canvasRef.current) {
238
- canvasRef.current =
239
- document.createElement('canvas');
240
- }
241
-
200
+ const possibleFormats = Array.from(new Set(
201
+ formats.map(format => ZXING_FORMATS[format]).filter((format): format is BarcodeFormat => format !== undefined),
202
+ ));
242
203
  const hints = new Map();
204
+ hints.set(DecodeHintType.POSSIBLE_FORMATS, possibleFormats.length > 0 ? possibleFormats : [BarcodeFormat.PDF_417]);
205
+ hints.set(DecodeHintType.TRY_HARDER, true);
243
206
 
244
- hints.set(
245
- DecodeHintType.POSSIBLE_FORMATS,
246
- [BarcodeFormat.PDF_417]
247
- );
248
-
249
- hints.set(
250
- DecodeHintType.TRY_HARDER,
251
- true
252
- );
253
-
254
- const reader =
255
- new MultiFormatReader();
256
-
207
+ const reader = new MultiFormatReader();
257
208
  reader.setHints(hints);
258
-
259
209
  readerRef.current = reader;
260
-
261
210
  tick();
262
211
 
263
212
  return () => {
264
213
  stoppedRef.current = true;
265
-
214
+ if (timerRef.current) clearTimeout(timerRef.current);
215
+ timerRef.current = null;
266
216
  readerRef.current?.reset();
267
-
268
217
  readerRef.current = null;
269
218
  };
270
- }, [active, tick]);
219
+ }, [active, formats, tick]);
271
220
 
272
221
  return null;
273
- };
222
+ };
@@ -279,14 +279,14 @@ export class KYCService {
279
279
  }
280
280
  }
281
281
 
282
- async extractBarcode(params: { fileUri: string; token: string; docType: string; postfix?: string }, env: KycEnvironment = 'PRODUCTION'): Promise<any> {
282
+ async extractBarcode(params: { fileUri: string; token: string; docType: string; postfix?: string; timeoutMs?: number }, env: KycEnvironment = 'PRODUCTION'): Promise<any> {
283
283
  const formData = new FormData();
284
284
  await appendFileToFormData(formData, 'file', params.fileUri, `id_${params.postfix || 'back'}_photo.jpg`, 'image/jpeg');
285
285
 
286
286
  const url = `${this.barcodeServiceURL}/decode_barcode/?doc_type=${encodeURIComponent(params.docType)}`;
287
287
 
288
288
  try {
289
- return await this.crossPlatformMultipartUpload<any>(url, formData, params.token, 60000);
289
+ return await this.crossPlatformMultipartUpload<any>(url, formData, params.token, params.timeoutMs ?? 60000);
290
290
  } catch (e: any) {
291
291
  throw e;
292
292
  }
@@ -651,4 +651,4 @@ export const clearAuthToken = () => {
651
651
 
652
652
  export const errorMessage = (error: any) => {
653
653
  return error.response?.data?.message || error.response?.data?.detail || error.response?.data?.error || error.response?.data || error.message;
654
- }
654
+ }
@@ -34,6 +34,7 @@ export interface VerificationResult {
34
34
  session_id?: string;
35
35
  verification_id?: string;
36
36
  verification_status?: string;
37
+ document_type?: string;
37
38
  completion_timestamp?: string;
38
39
  user_data?: {
39
40
  personal_information?: {