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

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 +3 -1
  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 +3 -1
  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,4 +1,4 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
2
  import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
3
3
  import { TemplateComponent } from '../../types/KYC.types';
4
4
  import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
@@ -20,85 +20,139 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
20
20
  const [retryCount, setRetryCount] = useState(0);
21
21
  const [isRetrying, setIsRetrying] = useState(false);
22
22
  const [timeoutId, setTimeoutId] = useState<number | null>(null);
23
+ const isPollingRef = useRef(false);
23
24
 
24
25
  const verification = state.verification;
25
26
  const isSuccess = verification.status === 'success';
26
27
  const isFailed = verification.status === 'failed';
27
28
 
28
- const getVerificationResult = async (currentRetryCount = 0, maxRetries = 40) => {
29
+ const getVerificationResult = async (currentRetryCount = 0, maxRetries = 60) => {
29
30
  try {
30
31
  logger.log(`Starting verification check (attempt ${currentRetryCount + 1}/${maxRetries + 1})`);
31
32
  const result: any = await kycService.getVerificationResult(state.session.session_id);
32
33
 
33
34
  logger.log('getVerificationResult raw result:', JSON.stringify(result, null, 2));
34
-
35
- if (result?.status === 'Failed.' || result?.status === 'failed' || result?.status === 'rejected') {
35
+
36
+ const rootStatus = (result?.status || '').toLowerCase();
37
+ if (rootStatus === 'failed.' || rootStatus === 'failed' || rootStatus === 'rejected' || rootStatus === 'error') {
36
38
  actions.setVerificationState({
37
39
  status: 'failed',
38
40
  result: {
39
41
  referenceId: state.session.session_id,
40
- issues: [result?.message || 'Verification failed. Sent for manual review.']
42
+ issues: [result?.message || result?.reason || 'Verification failed. Sent for manual review.']
41
43
  } as any
42
44
  });
43
45
  setIsRetrying(false);
44
- return;
46
+ isPollingRef.current = false; // Release lock
47
+ return;
45
48
  }
46
-
47
- let verificationResult = result?.data;
48
-
49
- if (verificationResult && !verificationResult.verification_status && verificationResult[state.session.session_id]) {
50
- const nestedData = verificationResult[state.session.session_id]?.data;
51
- verificationResult = typeof nestedData === 'string' ? JSON.parse(nestedData) : nestedData;
49
+
50
+ // 2. Safely Extract Nested Payload
51
+ let vData = result?.data || result; // Fallback to root if .data is missing
52
+
53
+ // Handle stringified JSON
54
+ if (typeof vData === 'string') {
55
+ try {
56
+ vData = JSON.parse(vData);
57
+ } catch (e) { }
52
58
  }
53
-
54
- if (!verificationResult) {
55
- throw new Error('Verification payload missing for current session');
59
+
60
+ // Handle backend dictionaries indexed by session_id
61
+ if (vData && typeof vData === 'object' && vData[state.session.session_id]) {
62
+ let nested = vData[state.session.session_id];
63
+ if (nested && nested.data) {
64
+ nested = typeof nested.data === 'string' ? JSON.parse(nested.data) : nested.data;
65
+ }
66
+ vData = nested;
67
+ }
68
+
69
+ if (!vData || typeof vData !== 'object') {
70
+ throw new Error('Verification payload missing or invalid for current session');
71
+ }
72
+
73
+ const vStatus = (vData.verification_status || vData.status || vData.state || '').toLowerCase();
74
+ const vDecision = (vData.decision || vData.outcome || vData.result || '').toLowerCase();
75
+ // const processingError = vData.reason || vData.error || vData.issues || vData.error_message;
76
+ const selfieMatch = vData.selfie_info?.is_match; // true, false, or null
77
+
78
+ logger.log(`Parsed Status: [${vStatus}], Decision: [${vDecision}], Selfie Match: [${selfieMatch}]`);
79
+
80
+ // 4. Evaluate Failures First
81
+ // 5. Evaluate Definitive Success
82
+ if (
83
+ vStatus === 'approved' || vStatus === 'verified' || vStatus === 'success' ||
84
+ vDecision === 'approved' || vDecision === 'passed' || vDecision === 'clear' ||
85
+ (vStatus === 'completed' && selfieMatch === true)
86
+ ) {
87
+
88
+ // 🚨 THE FIX: Prevent False Positives (Ghost Payloads)
89
+ // If it says success, but the data object is completely empty, it is actually a failure.
90
+ const hasActualData = vData && Object.keys(vData).length > 0;
91
+
92
+ if (!hasActualData) {
93
+ logger.warn('Backend returned success, but data payload is entirely empty.');
94
+ actions.setVerificationState({
95
+ status: 'failed',
96
+ result: {
97
+ referenceId: state.session.session_id,
98
+ issues: ['Document text could not be read clearly. Please retake the photo in better lighting.']
99
+ } as any
100
+ });
101
+ setIsRetrying(false);
102
+ isPollingRef.current = false;
103
+ return;
56
104
  }
57
105
 
58
- const currentStatus = verificationResult.verification_status;
59
-
60
- if (currentStatus === null || currentStatus === "processing" || currentStatus === "pending") {
106
+ // If we have data, proceed to actual success
107
+ actions.setVerificationState({
108
+ status: 'success',
109
+ result: {
110
+ ...result,
111
+ referenceId: vData.verification_id || vData.session_id || state.session.session_id
112
+ } as any
113
+ });
114
+ setIsRetrying(false);
115
+ isPollingRef.current = false;
116
+ return;
117
+ }
118
+
119
+ if (
120
+ vStatus === 'processing' || vStatus === 'pending' || vStatus === 'in_progress' ||
121
+ vStatus === 'initiated' || (vStatus === 'completed' && selfieMatch === null) || !vStatus
122
+ ) {
61
123
  if (currentRetryCount < maxRetries) {
62
124
  const nextRetryCount = currentRetryCount + 1;
63
125
  setRetryCount(nextRetryCount);
64
126
  setIsRetrying(true);
65
- logger.log(`Verification still processing, retrying in 10s... (${nextRetryCount}/${maxRetries})`);
66
127
 
67
- if (timeoutId) clearTimeout(timeoutId);
128
+ logger.log(`Verification still processing, retrying in 3s... (${nextRetryCount}/${maxRetries})`);
68
129
 
130
+ if (timeoutId) clearTimeout(timeoutId);
69
131
  const newTimeoutId = setTimeout(() => {
70
132
  getVerificationResult(nextRetryCount, maxRetries);
71
- }, 10000);
133
+ }, 3000);
72
134
 
73
135
  setTimeoutId(newTimeoutId as any);
74
- return;
75
136
  } else {
76
137
  logger.error('Max retries reached, verification still processing');
77
138
  setIsRetrying(false);
78
- return;
139
+ isPollingRef.current = false;
140
+ actions.setVerificationState({
141
+ status: 'failed',
142
+ result: {
143
+ referenceId: state.session.session_id,
144
+ issues: ['Verification timed out. Please try again.']
145
+ } as any
146
+ });
79
147
  }
148
+ return;
80
149
  }
81
-
82
- if (currentStatus === "completed" || currentStatus === "approved") {
83
- actions.setVerificationState({
84
- status: 'success',
85
- result: {
86
- ...result,
87
- referenceId: verificationResult.verification_id || verificationResult.session_id
88
- } as any
89
- });
90
- } else if (currentStatus === "failed" || currentStatus === "rejected") {
91
- actions.setVerificationState({
92
- status: 'failed',
93
- result: {
94
- referenceId: verificationResult.verification_id || state.session.session_id,
95
- issues: ['Verification failed during processing.']
96
- } as any
97
- });
98
- }
99
-
150
+
151
+ // Fallback release if status is completely unknown
152
+ logger.warn(`Unknown verification status received: ${vStatus}`);
100
153
  setIsRetrying(false);
101
-
154
+ isPollingRef.current = false;
155
+
102
156
  } catch (error: any) {
103
157
  logger.error('getVerificationResult error', JSON.stringify(errorMessage(error) || { message: error?.message || 'Unknown error' }, null, 2));
104
158
 
@@ -106,47 +160,45 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
106
160
  const nextRetryCount = currentRetryCount + 1;
107
161
  setRetryCount(nextRetryCount);
108
162
  setIsRetrying(true);
109
- logger.log(`getVerificationResult Error occurred, retrying in 10s... (${nextRetryCount}/${maxRetries})`);
110
163
 
111
- if (timeoutId) clearTimeout(timeoutId);
164
+ logger.log(`getVerificationResult Error occurred, retrying in 3s... (${nextRetryCount}/${maxRetries})`);
112
165
 
166
+ if (timeoutId) clearTimeout(timeoutId);
113
167
  const newTimeoutId = setTimeout(() => {
114
168
  getVerificationResult(nextRetryCount, maxRetries);
115
- }, 10000);
169
+ }, 3000);
116
170
 
117
171
  setTimeoutId(newTimeoutId as any);
118
172
  } else {
119
173
  setIsRetrying(false);
174
+ isPollingRef.current = false; // Release lock
120
175
  logger.error('Max retries reached after errors');
121
176
  }
122
177
  }
123
178
  };
124
179
 
125
180
  useEffect(() => {
126
- if (state.session.session_id) {
181
+ if (state.session.session_id && !isSuccess && !isFailed && !isPollingRef.current) {
182
+ isPollingRef.current = true; // Lock the gate instantly
127
183
  getVerificationResult();
128
184
  }
129
- }, [state.session.session_id]);
185
+ }, [state.session.session_id, isSuccess, isFailed]);
130
186
 
131
187
  useEffect(() => {
132
188
  return () => {
133
189
  if (timeoutId) clearTimeout(timeoutId);
190
+ isPollingRef.current = false; // Reset lock on unmount
134
191
  };
135
192
  }, [timeoutId]);
136
193
 
137
- // 🚨 THE FIX: Automatically trigger the webhook/completion flow on success
138
194
  useEffect(() => {
139
195
  let autoSubmitTimer: ReturnType<typeof setTimeout>;
140
-
141
196
  if (isSuccess) {
142
197
  logger.log("Verification successful. Initiating auto-submit countdown...");
143
- // Wait 3 seconds so the user can see the "Complete" screen, then automatically push the data
144
198
  autoSubmitTimer = setTimeout(() => {
145
199
  actions.submitVerification();
146
200
  }, 3000);
147
201
  }
148
-
149
- // Cleanup the timer if the user clicks the button manually before 3 seconds are up
150
202
  return () => {
151
203
  if (autoSubmitTimer) clearTimeout(autoSubmitTimer);
152
204
  };
@@ -171,26 +223,22 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
171
223
  return (
172
224
  <View style={styles.container}>
173
225
  <Text style={styles.title}>{t('kyc.verificationProgress.status.failed')}</Text>
174
-
175
226
  <View style={styles.issuesBox}>
176
227
  <Text style={styles.issuesTitle}>{t('common.error')}</Text>
177
228
  {Array.isArray(verification.result?.issues) && verification.result.issues.map((issue: string, idx: number) => (
178
229
  <Text key={idx} style={styles.issueItem}>• {issue}</Text>
179
230
  ))}
180
231
  </View>
181
-
182
232
  <TouchableOpacity style={[styles.button, styles.primary]} onPress={() => actions.submitVerification()}>
183
233
  <Text style={styles.buttonText}>
184
234
  {state.currentLanguage === 'en' ? 'OK / Finish' : 'Terminer'}
185
235
  </Text>
186
236
  </TouchableOpacity>
187
-
188
237
  <TouchableOpacity style={[styles.button, styles.secondary]} onPress={() => actions.resetTemplate()}>
189
238
  <Text style={styles.secondaryText}>
190
239
  {state.currentLanguage === 'en' ? 'Start Over' : 'Recommencer'}
191
240
  </Text>
192
241
  </TouchableOpacity>
193
-
194
242
  {verification.result?.referenceId ? (
195
243
  <Text style={styles.refText}>{`Reference ID: ${verification.result.referenceId}`}</Text>
196
244
  ) : null}
@@ -202,11 +250,9 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
202
250
  <View style={styles.container}>
203
251
  <Text style={styles.title}>{t('kyc.verificationProgress.title')}</Text>
204
252
  <Text style={styles.subtitle}>
205
- {isRetrying
206
- ? `${t('kyc.verificationProgress.retrying', { current: retryCount, max: 40 })}`
207
- : t('kyc.verificationProgress.estimatedTime')
208
- }
253
+ {isRetrying ? `${t('kyc.verificationProgress.retrying', { current: retryCount, max: 40 })}` : t('kyc.verificationProgress.estimatedTime')}
209
254
  </Text>
255
+
210
256
  <View style={styles.timeline}>
211
257
  <View style={[styles.dot, styles.done]}><Text style={styles.stepCheckmark}>✓</Text></View>
212
258
  <View style={styles.line} />
@@ -214,22 +260,23 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
214
260
  <View style={styles.line} />
215
261
  <View style={[styles.dot, styles.pending]}><Text style={styles.stepCheckmark}>3</Text></View>
216
262
  </View>
263
+
217
264
  <View style={styles.row}>
218
265
  <Text style={styles.stepLabel}>{t('kyc.verificationProgress.status.completed')}</Text>
219
266
  <Text style={styles.stepLabel}>{t('kyc.verificationProgress.steps.analyzing')}</Text>
220
267
  <Text style={styles.stepLabel}>{t('kyc.verificationProgress.steps.verifying')}</Text>
221
268
  </View>
269
+
222
270
  <View style={styles.card}>
223
271
  <Text style={styles.cardTitle}>{t('kyc.idCardCapture.title')}</Text>
224
272
  <Text style={styles.cardLine}>{t('kyc.verificationProgress.steps.extracting')}</Text>
273
+
225
274
  <Text style={styles.cardTitle}>{t('kyc.selfieCapture.title')}</Text>
226
275
  <Text style={styles.cardLine}>{t('kyc.verificationProgress.steps.analyzing')}</Text>
276
+
227
277
  <Text style={styles.cardTitle}>{t('kyc.verificationProgress.title')}</Text>
228
278
  <Text style={styles.cardLine}>
229
- {isRetrying
230
- ? `${t('kyc.verificationProgress.status.processing')}...`
231
- : t('kyc.verificationProgress.status.pending')
232
- }
279
+ {isRetrying ? `${t('kyc.verificationProgress.status.processing')}...` : t('kyc.verificationProgress.status.pending')}
233
280
  </Text>
234
281
  </View>
235
282
  </View>
@@ -55,7 +55,7 @@ export interface EnhancedCameraViewProps {
55
55
  cameraType?: 'front' | 'back';
56
56
  style?: ViewStyle;
57
57
  onCapture?: (result: { success: boolean; path?: string; error?: string }) => void;
58
- onSilentCapture?: (result: { success: boolean; path?: string; error?: string }) => void;
58
+ onSilentCapture?: (result: { success: boolean; path?: string; error?: string; barcode:unknown }) => void;
59
59
  silentCaptureResult?: ISilentCaptureResult;
60
60
  onError?: (event: { message: string }) => void;
61
61
  onClose?: () => void;
@@ -0,0 +1,273 @@
1
+ import React, { useCallback, useEffect, useRef } from 'react';
2
+ import {
3
+ MultiFormatReader,
4
+ BarcodeFormat,
5
+ DecodeHintType,
6
+ HTMLCanvasElementLuminanceSource,
7
+ BinaryBitmap,
8
+ HybridBinarizer,
9
+ GlobalHistogramBinarizer,
10
+ } from '@zxing/library';
11
+
12
+ interface WebBarcodeScannerProps {
13
+ videoRef: React.RefObject<HTMLVideoElement | null>;
14
+ active: boolean;
15
+ onBarcodeScanned: (value: string) => void;
16
+ }
17
+
18
+ export const WebBarcodeScanner: React.FC<WebBarcodeScannerProps> = ({
19
+ videoRef,
20
+ active,
21
+ onBarcodeScanned,
22
+ }) => {
23
+ const readerRef = useRef<MultiFormatReader | null>(null);
24
+
25
+ const canvasRef = useRef<HTMLCanvasElement | null>(null);
26
+
27
+ const lastValueRef = useRef<string | null>(null);
28
+
29
+ const stoppedRef = useRef(false);
30
+
31
+ const callbackRef = useRef(onBarcodeScanned);
32
+
33
+ useEffect(() => {
34
+ callbackRef.current = onBarcodeScanned;
35
+ }, [onBarcodeScanned]);
36
+
37
+ const decodeBitmap = useCallback(
38
+ (canvas: HTMLCanvasElement) => {
39
+ if (!readerRef.current) {
40
+ return null;
41
+ }
42
+
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
+ );
98
+
99
+ const sy = Math.floor(
100
+ (video.videoHeight - sh) / 2
101
+ );
102
+
103
+ if (
104
+ canvas.width !== sw ||
105
+ canvas.height !== sh
106
+ ) {
107
+ canvas.width = sw;
108
+ canvas.height = sh;
109
+ }
110
+
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
+ const rotated = rotationCanvasRef.current;
138
+ const ctx = rotated.getContext('2d');
139
+ if (!ctx) return null;
140
+
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
+ }
149
+
150
+ ctx.clearRect(0, 0, rotated.width, rotated.height);
151
+ ctx.translate(rotated.width / 2, rotated.height / 2);
152
+ ctx.rotate(radians);
153
+ ctx.drawImage(sourceCanvas, -sourceCanvas.width / 2, -sourceCanvas.height / 2);
154
+ return rotated;
155
+ },
156
+ []
157
+ );
158
+
159
+
160
+
161
+ const tryDecode = useCallback(() => {
162
+ const video = videoRef.current;
163
+
164
+ const canvas = canvasRef.current;
165
+
166
+ if (
167
+ !video ||
168
+ !canvas ||
169
+ video.readyState < 2
170
+ ) {
171
+ return null;
172
+ }
173
+
174
+ const ok = drawROI(video, canvas);
175
+
176
+ if (!ok) {
177
+ return null;
178
+ }
179
+
180
+ let value = decodeBitmap(canvas);
181
+
182
+ 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;
202
+ }
203
+ }
204
+
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);
226
+ }, [tryDecode]);
227
+
228
+ useEffect(() => {
229
+ if (!active) {
230
+ return;
231
+ }
232
+
233
+ stoppedRef.current = false;
234
+
235
+ lastValueRef.current = null;
236
+
237
+ if (!canvasRef.current) {
238
+ canvasRef.current =
239
+ document.createElement('canvas');
240
+ }
241
+
242
+ const hints = new Map();
243
+
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
+
257
+ reader.setHints(hints);
258
+
259
+ readerRef.current = reader;
260
+
261
+ tick();
262
+
263
+ return () => {
264
+ stoppedRef.current = true;
265
+
266
+ readerRef.current?.reset();
267
+
268
+ readerRef.current = null;
269
+ };
270
+ }, [active, tick]);
271
+
272
+ return null;
273
+ };
@@ -56,7 +56,7 @@ export const basicKYCConfig: KYCFlowConfig = {
56
56
  validation: { required: true },
57
57
  cameraConfig: {
58
58
  aspectRatio: 4/3,
59
- quality: 0.8,
59
+ quality: 1,
60
60
  flashMode: 'auto',
61
61
  cameraType: 'back',
62
62
  allowRetake: true,
@@ -78,7 +78,7 @@ export const basicKYCConfig: KYCFlowConfig = {
78
78
  validation: { required: true },
79
79
  cameraConfig: {
80
80
  aspectRatio: 4/3,
81
- quality: 0.8,
81
+ quality: 1,
82
82
  flashMode: 'auto',
83
83
  cameraType: 'back',
84
84
  allowRetake: true,
@@ -275,20 +275,6 @@
275
275
  "py_file": "ontario_front.py",
276
276
  "keys_found": null
277
277
  }
278
- ],
279
- "Quebec": [
280
- {
281
- "py_file": "quebec_01_back.py",
282
- "back": "2D_barcode"
283
- },
284
- {
285
- "py_file": "quebec_01_front.py",
286
- "keys_found": null
287
- },
288
- {
289
- "py_file": "quebec_02_front.py",
290
- "front": "2D_barcode"
291
- }
292
278
  ]
293
279
  },
294
280
  "identity_card": {