@sanctum-key/react-native-sdk 1.0.16 → 1.0.18
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/build/package.json +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +113 -120
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +13 -63
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/utils/cropByObb.d.ts +0 -7
- package/build/src/utils/cropByObb.d.ts.map +1 -1
- package/build/src/utils/cropByObb.js +0 -8
- package/build/src/utils/cropByObb.js.map +1 -1
- package/package.json +1 -1
- package/src/components/KYCElements/IDCardCapture.tsx +566 -574
- package/src/modules/api/CardAuthentification.ts +21 -89
- package/src/utils/cropByObb.ts +1 -8
|
@@ -22,50 +22,6 @@ interface ApiVerificationResponse {
|
|
|
22
22
|
[key: string]: any;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function getDistance(p1: number[], p2: number[]): number {
|
|
26
|
-
return Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getPolygonArea(points: number[][]): number {
|
|
30
|
-
const n = points.length;
|
|
31
|
-
let area = 0;
|
|
32
|
-
for (let i = 0; i < n; i++) {
|
|
33
|
-
const j = (i + 1) % n;
|
|
34
|
-
area += points[i][0] * points[j][1];
|
|
35
|
-
area -= points[j][0] * points[i][1];
|
|
36
|
-
}
|
|
37
|
-
return Math.abs(area) / 2;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function assertCardNotTooFar(points: number[][]): void {
|
|
41
|
-
if (!points || points.length < 3) return;
|
|
42
|
-
|
|
43
|
-
const longestCardEdge = Math.max(
|
|
44
|
-
getDistance(points[0], points[1]),
|
|
45
|
-
getDistance(points[1], points[2]),
|
|
46
|
-
getDistance(points[2], points[3] ?? points[0]),
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const cardArea = getPolygonArea(points);
|
|
50
|
-
|
|
51
|
-
logger.log(
|
|
52
|
-
`Card distance check – longest edge: ${Math.round(longestCardEdge)} px, ` +
|
|
53
|
-
`OBB area: ${Math.round(cardArea)} px²`
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
if (longestCardEdge < 700) {
|
|
57
|
-
logger.log(`🛑 Rejected: longest edge too short (${Math.round(longestCardEdge)} px < 700 px)`);
|
|
58
|
-
throw new Error('TOO_FAR_AWAY');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (cardArea < 400_000) {
|
|
62
|
-
logger.log(`🛑 Rejected: card area too small (${Math.round(cardArea)} px² < 400 000 px²)`);
|
|
63
|
-
throw new Error('TOO_FAR_AWAY');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ─── frontVerification ────────────────────────────────────────────────────────
|
|
68
|
-
|
|
69
25
|
export async function frontVerification(
|
|
70
26
|
result: {
|
|
71
27
|
path?: string,
|
|
@@ -82,8 +38,8 @@ export async function frontVerification(
|
|
|
82
38
|
console.log("Front verification START", JSON.stringify({ result }, null, 2));
|
|
83
39
|
logger.log("Front verification", JSON.stringify({ result }, null, 2));
|
|
84
40
|
|
|
85
|
-
const authMethods = Array.isArray(result.regionMapping?.authMethod)
|
|
86
|
-
? result.regionMapping.authMethod.map(m => String(m).toUpperCase())
|
|
41
|
+
const authMethods = Array.isArray(result.regionMapping?.authMethod)
|
|
42
|
+
? result.regionMapping.authMethod.map(m => String(m).toUpperCase())
|
|
87
43
|
: [];
|
|
88
44
|
const hasMrz = authMethods.some(m => m.includes('MRZ'));
|
|
89
45
|
|
|
@@ -130,6 +86,7 @@ export async function frontVerification(
|
|
|
130
86
|
let points: number[][] | null = null;
|
|
131
87
|
let isCardInFrame = true;
|
|
132
88
|
let hasCroppedSides = false;
|
|
89
|
+
console.log(points)
|
|
133
90
|
|
|
134
91
|
if (cardData) {
|
|
135
92
|
points = cardData.obb;
|
|
@@ -140,14 +97,10 @@ export async function frontVerification(
|
|
|
140
97
|
}
|
|
141
98
|
|
|
142
99
|
if (!isCardInFrame || hasCroppedSides) {
|
|
143
|
-
logger.log(`Front Framing failed. Cropped sides detected.`);
|
|
100
|
+
logger.log(`Front Framing failed. Cropped sides detected by backend.`);
|
|
144
101
|
throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
145
102
|
}
|
|
146
103
|
|
|
147
|
-
if (points && points.length >= 3) {
|
|
148
|
-
assertCardNotTooFar(points);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
104
|
const obbConfidence = getObbConfidence(detected.card_obb);
|
|
152
105
|
if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
|
|
153
106
|
throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
|
|
@@ -174,11 +127,11 @@ export async function frontVerification(
|
|
|
174
127
|
postfix: result?.currentSide || 'front',
|
|
175
128
|
token: token,
|
|
176
129
|
template_path: result?.templatePath || '',
|
|
177
|
-
mrz_type: result?.mrzType || 'TD1'
|
|
130
|
+
mrz_type: result?.mrzType || 'TD1'
|
|
178
131
|
},
|
|
179
132
|
env
|
|
180
133
|
);
|
|
181
|
-
|
|
134
|
+
|
|
182
135
|
mrz = typeof mrzResponse === 'string' ? JSON.parse(mrzResponse) : mrzResponse;
|
|
183
136
|
|
|
184
137
|
if (mrz?.parsed_data?.status === 'FAILURE' || mrz?.success === false) {
|
|
@@ -186,7 +139,7 @@ export async function frontVerification(
|
|
|
186
139
|
throw new Error(`MRZ illisible: ${mrz?.parsed_data?.status_message || 'Veuillez nettoyer l\'objectif et réessayer'}`);
|
|
187
140
|
}
|
|
188
141
|
} catch (err: any) {
|
|
189
|
-
logger.
|
|
142
|
+
logger.log("MRZ Extraction Error:", err);
|
|
190
143
|
throw new Error(err?.message || "Erreur lors de l'extraction MRZ. Veuillez reprendre la photo.");
|
|
191
144
|
}
|
|
192
145
|
}
|
|
@@ -194,12 +147,11 @@ export async function frontVerification(
|
|
|
194
147
|
return { ...detected, croppedBase64, bbox, ...(mrz ? { mrz } : {}) };
|
|
195
148
|
|
|
196
149
|
} catch (e: any) {
|
|
197
|
-
logger.
|
|
150
|
+
logger.log('Error front verification:', e?.message);
|
|
198
151
|
throw new Error(e?.message || 'Erreur de détection du visage');
|
|
199
152
|
}
|
|
200
153
|
}
|
|
201
154
|
|
|
202
|
-
|
|
203
155
|
export async function backVerification(
|
|
204
156
|
result: {
|
|
205
157
|
path?: string,
|
|
@@ -218,8 +170,8 @@ export async function backVerification(
|
|
|
218
170
|
logger.log("result.regionMapping", result.regionMapping, result.currentSide, result.code);
|
|
219
171
|
logger.log('result object', result)
|
|
220
172
|
|
|
221
|
-
const authMethods = Array.isArray(result.regionMapping?.authMethod)
|
|
222
|
-
? result.regionMapping.authMethod.map(m => String(m).toUpperCase())
|
|
173
|
+
const authMethods = Array.isArray(result.regionMapping?.authMethod)
|
|
174
|
+
? result.regionMapping.authMethod.map(m => String(m).toUpperCase())
|
|
223
175
|
: [];
|
|
224
176
|
|
|
225
177
|
const hasMrz = authMethods.some(m => m.includes('MRZ'))
|
|
@@ -285,6 +237,7 @@ export async function backVerification(
|
|
|
285
237
|
let points: number[][] | null = null;
|
|
286
238
|
let isCardInFrame = false;
|
|
287
239
|
let hasCroppedSides = true;
|
|
240
|
+
console.log(points)
|
|
288
241
|
|
|
289
242
|
if (cardData.obb) {
|
|
290
243
|
points = cardData.obb;
|
|
@@ -296,25 +249,14 @@ export async function backVerification(
|
|
|
296
249
|
points = cardData[0];
|
|
297
250
|
isCardInFrame = true;
|
|
298
251
|
hasCroppedSides = false;
|
|
299
|
-
|
|
300
|
-
const minX = Math.min(...points.map((p: number[]) => p[0]));
|
|
301
|
-
const minY = Math.min(...points.map((p: number[]) => p[1]));
|
|
302
|
-
if (minX <= 15 || minY <= 15) {
|
|
303
|
-
isCardInFrame = false;
|
|
304
|
-
hasCroppedSides = true;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
252
|
+
// 🚨 ARTIFICIAL CHECK REMOVED HERE
|
|
307
253
|
}
|
|
308
254
|
|
|
309
255
|
if (!isCardInFrame || hasCroppedSides) {
|
|
310
|
-
logger.log(`Back Framing failed.
|
|
256
|
+
logger.log(`Back Framing failed. Backend reported document out of bounds.`);
|
|
311
257
|
throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
312
258
|
}
|
|
313
259
|
|
|
314
|
-
if (points && points.length >= 3) {
|
|
315
|
-
assertCardNotTooFar(points);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
260
|
const obbConf = getObbConfidence(templateResponse?.card_obb);
|
|
319
261
|
if (obbConf !== null && obbConf < OBB_CONFIDENCE_THRESHOLD) {
|
|
320
262
|
throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
|
|
@@ -354,9 +296,9 @@ export async function backVerification(
|
|
|
354
296
|
if (mrz?.parsed_data?.status === 'FAILURE' || mrz?.success === false) {
|
|
355
297
|
throw new Error(mrz?.parsed_data?.status_message || 'Lecture MRZ échouée');
|
|
356
298
|
}
|
|
357
|
-
|
|
299
|
+
|
|
358
300
|
extractionResult.mrz = mrz; // Attaching securely to result object
|
|
359
|
-
|
|
301
|
+
|
|
360
302
|
} catch (mrzError: any) {
|
|
361
303
|
logger.log(`MRZ échoué: ${mrzError?.message}`);
|
|
362
304
|
// If MRZ fails, we MUST throw an error so the user is forced to retake it!
|
|
@@ -394,7 +336,7 @@ export async function backVerification(
|
|
|
394
336
|
};
|
|
395
337
|
|
|
396
338
|
} catch (e: any) {
|
|
397
|
-
if (e?.message === 'CARD_NOT_FULLY_IN_FRAME'
|
|
339
|
+
if (e?.message === 'CARD_NOT_FULLY_IN_FRAME') throw e;
|
|
398
340
|
throw new Error(e?.message || 'Erreur de détection des données');
|
|
399
341
|
}
|
|
400
342
|
}
|
|
@@ -439,6 +381,7 @@ export async function checkTemplateType(
|
|
|
439
381
|
let points: number[][] | null = null;
|
|
440
382
|
let isCardInFrame = false;
|
|
441
383
|
let hasCroppedSides = true;
|
|
384
|
+
console.log(points)
|
|
442
385
|
|
|
443
386
|
if (cardData.obb) {
|
|
444
387
|
points = cardData.obb;
|
|
@@ -450,25 +393,14 @@ export async function checkTemplateType(
|
|
|
450
393
|
points = cardData[0];
|
|
451
394
|
isCardInFrame = true;
|
|
452
395
|
hasCroppedSides = false;
|
|
453
|
-
|
|
454
|
-
const minX = Math.min(...points.map((p: number[]) => p[0]));
|
|
455
|
-
const minY = Math.min(...points.map((p: number[]) => p[1]));
|
|
456
|
-
if (minX <= 15 || minY <= 15) {
|
|
457
|
-
isCardInFrame = false;
|
|
458
|
-
hasCroppedSides = true;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
396
|
+
// 🚨 ARTIFICIAL CHECK REMOVED HERE
|
|
461
397
|
}
|
|
462
398
|
|
|
463
399
|
if (!isCardInFrame || hasCroppedSides) {
|
|
464
|
-
logger.log(`Template Framing failed.`);
|
|
400
|
+
logger.log(`Template Framing failed by backend check.`);
|
|
465
401
|
throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
466
402
|
}
|
|
467
403
|
|
|
468
|
-
if (points && points.length >= 3) {
|
|
469
|
-
assertCardNotTooFar(points);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
404
|
const LPIPS_THRESHOLD = 0.75;
|
|
473
405
|
if (templateType.lpips_score !== undefined && templateType.lpips_score > LPIPS_THRESHOLD) {
|
|
474
406
|
logger.log(`🛑 Country Mismatch! LPIPS: ${templateType.lpips_score}`);
|
|
@@ -478,8 +410,8 @@ export async function checkTemplateType(
|
|
|
478
410
|
logger.log("templateType result", JSON.stringify(truncateFields(templateType), null, 2));
|
|
479
411
|
return templateType;
|
|
480
412
|
} catch (e: any) {
|
|
481
|
-
logger.
|
|
482
|
-
if (e?.message === 'CARD_NOT_FULLY_IN_FRAME' || e?.message
|
|
413
|
+
logger.log('Errorrr checking template type:', JSON.stringify(errorMessage(e), null, 2));
|
|
414
|
+
if (e?.message === 'CARD_NOT_FULLY_IN_FRAME' || e?.message?.includes('ne correspond pas')) throw e;
|
|
483
415
|
throw new Error(e?.message || 'Erreur de vérification du template');
|
|
484
416
|
}
|
|
485
417
|
}
|
package/src/utils/cropByObb.ts
CHANGED
|
@@ -4,15 +4,9 @@ import * as ImageManipulator from "expo-image-manipulator";
|
|
|
4
4
|
|
|
5
5
|
type Point = [number, number];
|
|
6
6
|
|
|
7
|
-
/** OBB confidence below this = card not fully in frame; don't crop, give user feedback. */
|
|
8
7
|
export const OBB_CONFIDENCE_THRESHOLD = 0.85;
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
* card_obb format from API:
|
|
12
|
-
* - Legacy: [ [ [p1,p2,p3,p4], confidence ] ]
|
|
13
|
-
* - New: [ { obb: [p1,p2,p3,p4], confidence, card_in_frame?, cropped_sides? } ]
|
|
14
|
-
* Returns confidence in [0,1] or null if not present.
|
|
15
|
-
*/
|
|
9
|
+
|
|
16
10
|
export function getObbConfidence(cardObb: any): number | null {
|
|
17
11
|
if (!cardObb || !Array.isArray(cardObb) || cardObb.length === 0) return null;
|
|
18
12
|
const first = cardObb[0];
|
|
@@ -90,7 +84,6 @@ function computeAabb(points: Point[]) {
|
|
|
90
84
|
return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };
|
|
91
85
|
}
|
|
92
86
|
|
|
93
|
-
// Web-only crop using Canvas; returns dataURL (base64)
|
|
94
87
|
async function cropWeb(uri: string, points: Point[]): Promise<string> {
|
|
95
88
|
return new Promise((resolve, reject) => {
|
|
96
89
|
const img = new Image();
|