@sanctum-key/react-native-sdk 1.0.18 → 1.0.19
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.
- package/README.md +1 -1
- package/build/package.json +3 -2
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +19 -182
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +189 -191
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js +0 -2
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
- package/build/src/components/OverLay/IdCard.d.ts +6 -1
- package/build/src/components/OverLay/IdCard.d.ts.map +1 -1
- package/build/src/components/OverLay/IdCard.js +36 -34
- package/build/src/components/OverLay/IdCard.js.map +1 -1
- package/build/src/config/countriesData.d.ts.map +1 -1
- package/build/src/config/countriesData.js.map +1 -1
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +0 -1
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +41 -24
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/package.json +3 -2
- package/src/components/EnhancedCameraView.tsx +28 -219
- package/src/components/KYCElements/IDCardCapture.tsx +560 -581
- package/src/components/KYCElements/PhoneVerificationTemplate.tsx +0 -2
- package/src/components/OverLay/IdCard.tsx +48 -36
- package/src/config/countriesData.ts +0 -4
- package/src/modules/api/CardAuthentification.ts +0 -1
- package/src/modules/api/KYCService.ts +48 -29
|
@@ -59,14 +59,12 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
59
59
|
const sendButtonText = t('common.sendCode') || 'Send Verification Code';
|
|
60
60
|
const buttonText = step === 'phone' ? sendButtonText : verifyButtonText;
|
|
61
61
|
|
|
62
|
-
// --- AUTO SUBMIT LOGIC ---
|
|
63
62
|
useEffect(() => {
|
|
64
63
|
if (otp.length === CODE_LENGTH && step === 'otp' && !isSimulating) {
|
|
65
64
|
handleVerifyCode();
|
|
66
65
|
}
|
|
67
66
|
}, [otp]);
|
|
68
67
|
|
|
69
|
-
// --- SAFE AUTO FOCUS LOGIC ---
|
|
70
68
|
useEffect(() => {
|
|
71
69
|
let focusTimer: ReturnType<typeof setTimeout>;
|
|
72
70
|
if (step === 'otp' && !isSimulating) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Animated,
|
|
4
|
-
Dimensions,
|
|
5
4
|
Platform,
|
|
6
5
|
SafeAreaView,
|
|
7
6
|
StatusBar,
|
|
8
7
|
StyleSheet,
|
|
9
8
|
Text,
|
|
10
9
|
View,
|
|
10
|
+
useWindowDimensions,
|
|
11
11
|
} from 'react-native';
|
|
12
12
|
import Svg, {
|
|
13
13
|
Defs,
|
|
@@ -22,44 +22,41 @@ import ScanningLine from '../Svgs/scanningLine';
|
|
|
22
22
|
import StepOverlay from './StepOverlay';
|
|
23
23
|
import { IdCardOverlayProps } from './type';
|
|
24
24
|
|
|
25
|
-
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
|
|
26
|
-
|
|
27
25
|
const IdCardOverlay = ({
|
|
28
26
|
cornerOpacity,
|
|
29
27
|
instructions,
|
|
30
28
|
stepperProps,
|
|
31
29
|
isSuccess,
|
|
32
30
|
language = 'en',
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
xMin: xMinPercent = 5,
|
|
32
|
+
xMax: xMaxPercent = 95,
|
|
33
|
+
yMin: yMinPercent = 34,
|
|
34
|
+
yMax: yMaxPercent = 66,
|
|
35
|
+
}: IdCardOverlayProps & { xMin?: number, xMax?: number, yMin?: number, yMax?: number }) => {
|
|
36
36
|
const AnimatedG = Animated.createAnimatedComponent(G);
|
|
37
|
-
|
|
38
|
-
// --- 🚨 GEOMETRY FIX: Perfect ID Card Ratio ---
|
|
39
|
-
// Standard ISO ID-1 ratio is 1.586 (85.6mm / 53.98mm)
|
|
40
|
-
const ID_ASPECT_RATIO = 1.586;
|
|
41
37
|
|
|
42
|
-
//
|
|
43
|
-
const
|
|
44
|
-
|
|
38
|
+
// 1. Get baseline window dimensions (fallback for Native mobile)
|
|
39
|
+
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
|
40
|
+
|
|
41
|
+
// 2. Track actual container dimensions (Crucial for Web Modals where window != container)
|
|
42
|
+
const [container, setContainer] = useState({ width: windowWidth, height: windowHeight });
|
|
45
43
|
|
|
46
|
-
//
|
|
47
|
-
const xMin = (
|
|
48
|
-
const xMax =
|
|
44
|
+
// 3. Convert parent percentages to exact pixels within the container
|
|
45
|
+
const xMin = (container.width * xMinPercent) / 100;
|
|
46
|
+
const xMax = (container.width * xMaxPercent) / 100;
|
|
47
|
+
const yMin = (container.height * yMinPercent) / 100;
|
|
48
|
+
const yMax = (container.height * yMaxPercent) / 100;
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const yMax = yMin + boxHeight;
|
|
50
|
+
const boxWidth = xMax - xMin;
|
|
51
|
+
const boxHeight = yMax - yMin;
|
|
53
52
|
|
|
54
53
|
// --- Clean, Professional Copy Formatting ---
|
|
55
54
|
const formatDocType = (type?: string) => {
|
|
56
55
|
if (!type) return 'document';
|
|
57
|
-
// e.g., "identity_card" -> "identity card"
|
|
58
56
|
return type.replace(/_/g, ' ').toLowerCase();
|
|
59
57
|
};
|
|
60
58
|
|
|
61
59
|
const getSideText = (side?: string) => {
|
|
62
|
-
// Using "recto/verso" for French as it is much more professional for documents
|
|
63
60
|
if (side === 'front') return language === 'en' ? 'front' : 'recto';
|
|
64
61
|
return language === 'en' ? 'back' : 'verso';
|
|
65
62
|
};
|
|
@@ -80,19 +77,34 @@ const IdCardOverlay = ({
|
|
|
80
77
|
: 'Pas de reflets • Coins visibles • Maintenez stable';
|
|
81
78
|
|
|
82
79
|
// --- SVG Constants ---
|
|
83
|
-
const CORNER_SIZE = 40;
|
|
80
|
+
const CORNER_SIZE = 40;
|
|
84
81
|
const STROKE_WIDTH = 4;
|
|
85
|
-
const BORDER_RADIUS = 16;
|
|
82
|
+
const BORDER_RADIUS = 16;
|
|
83
|
+
|
|
84
|
+
// Safety check to prevent SVG NaN errors during initial render
|
|
85
|
+
if (container.width === 0 || container.height === 0 || boxWidth <= 0) {
|
|
86
|
+
return <View style={StyleSheet.absoluteFill} />;
|
|
87
|
+
}
|
|
86
88
|
|
|
87
89
|
return (
|
|
88
|
-
<View
|
|
90
|
+
<View
|
|
91
|
+
style={StyleSheet.absoluteFill}
|
|
92
|
+
pointerEvents="box-none"
|
|
93
|
+
onLayout={(e) => {
|
|
94
|
+
// Update container dimensions only if they change to prevent loops
|
|
95
|
+
const { width, height } = e.nativeEvent.layout;
|
|
96
|
+
if (width !== container.width || height !== container.height) {
|
|
97
|
+
setContainer({ width, height });
|
|
98
|
+
}
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
89
101
|
<StatusBar translucent backgroundColor="transparent" barStyle="light-content" />
|
|
90
|
-
|
|
102
|
+
|
|
91
103
|
{/* SVG Overlay */}
|
|
92
|
-
<Svg width={
|
|
104
|
+
<Svg width={container.width} height={container.height} style={styles.svg}>
|
|
93
105
|
<Defs>
|
|
94
106
|
<Mask id="cameraMask">
|
|
95
|
-
<Rect x="0" y="0" width={
|
|
107
|
+
<Rect x="0" y="0" width={container.width} height={container.height} fill="white" />
|
|
96
108
|
{/* Transparent Cutout */}
|
|
97
109
|
<Rect
|
|
98
110
|
x={xMin}
|
|
@@ -109,10 +121,10 @@ const IdCardOverlay = ({
|
|
|
109
121
|
<Stop offset="100%" stopColor="#22C55E" />
|
|
110
122
|
</LinearGradient>
|
|
111
123
|
</Defs>
|
|
112
|
-
|
|
124
|
+
|
|
113
125
|
{/* Dim background */}
|
|
114
|
-
<Rect x="0" y="0" width={
|
|
115
|
-
|
|
126
|
+
<Rect x="0" y="0" width={container.width} height={container.height} fill="rgba(0,0,0,0.85)" mask="url(#cameraMask)" />
|
|
127
|
+
|
|
116
128
|
{/* Main Card Border */}
|
|
117
129
|
<Rect
|
|
118
130
|
x={xMin}
|
|
@@ -121,11 +133,11 @@ const IdCardOverlay = ({
|
|
|
121
133
|
height={boxHeight}
|
|
122
134
|
rx={BORDER_RADIUS}
|
|
123
135
|
ry={BORDER_RADIUS}
|
|
124
|
-
stroke="rgba(255,255,255,0.25)"
|
|
125
|
-
strokeWidth={1.5}
|
|
136
|
+
stroke={isSuccess ? '#2DBD60' : "rgba(255,255,255,0.25)"}
|
|
137
|
+
strokeWidth={isSuccess ? 3 : 1.5}
|
|
126
138
|
fill="transparent"
|
|
127
139
|
/>
|
|
128
|
-
|
|
140
|
+
|
|
129
141
|
{/* Professional Corner Guides */}
|
|
130
142
|
<AnimatedG opacity={cornerOpacity || 1}>
|
|
131
143
|
{/* Top Left */}
|
|
@@ -165,7 +177,7 @@ const IdCardOverlay = ({
|
|
|
165
177
|
|
|
166
178
|
{/* Scanning animation */}
|
|
167
179
|
{!isSuccess && (
|
|
168
|
-
<ScanningLine yMin={yMin} yMax={yMax} screenWidth={
|
|
180
|
+
<ScanningLine yMin={yMin} yMax={yMax} screenWidth={container.width} height={container.height} />
|
|
169
181
|
)}
|
|
170
182
|
|
|
171
183
|
{/* Top UI Zone */}
|
|
@@ -187,7 +199,7 @@ const IdCardOverlay = ({
|
|
|
187
199
|
</View>
|
|
188
200
|
|
|
189
201
|
{/* Bottom UI Zone */}
|
|
190
|
-
<View style={[styles.bottomZone, { top: yMax +
|
|
202
|
+
<View style={[styles.bottomZone, { top: yMax + 20 }]} pointerEvents="box-none">
|
|
191
203
|
<Text style={styles.bottomTitle}>{bottomTitle}</Text>
|
|
192
204
|
<Text style={styles.bottomSubtitle}>{bottomSubtitle}</Text>
|
|
193
205
|
<View style={styles.helperPill}>
|
|
@@ -296,7 +296,6 @@ export class KYCService {
|
|
|
296
296
|
const { fileUri, docType, docRegion, token, postfix } = params;
|
|
297
297
|
const formData = new FormData();
|
|
298
298
|
|
|
299
|
-
// ✅ FIX: Dynamically assign the postfix to the filename
|
|
300
299
|
await appendFileToFormData(formData, 'file', fileUri, `id_card_${postfix}.jpg`, 'image/jpeg');
|
|
301
300
|
|
|
302
301
|
const docTypeShorted = GovernmentDocumentTypeShorted[docType as GovernmentDocumentType];
|
|
@@ -412,24 +411,14 @@ export class KYCService {
|
|
|
412
411
|
card_obb: { x: 50, y: 50, width: 200, height: 200 }
|
|
413
412
|
};
|
|
414
413
|
}
|
|
415
|
-
|
|
416
414
|
const { fileUri, docType, docRegion, postfix = 'back', token, template_path, mrz_type } = params;
|
|
417
415
|
|
|
418
|
-
//
|
|
419
|
-
const formData = new FormData();
|
|
420
|
-
const filePayload = {
|
|
421
|
-
uri: fileUri,
|
|
422
|
-
type: 'image/jpeg',
|
|
423
|
-
name: `id_card_${postfix}.jpg`,
|
|
424
|
-
};
|
|
425
|
-
formData.append('file', filePayload as any);
|
|
426
|
-
|
|
416
|
+
// ... top of extractMrzText (params extraction, etc) ...
|
|
427
417
|
const docTypeShorted = GovernmentDocumentTypeShorted[docType as GovernmentDocumentType] || docType;
|
|
428
418
|
const safeMrzType = mrz_type && mrz_type.trim() !== '' ? mrz_type : 'TD1';
|
|
429
419
|
|
|
430
420
|
logger.log("docTypeShorted", docTypeShorted, docRegion, postfix);
|
|
431
421
|
|
|
432
|
-
// 🚨 THE FIX: Pass all required text parameters in the URL query string for FastAPI
|
|
433
422
|
const queryParams = new URLSearchParams({
|
|
434
423
|
doc_type: docTypeShorted,
|
|
435
424
|
doc_region: docRegion,
|
|
@@ -443,14 +432,52 @@ export class KYCService {
|
|
|
443
432
|
logger.log("url", url);
|
|
444
433
|
|
|
445
434
|
try {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
435
|
+
let response;
|
|
436
|
+
|
|
437
|
+
if (Platform.OS === 'web') {
|
|
438
|
+
// 🚨 THE FIX: Completely bypass React Native Web's networking polyfills.
|
|
439
|
+
// We explicitly grab the browser's native APIs so the bundler cannot interfere.
|
|
440
|
+
const nativeFetch = window.fetch;
|
|
441
|
+
const NativeFormData = window.FormData;
|
|
442
|
+
|
|
443
|
+
const formData = new NativeFormData();
|
|
444
|
+
|
|
445
|
+
// Fetch the Blob natively
|
|
446
|
+
const fileReq = await nativeFetch(fileUri);
|
|
447
|
+
const blob = await fileReq.blob();
|
|
448
|
+
|
|
449
|
+
// Append the Blob. The native browser will perfectly handle the multipart boundary.
|
|
450
|
+
formData.append('file', blob, `id_card_${postfix}.jpg`);
|
|
451
|
+
|
|
452
|
+
// Execute using Native Fetch!
|
|
453
|
+
response = await nativeFetch(url, {
|
|
454
|
+
method: 'POST',
|
|
455
|
+
headers: {
|
|
456
|
+
'Authorization': `Bearer ${token}`,
|
|
457
|
+
'Accept': 'application/json'
|
|
458
|
+
},
|
|
459
|
+
body: formData,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
} else {
|
|
463
|
+
// 📱 MOBILE NATIVE: React Native's Native Bridge expects this exact object
|
|
464
|
+
const formData = new FormData();
|
|
465
|
+
const filePayload = {
|
|
466
|
+
uri: fileUri,
|
|
467
|
+
type: 'image/jpeg',
|
|
468
|
+
name: `id_card_${postfix}.jpg`,
|
|
469
|
+
};
|
|
470
|
+
formData.append('file', filePayload as any);
|
|
471
|
+
|
|
472
|
+
response = await fetch(url, {
|
|
473
|
+
method: 'POST',
|
|
474
|
+
headers: {
|
|
475
|
+
'Authorization': `Bearer ${token}`,
|
|
476
|
+
'Accept': 'application/json'
|
|
477
|
+
},
|
|
478
|
+
body: formData,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
454
481
|
|
|
455
482
|
if (!response.ok) {
|
|
456
483
|
const errorText = await response.text();
|
|
@@ -458,19 +485,11 @@ export class KYCService {
|
|
|
458
485
|
throw new Error(`Erreur serveur: ${response.status}`);
|
|
459
486
|
}
|
|
460
487
|
|
|
461
|
-
|
|
462
|
-
// logger.log('extractMrzText res', JSON.stringify(data, null, 2));
|
|
463
|
-
|
|
464
|
-
// if (Object.keys(data).length === 0) throw new Error('No data found');
|
|
465
|
-
// if (data?.success === false) throw new Error(data.parsed_data?.status || 'Échec de l\'extraction MRZ');
|
|
466
|
-
|
|
467
|
-
// return data;
|
|
468
|
-
const data = await response.json();
|
|
488
|
+
const data = await response.json();
|
|
469
489
|
logger.log('extractMrzText res', JSON.stringify(data, null, 2));
|
|
470
490
|
|
|
471
491
|
if (Object.keys(data).length === 0) throw new Error('No data found');
|
|
472
492
|
|
|
473
|
-
// 🚨 UPDATE THIS LINE to grab the actual status_message:
|
|
474
493
|
if (data?.success === false) {
|
|
475
494
|
const serverMessage = data.parsed_data?.status_message || data.parsed_data?.status || 'Échec de l\'extraction MRZ';
|
|
476
495
|
throw new Error(`Lecture MRZ refusée: ${serverMessage}`);
|