@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.
Files changed (31) hide show
  1. package/README.md +1 -1
  2. package/build/package.json +3 -2
  3. package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
  4. package/build/src/components/EnhancedCameraView.js +19 -182
  5. package/build/src/components/EnhancedCameraView.js.map +1 -1
  6. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  7. package/build/src/components/KYCElements/IDCardCapture.js +189 -191
  8. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  9. package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
  10. package/build/src/components/KYCElements/PhoneVerificationTemplate.js +0 -2
  11. package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
  12. package/build/src/components/OverLay/IdCard.d.ts +6 -1
  13. package/build/src/components/OverLay/IdCard.d.ts.map +1 -1
  14. package/build/src/components/OverLay/IdCard.js +36 -34
  15. package/build/src/components/OverLay/IdCard.js.map +1 -1
  16. package/build/src/config/countriesData.d.ts.map +1 -1
  17. package/build/src/config/countriesData.js.map +1 -1
  18. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  19. package/build/src/modules/api/CardAuthentification.js +0 -1
  20. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  21. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  22. package/build/src/modules/api/KYCService.js +41 -24
  23. package/build/src/modules/api/KYCService.js.map +1 -1
  24. package/package.json +3 -2
  25. package/src/components/EnhancedCameraView.tsx +28 -219
  26. package/src/components/KYCElements/IDCardCapture.tsx +560 -581
  27. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +0 -2
  28. package/src/components/OverLay/IdCard.tsx +48 -36
  29. package/src/config/countriesData.ts +0 -4
  30. package/src/modules/api/CardAuthentification.ts +0 -1
  31. 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
- // Note: xMinPercent, yMinPercent, etc. are intentionally ignored below
34
- // to force a mathematically perfect ID card aspect ratio.
35
- }: IdCardOverlayProps) => {
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
- // Set width to 85% of the screen, and calculate perfect height
43
- const boxWidth = screenWidth * 0.85;
44
- const boxHeight = boxWidth / ID_ASPECT_RATIO;
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
- // Center horizontally
47
- const xMin = (screenWidth - boxWidth) / 2;
48
- const xMax = xMin + boxWidth;
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
- // Center vertically (shifted slightly up for better holding ergonomics)
51
- const yMin = (screenHeight - boxHeight) * 0.42;
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; // Reduced slightly for a tighter, more realistic card feel
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 style={StyleSheet.absoluteFill} pointerEvents="box-none">
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={screenWidth} height={screenHeight} style={styles.svg}>
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={screenWidth} height={screenHeight} fill="white" />
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={screenWidth} height={screenHeight} fill="rgba(0,0,0,0.85)" mask="url(#cameraMask)" />
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)" // Subtle inner frame
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={screenWidth} height={screenHeight} />
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 + 30 }]} pointerEvents="box-none">
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}>
@@ -1,9 +1,5 @@
1
1
  import { Country } from "../types/KYC.types";
2
2
 
3
-
4
-
5
-
6
-
7
3
  export const countryData: Record<string, Country> = {
8
4
  FR: { name: 'France', name_en: "France", flag: '🇫🇷' },
9
5
  CM: { name: 'Cameroun', name_en: "Cameroon", flag: '🇨🇲' },
@@ -249,7 +249,6 @@ export async function backVerification(
249
249
  points = cardData[0];
250
250
  isCardInFrame = true;
251
251
  hasCroppedSides = false;
252
- // 🚨 ARTIFICIAL CHECK REMOVED HERE
253
252
  }
254
253
 
255
254
  if (!isCardInFrame || hasCroppedSides) {
@@ -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
- // 1. Build the FormData ONLY for the file
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
- const response = await fetch(url, {
447
- method: 'POST',
448
- headers: {
449
- 'Authorization': `Bearer ${token}`,
450
- 'Accept': 'application/json'
451
- },
452
- body: formData, // Only the file goes in the body
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
- // const data = await response.json();
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}`);