@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.
Files changed (29) hide show
  1. package/build/package.json +7 -2
  2. package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
  3. package/build/src/components/EnhancedCameraView.js +5 -1
  4. package/build/src/components/EnhancedCameraView.js.map +1 -1
  5. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  6. package/build/src/components/KYCElements/IDCardCapture.js +34 -13
  7. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  8. package/build/src/components/TemplateKYCExample.d.ts +2 -1
  9. package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
  10. package/build/src/components/TemplateKYCExample.js +2 -2
  11. package/build/src/components/TemplateKYCExample.js.map +1 -1
  12. package/build/src/components/TemplateKYCFlowRefactored.d.ts +1 -0
  13. package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  14. package/build/src/components/TemplateKYCFlowRefactored.js +56 -12
  15. package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
  16. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  17. package/build/src/modules/api/CardAuthentification.js +10 -2
  18. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  19. package/build/src/utils/cropByObb.d.ts +1 -1
  20. package/build/src/utils/cropByObb.d.ts.map +1 -1
  21. package/build/src/utils/cropByObb.js +112 -5
  22. package/build/src/utils/cropByObb.js.map +1 -1
  23. package/package.json +7 -2
  24. package/src/components/EnhancedCameraView.tsx +4 -0
  25. package/src/components/KYCElements/IDCardCapture.tsx +49 -21
  26. package/src/components/TemplateKYCExample.tsx +4 -1
  27. package/src/components/TemplateKYCFlowRefactored.tsx +66 -11
  28. package/src/modules/api/CardAuthentification.ts +11 -3
  29. package/src/utils/cropByObb.ts +160 -6
@@ -1 +1 @@
1
- {"version":3,"file":"cropByObb.d.ts","sourceRoot":"","sources":["../../../src/utils/cropByObb.ts"],"names":[],"mappings":"AAMA,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;AAuDD,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,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,GAAE,MAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA0FtH;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,cAAc,GAAE,MAAa,EAAE,uBAAuB;AACtD,mBAAmB,GAAE,MAAa,GACjC,OAAO,CAAC,MAAM,CAAC,CAsDjB"}
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
- pixelMinX = bbox.minX;
162
- pixelMinY = bbox.minY;
163
- pixelWidth = bbox.width;
164
- pixelHeight = bbox.height;
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.58",
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.7.1"
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
- const overlayBbox = cameraConfig.overlay.bbox;
627
+ const overlayBbox = cameraConfig.overlay.bbox;
628
628
 
629
- // 1. Default: Crop based on the landscape UI box
630
- let uiCropBbox = {
631
- minX: overlayBbox.xMin / 100,
632
- minY: overlayBbox.yMin / 100,
633
- width: (overlayBbox.xMax - overlayBbox.xMin) / 100,
634
- height: (overlayBbox.yMax - overlayBbox.yMin) / 100
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
- try {
638
- const isSpecificPortrait = verified.templatePath && verified.templatePath.includes('cameroon_id_front_1_1_new');
639
-
640
- if (isSpecificPortrait) {
641
- uiCropBbox = {
642
- minX: 0.15,
643
- minY: 0.15,
644
- width: 0.70,
645
- height: 0.70
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
- const cropTolerance = isSpecificPortrait ? 0.15 : 0.06;
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
- imagePathForUpload = await cropImageWithBBoxWithTolerance(safeCapturePath, uiCropBbox, cropTolerance);
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
- if (!providedTemplate && !templateId) {
82
- const errorMsg = 'Either template or templateId must be provided';
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
- if (templateId && !activeApiKey) {
89
- logger.log(`Fetching template ${templateId} via token authentication`);
90
- loadTemplate(templateId);
91
- } else if (templateId && activeApiKey) {
92
- logger.log(`Fetching template ${templateId} via API Key`);
93
- // 🚨 2. Use the resolved active key here
94
- loadTemplate(templateId, activeApiKey);
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
- }, [templateId, activeApiKey, providedTemplate, loadTemplate, onError]);
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 || '', templateResponse.card_obb);
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: templateResponse.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
+ }