@sanctum-key/react-native-sdk 1.0.58 → 1.0.60
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 +7 -2
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +5 -1
- 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 +34 -13
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/TemplateKYCExample.d.ts +2 -1
- package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
- package/build/src/components/TemplateKYCExample.js +2 -2
- package/build/src/components/TemplateKYCExample.js.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.d.ts +1 -0
- package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.js +56 -12
- package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +10 -2
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/utils/cropByObb.d.ts +1 -1
- package/build/src/utils/cropByObb.d.ts.map +1 -1
- package/build/src/utils/cropByObb.js +112 -5
- package/build/src/utils/cropByObb.js.map +1 -1
- package/package.json +7 -2
- package/src/components/EnhancedCameraView.tsx +4 -0
- package/src/components/KYCElements/IDCardCapture.tsx +49 -21
- package/src/components/TemplateKYCExample.tsx +4 -1
- package/src/components/TemplateKYCFlowRefactored.tsx +66 -11
- package/src/modules/api/CardAuthentification.ts +11 -3
- package/src/utils/cropByObb.ts +160 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cropByObb.d.ts","sourceRoot":"","sources":["../../../src/utils/cropByObb.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cropByObb.d.ts","sourceRoot":"","sources":["../../../src/utils/cropByObb.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAG7C,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAc5D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,GAAG,IAAI,CAyB3D;AA0JD,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAwB7J;AA4BD,wBAAsB,8BAA8B,CAClD,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,GAAG,EACT,SAAS,GAAE,MAAa,EACxB,wBAAwB,GAAE,WAAW,GAAG,UAAU,GAAG,QAAsB,GAC1E,OAAO,CAAC,MAAM,CAAC,CA4HjB;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,cAAc,GAAE,MAAa,EAAE,uBAAuB;AACtD,mBAAmB,GAAE,MAAa,GACjC,OAAO,CAAC,MAAM,CAAC,CAsDjB"}
|
|
@@ -67,6 +67,87 @@ function computeAabb(points) {
|
|
|
67
67
|
const maxY = Math.max(...ys);
|
|
68
68
|
return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };
|
|
69
69
|
}
|
|
70
|
+
function getInFrameRatio(bbox, imageWidth, imageHeight) {
|
|
71
|
+
const left = Math.max(0, bbox.minX);
|
|
72
|
+
const top = Math.max(0, bbox.minY);
|
|
73
|
+
const right = Math.min(imageWidth, bbox.minX + bbox.width);
|
|
74
|
+
const bottom = Math.min(imageHeight, bbox.minY + bbox.height);
|
|
75
|
+
const intersectionArea = Math.max(0, right - left) * Math.max(0, bottom - top);
|
|
76
|
+
const bboxArea = bbox.width * bbox.height;
|
|
77
|
+
return bboxArea > 0 ? intersectionArea / bboxArea : 0;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The API can return OBB coordinates in the camera sensor's pixel space while
|
|
81
|
+
* ImageManipulator returns an EXIF-normalized image. On phones held sideways,
|
|
82
|
+
* those spaces are 90 degrees apart. Select the representation which best fits
|
|
83
|
+
* the normalized image instead of applying the server coordinates blindly.
|
|
84
|
+
*/
|
|
85
|
+
function resolveBboxOrientation(bbox, imageWidth, imageHeight, preferredCardOrientation = 'landscape') {
|
|
86
|
+
const candidates = [
|
|
87
|
+
{
|
|
88
|
+
bbox,
|
|
89
|
+
orientation: 'direct',
|
|
90
|
+
priority: 0.75,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
// Source canvas is imageHeight x imageWidth and is rotated clockwise.
|
|
94
|
+
bbox: {
|
|
95
|
+
minX: imageWidth - bbox.minY - bbox.height,
|
|
96
|
+
minY: bbox.minX,
|
|
97
|
+
width: bbox.height,
|
|
98
|
+
height: bbox.width,
|
|
99
|
+
},
|
|
100
|
+
orientation: 'clockwise',
|
|
101
|
+
priority: 0,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
// Source canvas is imageHeight x imageWidth and is rotated counter-clockwise.
|
|
105
|
+
bbox: {
|
|
106
|
+
minX: bbox.minY,
|
|
107
|
+
minY: imageHeight - bbox.minX - bbox.width,
|
|
108
|
+
width: bbox.height,
|
|
109
|
+
height: bbox.width,
|
|
110
|
+
},
|
|
111
|
+
orientation: 'counter-clockwise',
|
|
112
|
+
priority: 0,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
bbox: {
|
|
116
|
+
minX: imageWidth - bbox.minX - bbox.width,
|
|
117
|
+
minY: imageHeight - bbox.minY - bbox.height,
|
|
118
|
+
width: bbox.width,
|
|
119
|
+
height: bbox.height,
|
|
120
|
+
},
|
|
121
|
+
orientation: 'upside-down',
|
|
122
|
+
priority: -0.25,
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
const scoredCandidates = candidates.map((candidate) => {
|
|
126
|
+
const candidateBbox = candidate.bbox;
|
|
127
|
+
const inFrameRatio = getInFrameRatio(candidateBbox, imageWidth, imageHeight);
|
|
128
|
+
const centerX = candidateBbox.minX + candidateBbox.width / 2;
|
|
129
|
+
const centerY = candidateBbox.minY + candidateBbox.height / 2;
|
|
130
|
+
const normalizedCenterDistance = Math.hypot((centerX - imageWidth / 2) / Math.max(1, imageWidth / 2), (centerY - imageHeight / 2) / Math.max(1, imageHeight / 2));
|
|
131
|
+
const centerScore = 1 - Math.min(1, normalizedCenterDistance);
|
|
132
|
+
const isLandscapeCard = candidateBbox.width >= candidateBbox.height;
|
|
133
|
+
const preferredOrientationScore = preferredCardOrientation === 'either'
|
|
134
|
+
? 0
|
|
135
|
+
: ((preferredCardOrientation === 'landscape') === isLandscapeCard ? 1 : 0);
|
|
136
|
+
const areaRatio = (candidateBbox.width * candidateBbox.height) / Math.max(1, imageWidth * imageHeight);
|
|
137
|
+
const plausibleAreaScore = Math.min(1, areaRatio / 0.08);
|
|
138
|
+
return {
|
|
139
|
+
bbox: candidateBbox,
|
|
140
|
+
orientation: candidate.orientation,
|
|
141
|
+
inFrameRatio,
|
|
142
|
+
score: inFrameRatio * 100
|
|
143
|
+
+ centerScore * 3
|
|
144
|
+
+ preferredOrientationScore * 2
|
|
145
|
+
+ plausibleAreaScore
|
|
146
|
+
+ candidate.priority,
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
return scoredCandidates.reduce((best, candidate) => (candidate.score > best.score ? candidate : best));
|
|
150
|
+
}
|
|
70
151
|
async function cropWeb(uri, points) {
|
|
71
152
|
return new Promise((resolve, reject) => {
|
|
72
153
|
const img = new Image();
|
|
@@ -127,7 +208,7 @@ async function normalizeOrientationAndGetDimensions(uri) {
|
|
|
127
208
|
});
|
|
128
209
|
return { normalizedUri: result.uri, width: result.width, height: result.height };
|
|
129
210
|
}
|
|
130
|
-
export async function cropImageWithBBoxWithTolerance(uri, bbox, tolerance = 0.02) {
|
|
211
|
+
export async function cropImageWithBBoxWithTolerance(uri, bbox, tolerance = 0.02, preferredCardOrientation = 'landscape') {
|
|
131
212
|
return new Promise((resolve) => {
|
|
132
213
|
let isResolved = false;
|
|
133
214
|
// Safety Timeout (15 seconds — slightly longer to account for normalization step)
|
|
@@ -158,10 +239,36 @@ export async function cropImageWithBBoxWithTolerance(uri, bbox, tolerance = 0.02
|
|
|
158
239
|
pixelHeight = bbox.height * actualHeight;
|
|
159
240
|
}
|
|
160
241
|
else {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
242
|
+
const resolvedBbox = resolveBboxOrientation({
|
|
243
|
+
minX: bbox.minX,
|
|
244
|
+
minY: bbox.minY,
|
|
245
|
+
width: bbox.width,
|
|
246
|
+
height: bbox.height,
|
|
247
|
+
}, actualWidth, actualHeight, preferredCardOrientation);
|
|
248
|
+
if (resolvedBbox.inFrameRatio < 0.6) {
|
|
249
|
+
if (!isResolved) {
|
|
250
|
+
isResolved = true;
|
|
251
|
+
clearTimeout(timeout);
|
|
252
|
+
console.warn('OBB does not match the normalized image bounds; returning the full image.', {
|
|
253
|
+
bbox,
|
|
254
|
+
imageSize: { width: actualWidth, height: actualHeight },
|
|
255
|
+
bestOrientation: resolvedBbox.orientation,
|
|
256
|
+
inFrameRatio: resolvedBbox.inFrameRatio,
|
|
257
|
+
});
|
|
258
|
+
resolve(normalizedUri);
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (resolvedBbox.orientation !== 'direct') {
|
|
263
|
+
console.info('Adjusted OBB coordinates for the normalized camera orientation.', {
|
|
264
|
+
orientation: resolvedBbox.orientation,
|
|
265
|
+
imageSize: { width: actualWidth, height: actualHeight },
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
pixelMinX = resolvedBbox.bbox.minX;
|
|
269
|
+
pixelMinY = resolvedBbox.bbox.minY;
|
|
270
|
+
pixelWidth = resolvedBbox.bbox.width;
|
|
271
|
+
pixelHeight = resolvedBbox.bbox.height;
|
|
165
272
|
}
|
|
166
273
|
// STEP 3: Add tolerance and clamp to image bounds.
|
|
167
274
|
const toleranceX = pixelWidth * tolerance;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cropByObb.js","sourceRoot":"","sources":["../../../src/utils/cropByObb.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from 'react-native';\nimport * as ImageManipulator from \"expo-image-manipulator\";\n\n\ntype Point = [number, number];\n\nexport const OBB_CONFIDENCE_THRESHOLD = 0.85;\n\n\nexport function getObbConfidence(cardObb: any): number | null {\n if (!cardObb || !Array.isArray(cardObb) || cardObb.length === 0) return null;\n const first = cardObb[0];\n\n // New format: first is an object with a confidence field\n if (first && typeof first === 'object' && !Array.isArray(first) && 'confidence' in first) {\n const score = (first as any).confidence;\n return typeof score === 'number' ? score : null;\n }\n\n // Legacy format: [ [points], score ]\n if (!Array.isArray(first) || first.length < 2) return null;\n const score = first[1];\n return typeof score === 'number' ? score : null;\n}\n\n/**\n * Extracts the framing data from card_obb.\n * STRICT CHECK: Returns true ONLY if card_in_frame is true AND cropped_sides is empty.\n */\nexport function getCardInFrame(cardObb: any): boolean | null {\n if (!cardObb) return null;\n \n const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;\n\n if (first && typeof first === 'object' && 'card_in_frame' in first) {\n const isCardInFrame = first.card_in_frame === true;\n const hasCroppedSides = Array.isArray(first.cropped_sides) && first.cropped_sides.length > 0;\n\n if (!isCardInFrame || hasCroppedSides) {\n if (hasCroppedSides) {\n console.warn(`Card is cropped on sides: ${first.cropped_sides.join(', ')}`);\n }\n return false;\n }\n\n return true;\n }\n\n if (cardObb && typeof cardObb === 'object' && 'card_in_frame' in cardObb) {\n const value = (cardObb as any).card_in_frame;\n return typeof value === 'boolean' ? value : null;\n }\n\n return null;\n}\n\n\nfunction extractObbPoints(cardObb: any): Point[] | null {\n if (!cardObb) return null;\n const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;\n\n // New format: { obb: [...], confidence, ... }\n if (first && typeof first === 'object' && !Array.isArray(first) && 'obb' in first) {\n const pts = (first as any).obb;\n if (Array.isArray(pts) && pts.length === 4) return pts as Point[];\n }\n\n // Legacy format: [ [p1,p2,p3,p4], confidence ]\n if (Array.isArray(first) && Array.isArray(first[0]) && first[0].length === 4) {\n return first[0] as Point[];\n }\n\n return null;\n}\n\n// Compute axis-aligned bounding box from oriented quadrilateral\nfunction computeAabb(points: Point[]) {\n const xs = points.map(p => p[0]);\n const ys = points.map(p => p[1]);\n const minX = Math.min(...xs);\n const minY = Math.min(...ys);\n const maxX = Math.max(...xs);\n const maxY = Math.max(...ys);\n return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };\n}\n\nasync function cropWeb(uri: string, points: Point[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n const { minX, minY, width, height } = computeAabb(points);\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width));\n canvas.height = Math.max(1, Math.round(height));\n const ctx = canvas.getContext('2d');\n if (!ctx) return reject(new Error('Canvas context not available'));\n ctx.drawImage(\n img,\n minX, minY, width, height, // Source\n 0, 0, canvas.width, canvas.height // Destination\n );\n resolve(canvas.toDataURL('image/jpeg', 0.92));\n };\n img.onerror = (e) => reject(e);\n img.src = uri;\n });\n}\n\nexport async function cropByObb(uri: string, cardObb: any): Promise<{ base64?: string; bbox?: { minX: number; minY: number; width: number; height: number } }> {\n try {\n const confidence = getObbConfidence(cardObb);\n if (confidence !== null && confidence < OBB_CONFIDENCE_THRESHOLD) {\n return {};\n }\n\n // FIX: Use extractObbPoints() which handles both legacy and new API formats.\n // The old code used `Array.isArray(first?.[0]) ? first[0] : null`, which\n // returned null for new-format responses like [{obb:[...], confidence}].\n const points = extractObbPoints(cardObb);\n if (!points || points.length !== 4) return {};\n\n const { minX, minY, width, height } = computeAabb(points);\n\n if (Platform.OS === 'web') {\n const base64 = await cropWeb(uri, points);\n return { base64, bbox: { minX, minY, width, height } };\n }\n \n return { bbox: { minX, minY, width, height } };\n } catch (e) {\n return {};\n }\n}\n\n\nasync function normalizeOrientationAndGetDimensions(\n uri: string\n): Promise<{ normalizedUri: string; width: number; height: number }> {\n // Web: canvas already handles EXIF; skip normalization\n if (Platform.OS === 'web') {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve({ normalizedUri: uri, width: img.naturalWidth, height: img.naturalHeight });\n img.onerror = reject;\n img.src = uri;\n });\n }\n\n const result = await ImageManipulator.manipulateAsync(\n uri,\n [], \n { \n compress: 1, \n format: ImageManipulator.SaveFormat.JPEG \n }\n );\n return { normalizedUri: result.uri, width: result.width, height: result.height };\n}\n\n\nexport async function cropImageWithBBoxWithTolerance(uri: string, bbox: any, tolerance: number = 0.02): Promise<string> {\n return new Promise<string>((resolve) => {\n let isResolved = false;\n\n // Safety Timeout (15 seconds — slightly longer to account for normalization step)\n const timeout = setTimeout(() => {\n if (!isResolved) {\n isResolved = true;\n console.warn(\"Crop timeout, returning original.\");\n resolve(uri);\n }\n }, 15000);\n\n (async () => {\n try {\n // STEP 1: Normalize EXIF orientation.\n // This is the core fix — on Android the raw photo pixels may be stored\n // rotated. After this call, normalizedUri has pixels matching the on-screen\n // orientation and actualWidth/actualHeight are the display-space dimensions.\n const { normalizedUri, width: actualWidth, height: actualHeight } =\n await normalizeOrientationAndGetDimensions(uri);\n\n if (isResolved) return;\n\n // STEP 2: Resolve bbox to pixel coordinates.\n // Detect if bbox is percentage (e.g., 0.90) or absolute pixels (e.g., 800).\n const isPercentageBased = bbox.width <= 1.5 && bbox.height <= 1.5;\n\n let pixelMinX: number, pixelMinY: number, pixelWidth: number, pixelHeight: number;\n\n if (isPercentageBased) {\n pixelMinX = bbox.minX * actualWidth;\n pixelMinY = bbox.minY * actualHeight;\n pixelWidth = bbox.width * actualWidth;\n pixelHeight = bbox.height * actualHeight;\n } else {\n pixelMinX = bbox.minX;\n pixelMinY = bbox.minY;\n pixelWidth = bbox.width;\n pixelHeight = bbox.height;\n }\n\n // STEP 3: Add tolerance and clamp to image bounds.\n const toleranceX = pixelWidth * tolerance;\n const toleranceY = pixelHeight * tolerance;\n\n const newMinX = Math.max(0, pixelMinX - toleranceX);\n const newMinY = Math.max(0, pixelMinY - toleranceY);\n const newMaxX = Math.min(actualWidth, pixelMinX + pixelWidth + toleranceX);\n const newMaxY = Math.min(actualHeight, pixelMinY + pixelHeight + toleranceY);\n\n const finalCrop = {\n originX: Math.floor(newMinX),\n originY: Math.floor(newMinY),\n width: Math.max(1, Math.floor(newMaxX - newMinX)),\n height: Math.max(1, Math.floor(newMaxY - newMinY)),\n };\n\n // STEP 4: Perform final crop on the already-normalized image.\n try {\n const result = await ImageManipulator.manipulateAsync(\n normalizedUri,\n [{ crop: finalCrop }],\n { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG }\n );\n\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n resolve(result.uri);\n }\n } catch (err) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"ImageManipulator crop failed, returning normalized image\", err);\n\n resolve(normalizedUri);\n }\n }\n } catch (e) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"normalizeOrientationAndGetDimensions failed, returning original\", e);\n resolve(uri);\n }\n }\n })();\n });\n}\n\nexport async function cropToCenterScanArea(\n uri: string, \n scanWidthRatio: number = 0.91, // Match Kotlin's 0.91f\n scanAreaAspectRatio: number = 1.59 // Standard ID Card ratio\n): Promise<string> {\n return new Promise<string>((resolve) => {\n let isResolved = false;\n\n const timeout = setTimeout(() => {\n if (!isResolved) {\n isResolved = true;\n resolve(uri);\n }\n }, 10000);\n\n (async () => {\n try {\n // 1. Use Claude's brilliant EXIF normalization first!\n const { normalizedUri, width: actualWidth, height: actualHeight } = \n await normalizeOrientationAndGetDimensions(uri);\n\n if (isResolved) return;\n\n // 2. Apply the Kotlin mathematical centering\n const scanWidth = actualWidth * scanWidthRatio;\n const scanHeight = scanWidth / scanAreaAspectRatio;\n const left = (actualWidth - scanWidth) / 2;\n const top = (actualHeight - scanHeight) / 2;\n\n const finalCrop = {\n originX: Math.max(0, Math.floor(left)),\n originY: Math.max(0, Math.floor(top)),\n width: Math.max(1, Math.floor(scanWidth)),\n height: Math.max(1, Math.floor(scanHeight)),\n };\n\n // 3. Perform the final centered crop\n const result = await ImageManipulator.manipulateAsync(\n normalizedUri,\n [{ crop: finalCrop }],\n { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG }\n );\n \n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n resolve(result.uri);\n }\n } catch (err) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"Center crop failed, returning original\", err);\n resolve(uri); // Fallback to raw image\n }\n }\n })();\n });\n}"]}
|
|
1
|
+
{"version":3,"file":"cropByObb.js","sourceRoot":"","sources":["../../../src/utils/cropByObb.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from 'react-native';\nimport * as ImageManipulator from \"expo-image-manipulator\";\n\n\ntype Point = [number, number];\n\ntype PixelBbox = {\n minX: number;\n minY: number;\n width: number;\n height: number;\n};\n\ntype BboxOrientation = 'direct' | 'clockwise' | 'counter-clockwise' | 'upside-down';\n\ntype OrientedBboxCandidate = {\n bbox: PixelBbox;\n orientation: BboxOrientation;\n inFrameRatio: number;\n score: number;\n};\n\nexport const OBB_CONFIDENCE_THRESHOLD = 0.85;\n\n\nexport function getObbConfidence(cardObb: any): number | null {\n if (!cardObb || !Array.isArray(cardObb) || cardObb.length === 0) return null;\n const first = cardObb[0];\n\n // New format: first is an object with a confidence field\n if (first && typeof first === 'object' && !Array.isArray(first) && 'confidence' in first) {\n const score = (first as any).confidence;\n return typeof score === 'number' ? score : null;\n }\n\n // Legacy format: [ [points], score ]\n if (!Array.isArray(first) || first.length < 2) return null;\n const score = first[1];\n return typeof score === 'number' ? score : null;\n}\n\n/**\n * Extracts the framing data from card_obb.\n * STRICT CHECK: Returns true ONLY if card_in_frame is true AND cropped_sides is empty.\n */\nexport function getCardInFrame(cardObb: any): boolean | null {\n if (!cardObb) return null;\n \n const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;\n\n if (first && typeof first === 'object' && 'card_in_frame' in first) {\n const isCardInFrame = first.card_in_frame === true;\n const hasCroppedSides = Array.isArray(first.cropped_sides) && first.cropped_sides.length > 0;\n\n if (!isCardInFrame || hasCroppedSides) {\n if (hasCroppedSides) {\n console.warn(`Card is cropped on sides: ${first.cropped_sides.join(', ')}`);\n }\n return false;\n }\n\n return true;\n }\n\n if (cardObb && typeof cardObb === 'object' && 'card_in_frame' in cardObb) {\n const value = (cardObb as any).card_in_frame;\n return typeof value === 'boolean' ? value : null;\n }\n\n return null;\n}\n\n\nfunction extractObbPoints(cardObb: any): Point[] | null {\n if (!cardObb) return null;\n const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;\n\n // New format: { obb: [...], confidence, ... }\n if (first && typeof first === 'object' && !Array.isArray(first) && 'obb' in first) {\n const pts = (first as any).obb;\n if (Array.isArray(pts) && pts.length === 4) return pts as Point[];\n }\n\n // Legacy format: [ [p1,p2,p3,p4], confidence ]\n if (Array.isArray(first) && Array.isArray(first[0]) && first[0].length === 4) {\n return first[0] as Point[];\n }\n\n return null;\n}\n\n// Compute axis-aligned bounding box from oriented quadrilateral\nfunction computeAabb(points: Point[]) {\n const xs = points.map(p => p[0]);\n const ys = points.map(p => p[1]);\n const minX = Math.min(...xs);\n const minY = Math.min(...ys);\n const maxX = Math.max(...xs);\n const maxY = Math.max(...ys);\n return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };\n}\n\nfunction getInFrameRatio(bbox: PixelBbox, imageWidth: number, imageHeight: number): number {\n const left = Math.max(0, bbox.minX);\n const top = Math.max(0, bbox.minY);\n const right = Math.min(imageWidth, bbox.minX + bbox.width);\n const bottom = Math.min(imageHeight, bbox.minY + bbox.height);\n const intersectionArea = Math.max(0, right - left) * Math.max(0, bottom - top);\n const bboxArea = bbox.width * bbox.height;\n\n return bboxArea > 0 ? intersectionArea / bboxArea : 0;\n}\n\n/**\n * The API can return OBB coordinates in the camera sensor's pixel space while\n * ImageManipulator returns an EXIF-normalized image. On phones held sideways,\n * those spaces are 90 degrees apart. Select the representation which best fits\n * the normalized image instead of applying the server coordinates blindly.\n */\nfunction resolveBboxOrientation(\n bbox: PixelBbox,\n imageWidth: number,\n imageHeight: number,\n preferredCardOrientation: 'landscape' | 'portrait' | 'either' = 'landscape',\n): OrientedBboxCandidate {\n const candidates: Array<{ bbox: PixelBbox; orientation: BboxOrientation; priority: number }> = [\n {\n bbox,\n orientation: 'direct',\n priority: 0.75,\n },\n {\n // Source canvas is imageHeight x imageWidth and is rotated clockwise.\n bbox: {\n minX: imageWidth - bbox.minY - bbox.height,\n minY: bbox.minX,\n width: bbox.height,\n height: bbox.width,\n },\n orientation: 'clockwise',\n priority: 0,\n },\n {\n // Source canvas is imageHeight x imageWidth and is rotated counter-clockwise.\n bbox: {\n minX: bbox.minY,\n minY: imageHeight - bbox.minX - bbox.width,\n width: bbox.height,\n height: bbox.width,\n },\n orientation: 'counter-clockwise',\n priority: 0,\n },\n {\n bbox: {\n minX: imageWidth - bbox.minX - bbox.width,\n minY: imageHeight - bbox.minY - bbox.height,\n width: bbox.width,\n height: bbox.height,\n },\n orientation: 'upside-down',\n priority: -0.25,\n },\n ];\n\n const scoredCandidates = candidates.map((candidate) => {\n const candidateBbox = candidate.bbox;\n const inFrameRatio = getInFrameRatio(candidateBbox, imageWidth, imageHeight);\n const centerX = candidateBbox.minX + candidateBbox.width / 2;\n const centerY = candidateBbox.minY + candidateBbox.height / 2;\n const normalizedCenterDistance = Math.hypot(\n (centerX - imageWidth / 2) / Math.max(1, imageWidth / 2),\n (centerY - imageHeight / 2) / Math.max(1, imageHeight / 2),\n );\n const centerScore = 1 - Math.min(1, normalizedCenterDistance);\n\n const isLandscapeCard = candidateBbox.width >= candidateBbox.height;\n const preferredOrientationScore = preferredCardOrientation === 'either'\n ? 0\n : ((preferredCardOrientation === 'landscape') === isLandscapeCard ? 1 : 0);\n const areaRatio = (candidateBbox.width * candidateBbox.height) / Math.max(1, imageWidth * imageHeight);\n const plausibleAreaScore = Math.min(1, areaRatio / 0.08);\n\n return {\n bbox: candidateBbox,\n orientation: candidate.orientation,\n inFrameRatio,\n score:\n inFrameRatio * 100\n + centerScore * 3\n + preferredOrientationScore * 2\n + plausibleAreaScore\n + candidate.priority,\n };\n });\n\n return scoredCandidates.reduce((best, candidate) => (\n candidate.score > best.score ? candidate : best\n ));\n}\n\nasync function cropWeb(uri: string, points: Point[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n const { minX, minY, width, height } = computeAabb(points);\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width));\n canvas.height = Math.max(1, Math.round(height));\n const ctx = canvas.getContext('2d');\n if (!ctx) return reject(new Error('Canvas context not available'));\n ctx.drawImage(\n img,\n minX, minY, width, height, // Source\n 0, 0, canvas.width, canvas.height // Destination\n );\n resolve(canvas.toDataURL('image/jpeg', 0.92));\n };\n img.onerror = (e) => reject(e);\n img.src = uri;\n });\n}\n\nexport async function cropByObb(uri: string, cardObb: any): Promise<{ base64?: string; bbox?: { minX: number; minY: number; width: number; height: number } }> {\n try {\n const confidence = getObbConfidence(cardObb);\n if (confidence !== null && confidence < OBB_CONFIDENCE_THRESHOLD) {\n return {};\n }\n\n // FIX: Use extractObbPoints() which handles both legacy and new API formats.\n // The old code used `Array.isArray(first?.[0]) ? first[0] : null`, which\n // returned null for new-format responses like [{obb:[...], confidence}].\n const points = extractObbPoints(cardObb);\n if (!points || points.length !== 4) return {};\n\n const { minX, minY, width, height } = computeAabb(points);\n\n if (Platform.OS === 'web') {\n const base64 = await cropWeb(uri, points);\n return { base64, bbox: { minX, minY, width, height } };\n }\n \n return { bbox: { minX, minY, width, height } };\n } catch (e) {\n return {};\n }\n}\n\n\nasync function normalizeOrientationAndGetDimensions(\n uri: string\n): Promise<{ normalizedUri: string; width: number; height: number }> {\n // Web: canvas already handles EXIF; skip normalization\n if (Platform.OS === 'web') {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve({ normalizedUri: uri, width: img.naturalWidth, height: img.naturalHeight });\n img.onerror = reject;\n img.src = uri;\n });\n }\n\n const result = await ImageManipulator.manipulateAsync(\n uri,\n [], \n { \n compress: 1, \n format: ImageManipulator.SaveFormat.JPEG \n }\n );\n return { normalizedUri: result.uri, width: result.width, height: result.height };\n}\n\n\nexport async function cropImageWithBBoxWithTolerance(\n uri: string,\n bbox: any,\n tolerance: number = 0.02,\n preferredCardOrientation: 'landscape' | 'portrait' | 'either' = 'landscape',\n): Promise<string> {\n return new Promise<string>((resolve) => {\n let isResolved = false;\n\n // Safety Timeout (15 seconds — slightly longer to account for normalization step)\n const timeout = setTimeout(() => {\n if (!isResolved) {\n isResolved = true;\n console.warn(\"Crop timeout, returning original.\");\n resolve(uri);\n }\n }, 15000);\n\n (async () => {\n try {\n // STEP 1: Normalize EXIF orientation.\n // This is the core fix — on Android the raw photo pixels may be stored\n // rotated. After this call, normalizedUri has pixels matching the on-screen\n // orientation and actualWidth/actualHeight are the display-space dimensions.\n const { normalizedUri, width: actualWidth, height: actualHeight } =\n await normalizeOrientationAndGetDimensions(uri);\n\n if (isResolved) return;\n\n // STEP 2: Resolve bbox to pixel coordinates.\n // Detect if bbox is percentage (e.g., 0.90) or absolute pixels (e.g., 800).\n const isPercentageBased = bbox.width <= 1.5 && bbox.height <= 1.5;\n\n let pixelMinX: number, pixelMinY: number, pixelWidth: number, pixelHeight: number;\n\n if (isPercentageBased) {\n pixelMinX = bbox.minX * actualWidth;\n pixelMinY = bbox.minY * actualHeight;\n pixelWidth = bbox.width * actualWidth;\n pixelHeight = bbox.height * actualHeight;\n } else {\n const resolvedBbox = resolveBboxOrientation(\n {\n minX: bbox.minX,\n minY: bbox.minY,\n width: bbox.width,\n height: bbox.height,\n },\n actualWidth,\n actualHeight,\n preferredCardOrientation,\n );\n\n if (resolvedBbox.inFrameRatio < 0.6) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.warn('OBB does not match the normalized image bounds; returning the full image.', {\n bbox,\n imageSize: { width: actualWidth, height: actualHeight },\n bestOrientation: resolvedBbox.orientation,\n inFrameRatio: resolvedBbox.inFrameRatio,\n });\n resolve(normalizedUri);\n }\n return;\n }\n\n if (resolvedBbox.orientation !== 'direct') {\n console.info('Adjusted OBB coordinates for the normalized camera orientation.', {\n orientation: resolvedBbox.orientation,\n imageSize: { width: actualWidth, height: actualHeight },\n });\n }\n\n pixelMinX = resolvedBbox.bbox.minX;\n pixelMinY = resolvedBbox.bbox.minY;\n pixelWidth = resolvedBbox.bbox.width;\n pixelHeight = resolvedBbox.bbox.height;\n }\n\n // STEP 3: Add tolerance and clamp to image bounds.\n const toleranceX = pixelWidth * tolerance;\n const toleranceY = pixelHeight * tolerance;\n\n const newMinX = Math.max(0, pixelMinX - toleranceX);\n const newMinY = Math.max(0, pixelMinY - toleranceY);\n const newMaxX = Math.min(actualWidth, pixelMinX + pixelWidth + toleranceX);\n const newMaxY = Math.min(actualHeight, pixelMinY + pixelHeight + toleranceY);\n\n const finalCrop = {\n originX: Math.floor(newMinX),\n originY: Math.floor(newMinY),\n width: Math.max(1, Math.floor(newMaxX - newMinX)),\n height: Math.max(1, Math.floor(newMaxY - newMinY)),\n };\n\n // STEP 4: Perform final crop on the already-normalized image.\n try {\n const result = await ImageManipulator.manipulateAsync(\n normalizedUri,\n [{ crop: finalCrop }],\n { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG }\n );\n\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n resolve(result.uri);\n }\n } catch (err) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"ImageManipulator crop failed, returning normalized image\", err);\n\n resolve(normalizedUri);\n }\n }\n } catch (e) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"normalizeOrientationAndGetDimensions failed, returning original\", e);\n resolve(uri);\n }\n }\n })();\n });\n}\n\nexport async function cropToCenterScanArea(\n uri: string, \n scanWidthRatio: number = 0.91, // Match Kotlin's 0.91f\n scanAreaAspectRatio: number = 1.59 // Standard ID Card ratio\n): Promise<string> {\n return new Promise<string>((resolve) => {\n let isResolved = false;\n\n const timeout = setTimeout(() => {\n if (!isResolved) {\n isResolved = true;\n resolve(uri);\n }\n }, 10000);\n\n (async () => {\n try {\n // 1. Use Claude's brilliant EXIF normalization first!\n const { normalizedUri, width: actualWidth, height: actualHeight } = \n await normalizeOrientationAndGetDimensions(uri);\n\n if (isResolved) return;\n\n // 2. Apply the Kotlin mathematical centering\n const scanWidth = actualWidth * scanWidthRatio;\n const scanHeight = scanWidth / scanAreaAspectRatio;\n const left = (actualWidth - scanWidth) / 2;\n const top = (actualHeight - scanHeight) / 2;\n\n const finalCrop = {\n originX: Math.max(0, Math.floor(left)),\n originY: Math.max(0, Math.floor(top)),\n width: Math.max(1, Math.floor(scanWidth)),\n height: Math.max(1, Math.floor(scanHeight)),\n };\n\n // 3. Perform the final centered crop\n const result = await ImageManipulator.manipulateAsync(\n normalizedUri,\n [{ crop: finalCrop }],\n { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG }\n );\n \n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n resolve(result.uri);\n }\n } catch (err) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"Center crop failed, returning original\", err);\n resolve(uri); // Fallback to raw image\n }\n }\n })();\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanctum-key/react-native-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.60",
|
|
4
4
|
"description": "Sanctum Key React Native SDK",
|
|
5
5
|
"main": "build/src/index.js",
|
|
6
6
|
"types": "build/src/index.d.ts",
|
|
@@ -78,6 +78,11 @@
|
|
|
78
78
|
"react": "*",
|
|
79
79
|
"react-native": "*",
|
|
80
80
|
"react-native-svg": "*",
|
|
81
|
-
"react-native-vision-camera": "^4.
|
|
81
|
+
"react-native-vision-camera": "^4.0.0",
|
|
82
|
+
"posthog-react-native": "^3.0.0 || ^4.0.0",
|
|
83
|
+
"@react-native-async-storage/async-storage": ">=1.18.0",
|
|
84
|
+
"expo-application": ">=5.0.0",
|
|
85
|
+
"expo-device": ">=5.0.0",
|
|
86
|
+
"expo-localization": ">=14.0.0"
|
|
82
87
|
}
|
|
83
88
|
}
|
|
@@ -294,6 +294,10 @@ export const EnhancedCameraView: React.FC<ExtendedCameraProps> = ({
|
|
|
294
294
|
style={StyleSheet.absoluteFill}
|
|
295
295
|
device={device}
|
|
296
296
|
resizeMode="cover"
|
|
297
|
+
// Keep the captured pixels in the same orientation as the preview and
|
|
298
|
+
// its document guide. The default "device" mode can save a landscape
|
|
299
|
+
// photo while a rotation-locked app is still displaying portrait.
|
|
300
|
+
outputOrientation="preview"
|
|
297
301
|
isActive={showCamera && !isProcessing && appState === 'active'}
|
|
298
302
|
photo={true}
|
|
299
303
|
photoQualityBalance={isScanningBarcode ? 'quality' : 'balanced'}
|
|
@@ -624,31 +624,59 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
624
624
|
|
|
625
625
|
try {
|
|
626
626
|
let imagePathForUpload = capturePath;
|
|
627
|
-
|
|
627
|
+
const overlayBbox = cameraConfig.overlay.bbox;
|
|
628
628
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
629
|
+
// Prefer the backend-detected card bounds. Front verification returns the
|
|
630
|
+
// detect_face OBB-derived bbox; back verification returns extract_mrz's
|
|
631
|
+
// OBB-derived bbox when MRZ is used. The fixed UI guide is only a fallback.
|
|
632
|
+
const detectedBbox = verified.bbox;
|
|
633
|
+
const hasDetectedBbox = detectedBbox
|
|
634
|
+
&& Number.isFinite(detectedBbox.minX)
|
|
635
|
+
&& Number.isFinite(detectedBbox.minY)
|
|
636
|
+
&& Number.isFinite(detectedBbox.width)
|
|
637
|
+
&& Number.isFinite(detectedBbox.height)
|
|
638
|
+
&& detectedBbox.width > 0
|
|
639
|
+
&& detectedBbox.height > 0;
|
|
640
|
+
|
|
641
|
+
let cropBbox: IBbox = hasDetectedBbox
|
|
642
|
+
? detectedBbox
|
|
643
|
+
: {
|
|
644
|
+
minX: overlayBbox.xMin / 100,
|
|
645
|
+
minY: overlayBbox.yMin / 100,
|
|
646
|
+
width: (overlayBbox.xMax - overlayBbox.xMin) / 100,
|
|
647
|
+
height: (overlayBbox.yMax - overlayBbox.yMin) / 100,
|
|
648
|
+
};
|
|
649
|
+
const isSpecificPortrait = Boolean(
|
|
650
|
+
verified.templatePath?.includes('cameroon_id_front_1_1_new'),
|
|
651
|
+
);
|
|
636
652
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
};
|
|
647
|
-
}
|
|
653
|
+
try {
|
|
654
|
+
if (!hasDetectedBbox && isSpecificPortrait) {
|
|
655
|
+
cropBbox = {
|
|
656
|
+
minX: 0.15,
|
|
657
|
+
minY: 0.15,
|
|
658
|
+
width: 0.70,
|
|
659
|
+
height: 0.70,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
648
662
|
|
|
649
|
-
|
|
663
|
+
// OBB bounds already track the physical card, so only retain a small
|
|
664
|
+
// safety margin. UI-guide fallbacks remain more forgiving.
|
|
665
|
+
const cropTolerance = hasDetectedBbox ? 0.025 : (isSpecificPortrait ? 0.15 : 0.06);
|
|
650
666
|
|
|
651
|
-
|
|
667
|
+
imagePathForUpload = await cropImageWithBBoxWithTolerance(
|
|
668
|
+
safeCapturePath,
|
|
669
|
+
cropBbox,
|
|
670
|
+
cropTolerance,
|
|
671
|
+
isSpecificPortrait ? 'portrait' : 'landscape',
|
|
672
|
+
);
|
|
673
|
+
sdkLogger.info('Prepared captured document using verified card bounds', {
|
|
674
|
+
side: captureSide,
|
|
675
|
+
cropSource: hasDetectedBbox
|
|
676
|
+
? (captureSide === 'front' ? 'detect_face_obb' : 'extract_mrz_or_template_obb')
|
|
677
|
+
: 'camera_overlay_fallback',
|
|
678
|
+
bbox: cropBbox,
|
|
679
|
+
});
|
|
652
680
|
} catch (e) {
|
|
653
681
|
console.warn("Crop failed, falling back to original image", e);
|
|
654
682
|
imagePathForUpload = safeCapturePath;
|
|
@@ -59,6 +59,7 @@ export interface TemplateKYCExampleProps {
|
|
|
59
59
|
referenceId?: string;
|
|
60
60
|
onSessionSave?: (sessionId: string) => void;
|
|
61
61
|
kyc_session_id?: string;
|
|
62
|
+
templateConfig?: Record<string, string>;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
export function TemplateKYCExample({
|
|
@@ -77,7 +78,8 @@ export function TemplateKYCExample({
|
|
|
77
78
|
initialCountryResume,
|
|
78
79
|
referenceId,
|
|
79
80
|
onSessionSave,
|
|
80
|
-
kyc_session_id
|
|
81
|
+
kyc_session_id,
|
|
82
|
+
templateConfig,
|
|
81
83
|
}: TemplateKYCExampleProps): JSX.Element {
|
|
82
84
|
|
|
83
85
|
const handleComplete = (data: VerificationState) => {
|
|
@@ -150,6 +152,7 @@ export function TemplateKYCExample({
|
|
|
150
152
|
<TemplateKYCFlow
|
|
151
153
|
template={templateToUse}
|
|
152
154
|
templateId={templateId}
|
|
155
|
+
templateConfig={templateConfig}
|
|
153
156
|
onComplete={handleComplete}
|
|
154
157
|
onError={handleError}
|
|
155
158
|
onCancel={handleCancel}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import { View, Text, StyleSheet, SafeAreaView, TouchableOpacity, ActivityIndicator, Dimensions, ScrollView, Platform } from 'react-native';
|
|
3
3
|
import { KYCTemplate, VerificationState } from '../types/KYC.types';
|
|
4
4
|
import { TemplateKYCFlowProvider, useTemplateKYCFlowContext } from '../hooks/useTemplateKYCFlow';
|
|
@@ -39,6 +39,7 @@ interface TemplateKYCFlowProps {
|
|
|
39
39
|
referenceId?: string;
|
|
40
40
|
onSessionSave?: (sessionId: string) => void;
|
|
41
41
|
kyc_session_id?: string;
|
|
42
|
+
templateConfig?: Record<string, string>;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
@@ -58,9 +59,12 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
58
59
|
referenceId,
|
|
59
60
|
onSessionSave,
|
|
60
61
|
kyc_session_id,
|
|
62
|
+
templateConfig,
|
|
61
63
|
}) => {
|
|
62
64
|
const { t } = useI18n();
|
|
63
65
|
const { template: loadedTemplate, isLoading, error, loadTemplate } = useTemplateLoader();
|
|
66
|
+
const [resolvedTemplateId, setResolvedTemplateId] = useState<string | undefined>(templateId);
|
|
67
|
+
const [isResolving, setIsResolving] = useState<boolean>(!templateId && !!templateConfig);
|
|
64
68
|
|
|
65
69
|
// 🚨 1. Determine the active API key based on the environment
|
|
66
70
|
const activeApiKey = env === 'PRODUCTION' ? API_KEY : (TEST_API_KEY || API_KEY);
|
|
@@ -77,23 +81,74 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
77
81
|
}
|
|
78
82
|
}, [serverEnv]);
|
|
79
83
|
|
|
84
|
+
|
|
80
85
|
useEffect(() => {
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
// If a strict templateId is provided, use it immediately and skip geolocation
|
|
87
|
+
if (templateId) {
|
|
88
|
+
setResolvedTemplateId(templateId);
|
|
89
|
+
setIsResolving(false);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// If neither templateId nor templateConfig is provided, skip to the next effect
|
|
94
|
+
if (!templateConfig) {
|
|
95
|
+
setIsResolving(false);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const fetchLocationAndRoute = async () => {
|
|
100
|
+
setIsResolving(true);
|
|
101
|
+
let selected = templateConfig['DEFAULT'];
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch('https://ipapi.co/json/');
|
|
105
|
+
const data = await response.json();
|
|
106
|
+
|
|
107
|
+
// Priority: Exact Country -> Continent -> DEFAULT
|
|
108
|
+
selected =
|
|
109
|
+
templateConfig[data.country_code] ||
|
|
110
|
+
templateConfig[data.continent_code] ||
|
|
111
|
+
selected;
|
|
112
|
+
|
|
113
|
+
logger.info('Template dynamically resolved via location', {
|
|
114
|
+
country: data.country_code,
|
|
115
|
+
continent: data.continent_code,
|
|
116
|
+
selected_template: selected
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
} catch (error) {
|
|
120
|
+
logger.warn('Location detection failed, using default template');
|
|
121
|
+
} finally {
|
|
122
|
+
setResolvedTemplateId(selected);
|
|
123
|
+
setIsResolving(false);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
fetchLocationAndRoute();
|
|
128
|
+
}, [templateId, templateConfig]); // Only re-run if the props change
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (isResolving) return;
|
|
134
|
+
|
|
135
|
+
// Updated error check to include templateConfig
|
|
136
|
+
if (!providedTemplate && !resolvedTemplateId) {
|
|
137
|
+
const errorMsg = 'Either template, templateId, or templateConfig must be provided';
|
|
83
138
|
logger.error("Template Loading Failed: Missing Props", { errorMsg });
|
|
84
139
|
onError?.(errorMsg);
|
|
85
140
|
return;
|
|
86
141
|
}
|
|
87
142
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
loadTemplate(
|
|
143
|
+
// Load using the newly resolved ID
|
|
144
|
+
if (resolvedTemplateId && !activeApiKey) {
|
|
145
|
+
logger.log(`Fetching template ${resolvedTemplateId} via token authentication`);
|
|
146
|
+
loadTemplate(resolvedTemplateId);
|
|
147
|
+
} else if (resolvedTemplateId && activeApiKey) {
|
|
148
|
+
logger.log(`Fetching template ${resolvedTemplateId} via API Key`);
|
|
149
|
+
loadTemplate(resolvedTemplateId, activeApiKey);
|
|
95
150
|
}
|
|
96
|
-
}, [
|
|
151
|
+
}, [resolvedTemplateId, activeApiKey, providedTemplate, loadTemplate, onError, isResolving]);
|
|
97
152
|
|
|
98
153
|
useEffect(() => {
|
|
99
154
|
if (error) {
|
|
@@ -284,6 +284,7 @@ export async function backVerification(
|
|
|
284
284
|
|
|
285
285
|
logger.log("Framing, Country, and Match verified successfully. Proceeding to Data Extraction.");
|
|
286
286
|
let extractionResult: any = {};
|
|
287
|
+
let mrzCardObb: any;
|
|
287
288
|
|
|
288
289
|
if (result.code === 'CA') {
|
|
289
290
|
try {
|
|
@@ -331,6 +332,9 @@ export async function backVerification(
|
|
|
331
332
|
throw new Error(mrz?.parsed_data?.status_message || 'Lecture MRZ échouée');
|
|
332
333
|
}
|
|
333
334
|
extractionResult.mrz = mrz;
|
|
335
|
+
if (mrz?.card_obb) {
|
|
336
|
+
mrzCardObb = mrz.card_obb;
|
|
337
|
+
}
|
|
334
338
|
} catch (mrzError: any) {
|
|
335
339
|
logger.log(`MRZ échoué: ${mrzError?.message}`);
|
|
336
340
|
throw new Error(`Lecture MRZ invalide: ${mrzError?.message || 'Veuillez nettoyer l\'objectif et réessayer'}`);
|
|
@@ -339,9 +343,13 @@ export async function backVerification(
|
|
|
339
343
|
|
|
340
344
|
let bbox: IBbox | undefined;
|
|
341
345
|
let croppedBase64: string | undefined;
|
|
346
|
+
// For MRZ-backed documents, extract_mrz sees the final verified back image
|
|
347
|
+
// and provides the authoritative card bounds. Keep the template OBB as a
|
|
348
|
+
// fallback for barcode-only and non-MRZ document backs.
|
|
349
|
+
const finalCardObb = mrzCardObb || templateResponse.card_obb;
|
|
342
350
|
|
|
343
351
|
try {
|
|
344
|
-
const crop = await cropByObb(result?.path || '',
|
|
352
|
+
const crop = await cropByObb(result?.path || '', finalCardObb);
|
|
345
353
|
bbox = crop.bbox;
|
|
346
354
|
croppedBase64 = crop.base64;
|
|
347
355
|
} catch {
|
|
@@ -352,7 +360,7 @@ export async function backVerification(
|
|
|
352
360
|
...extractionResult,
|
|
353
361
|
bbox,
|
|
354
362
|
croppedBase64,
|
|
355
|
-
card_obb:
|
|
363
|
+
card_obb: finalCardObb
|
|
356
364
|
};
|
|
357
365
|
|
|
358
366
|
} catch (e: any) {
|
|
@@ -443,4 +451,4 @@ export async function checkTemplateType(
|
|
|
443
451
|
|
|
444
452
|
throw new Error(typeof e === 'string' ? e : (e?.message || 'Erreur de vérification du template'));
|
|
445
453
|
}
|
|
446
|
-
}
|
|
454
|
+
}
|