@trustchex/react-native-sdk 1.488.0 → 1.489.0

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 (27) hide show
  1. package/ios/ImageDecoder/ImageDecoderModule.m +12 -0
  2. package/ios/ImageDecoder/ImageDecoderModule.swift +43 -0
  3. package/lib/module/Shared/Components/EIDScanner.js +5 -1
  4. package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +22 -2
  5. package/lib/module/Shared/Components/IdentityDocumentCamera.js +1 -1
  6. package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +7 -7
  7. package/lib/module/Shared/EIDReader/eidReader.js +15 -3
  8. package/lib/module/Shared/Libs/jp2Decode.js +43 -0
  9. package/lib/module/version.js +1 -1
  10. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  11. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  12. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +11 -0
  13. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -1
  14. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +1 -1
  15. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
  16. package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
  17. package/lib/typescript/src/Shared/Libs/jp2Decode.d.ts +13 -0
  18. package/lib/typescript/src/Shared/Libs/jp2Decode.d.ts.map +1 -0
  19. package/lib/typescript/src/version.d.ts +1 -1
  20. package/package.json +1 -1
  21. package/src/Shared/Components/EIDScanner.tsx +5 -1
  22. package/src/Shared/Components/IdentityDocumentCamera.flows.ts +22 -2
  23. package/src/Shared/Components/IdentityDocumentCamera.tsx +1 -2
  24. package/src/Shared/Components/IdentityDocumentCamera.utils.ts +6 -8
  25. package/src/Shared/EIDReader/eidReader.ts +19 -4
  26. package/src/Shared/Libs/jp2Decode.ts +48 -0
  27. package/src/version.ts +1 -1
@@ -0,0 +1,12 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(ImageDecoderModule, NSObject)
4
+
5
+ // Decode a JPEG2000 (JP2) base64 string and re-encode it as JPEG base64.
6
+ // iOS's <Image> cannot render an image/jp2 data URI, but ImageIO/UIImage
7
+ // decodes JPEG2000 natively, so we round-trip it to JPEG here.
8
+ RCT_EXTERN_METHOD(decodeJp2ToJpeg:(NSString *)base64Jp2
9
+ resolver:(RCTPromiseResolveBlock)resolve
10
+ rejecter:(RCTPromiseRejectBlock)reject)
11
+
12
+ @end
@@ -0,0 +1,43 @@
1
+ import Foundation
2
+ import React
3
+ import UIKit
4
+ import ImageIO
5
+
6
+ /// Decodes images that React Native's <Image> cannot render directly on iOS.
7
+ ///
8
+ /// The driving case is the passport chip portrait (DG2), which is frequently
9
+ /// stored as JPEG2000 (image/jp2). Android's image pipeline decodes JP2 data
10
+ /// URIs natively, but iOS's <Image> does not — so the chip photo shows as a
11
+ /// placeholder on iOS. iOS's ImageIO *can* decode JPEG2000, so we round-trip
12
+ /// the JP2 to JPEG here and hand back base64 the JS <Image> can render.
13
+ @objc(ImageDecoderModule)
14
+ class ImageDecoderModule: NSObject {
15
+
16
+ @objc
17
+ static func requiresMainQueueSetup() -> Bool {
18
+ return false
19
+ }
20
+
21
+ @objc
22
+ func decodeJp2ToJpeg(_ base64Jp2: String,
23
+ resolver resolve: @escaping RCTPromiseResolveBlock,
24
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
25
+ guard let jp2Data = Data(base64Encoded: base64Jp2) else {
26
+ reject("INVALID_BASE64", "Could not decode base64 JP2 input", nil)
27
+ return
28
+ }
29
+
30
+ // ImageIO decodes JPEG2000 on iOS even though <Image> won't render it.
31
+ guard let image = UIImage(data: jp2Data) else {
32
+ reject("DECODE_FAILED", "ImageIO could not decode the JP2 data", nil)
33
+ return
34
+ }
35
+
36
+ guard let jpegData = image.jpegData(compressionQuality: 0.95) else {
37
+ reject("ENCODE_FAILED", "Could not re-encode decoded image as JPEG", nil)
38
+ return
39
+ }
40
+
41
+ resolve(jpegData.base64EncodedString())
42
+ }
43
+ }
@@ -418,7 +418,11 @@ const EIDScanner = ({
418
418
  style: styles.idCardPhotoContainer,
419
419
  children: /*#__PURE__*/_jsx(View, {
420
420
  style: styles.idCardPhotoFrame,
421
- children: documentFaceImage && (documentFaceImageMimeType === 'image/jpeg' || documentFaceImageMimeType === 'image/png') ? /*#__PURE__*/_jsx(Image, {
421
+ children: documentFaceImage && (documentFaceImageMimeType === 'image/jpeg' || documentFaceImageMimeType === 'image/png' ||
422
+ // Android's <Image> decodes a raw JP2 data URI; on iOS
423
+ // the chip JP2 is converted to JPEG before this point
424
+ // (native ImageIO), so iOS never reaches here as jp2.
425
+ documentFaceImageMimeType === 'image/jp2') ? /*#__PURE__*/_jsx(Image, {
422
426
  source: {
423
427
  uri: `data:${documentFaceImageMimeType};base64,${documentFaceImage}`
424
428
  },
@@ -261,12 +261,32 @@ export function handleIDBackFlow(mrzText, mrzFields, mrzValid, mrzStableAndValid
261
261
  *
262
262
  * PASSPORT: → COMPLETED (no back side)
263
263
  * ID CARD: → SCAN_ID_BACK
264
+ *
265
+ * Routing is PASSPORT-BIASED on purpose: a passport has no back side, so if we
266
+ * route it to SCAN_ID_BACK it deadlocks forever waiting for a back that will
267
+ * never appear. We therefore only go to SCAN_ID_BACK when there is POSITIVE
268
+ * evidence the document is an ID card (a parsed MRZ document code that is
269
+ * present and not 'P'). When the type is ambiguous — e.g. the passport MRZ was
270
+ * never read as 'P' so the front step locked `detectedDocumentType` as
271
+ * 'ID_FRONT' (the torch/tilt during hologram capture makes the MRZ unreadable),
272
+ * or no MRZ code survived at all — we COMPLETE rather than wait for a
273
+ * non-existent back side. A two-sided ID card always yields an 'I'/'A'/'C'
274
+ * (non-'P') MRZ code, so genuine ID cards still route to SCAN_ID_BACK.
264
275
  */
265
276
  export function getNextStepAfterHologram(detectedDocumentType, currentFrameDocType, mrzDocCode) {
266
- // Check multiple sources to determine if it's a passport
277
+ // Any positive passport signal done (no back side).
267
278
  const isPassport = detectedDocumentType === 'PASSPORT' || currentFrameDocType === 'PASSPORT' || mrzDocCode === 'P';
268
279
  if (isPassport) {
269
280
  return 'COMPLETED';
270
281
  }
271
- return 'SCAN_ID_BACK';
282
+
283
+ // Only continue to the back side when we have POSITIVE proof it's an ID card:
284
+ // a real MRZ document code that is present and not a passport. Without that
285
+ // proof, default to COMPLETED so a misclassified passport can't hang waiting
286
+ // for a back side that doesn't exist.
287
+ const hasIdCardMrzCode = !!mrzDocCode && mrzDocCode !== 'P';
288
+ if (hasIdCardMrzCode) {
289
+ return 'SCAN_ID_BACK';
290
+ }
291
+ return 'COMPLETED';
272
292
  }
@@ -1799,7 +1799,7 @@ const IdentityDocumentCamera = ({
1799
1799
  status === 'SCANNING' && allElementsDetected && elementsOutsideScanArea.length === 0 && !isBrightnessLow && !isFrameBlurry && styles.topZoneTextScanning
1800
1800
  // 5. Default (white) - aligning (not all detected OR elements outside scan area)
1801
1801
  ],
1802
- children: getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry, allElementsDetected, elementsOutsideScanArea, t, mrzReliable)
1802
+ children: getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry, allElementsDetected, elementsOutsideScanArea, t)
1803
1803
  })]
1804
1804
  }), /*#__PURE__*/_jsx(View, {
1805
1805
  style: styles.leftZone
@@ -78,17 +78,17 @@ export function transformBoundsToScreen(bounds, scale, offsetX, offsetY) {
78
78
  * Unified status message logic used by both voice guidance and render text.
79
79
  * Returns the appropriate i18n key arguments for the current scan state.
80
80
  */
81
- export function getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry, allElementsDetected, elementsOutsideScanArea, t, mrzReliable = false) {
81
+ export function getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry, allElementsDetected, elementsOutsideScanArea, t) {
82
82
  if (nextStep === 'COMPLETED') {
83
83
  return t('identityDocumentCamera.scanCompleted');
84
84
  }
85
85
 
86
- // MRZ reliably detected (stable consensus) the green "GO" message. Ranks
87
- // above the in-progress states so the user gets a clear positive signal the
88
- // moment the reading has settled, but never on an early/transient valid read.
89
- if (mrzReliable) {
90
- return t('identityDocumentCamera.mrzDetected');
91
- }
86
+ // NOTE: the "MRZ detected — hold steady" message was intentionally removed
87
+ // (along with its former `mrzReliable` parameter). User-facing guidance
88
+ // should only describe the SCAN STEP (front side, back side, hologram, etc.),
89
+ // not low-level MRZ-detection state. We fall through to the step-specific
90
+ // messages below.
91
+
92
92
  if (status === 'INCORRECT') {
93
93
  if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
94
94
  return t('identityDocumentCamera.alignIDFront');
@@ -12,6 +12,7 @@ import { Buffer } from 'buffer';
12
12
  import { InputStream } from "./java/inputStream.js";
13
13
  import { debugLog } from "../Libs/debug.utils.js";
14
14
  import { diagnostics } from "../Libs/diagnostics.js";
15
+ import { decodeJp2ToJpeg } from "../Libs/jp2Decode.js";
15
16
 
16
17
  /**
17
18
  * Extract an APDU status word (hex, e.g. "6982") from an error message if the
@@ -220,7 +221,7 @@ function pixelsToPng(pixels, width, height, channels) {
220
221
  // conversion" hang. A JS timeout cannot interrupt a synchronous call, so we must
221
222
  // gate BEFORE decoding. ~12 KB decodes fast; beyond that we keep the raw JP2.
222
223
  const MAX_JP2_DECODE_BYTES = 12 * 1024;
223
- function convertJP2IfNeeded(imageBuffer, mimeType) {
224
+ async function convertJP2IfNeeded(imageBuffer, mimeType) {
224
225
  if (mimeType !== 'image/jp2') {
225
226
  return {
226
227
  base64: imageBuffer.toString('base64'),
@@ -228,10 +229,21 @@ function convertJP2IfNeeded(imageBuffer, mimeType) {
228
229
  };
229
230
  }
230
231
 
232
+ // Prefer the native decoder (iOS ImageIO). It decodes JPEG2000 of any size
233
+ // without blocking the JS thread, so it both fixes the iOS preview (RN's
234
+ // <Image> can't render a raw image/jp2 data URI) and avoids the slow pure-JS
235
+ // decoder entirely. Returns null on Android / when unavailable → fall through.
236
+ const native = await decodeJp2ToJpeg(imageBuffer);
237
+ if (native) {
238
+ debugLog('EID', `[EID] JP2 decoded natively → ${native.mimeType} (${native.base64.length} base64 chars)`);
239
+ return native;
240
+ }
241
+
231
242
  // Too large to decode synchronously without freezing the UI — keep the raw
232
243
  // JP2. The face image is still captured (the verification backend / face-match
233
244
  // accept JPEG2000); only the on-screen preview can't render it. This lets the
234
- // NFC read COMPLETE instead of hanging.
245
+ // NFC read COMPLETE instead of hanging. (On Android the raw JP2 still renders
246
+ // in <Image>; on iOS the native path above normally handles it.)
235
247
  if (imageBuffer.length > MAX_JP2_DECODE_BYTES) {
236
248
  debugLog('EID', `[EID] JP2 face image is ${imageBuffer.length} bytes (> ${MAX_JP2_DECODE_BYTES}); skipping in-JS decode to avoid blocking, keeping raw JP2`);
237
249
  return {
@@ -642,7 +654,7 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
642
654
  const buffer = Buffer.alloc(imageLength);
643
655
  await imageInputStream.readBytesWithOffset(buffer, 0, imageLength);
644
656
  const rawMimeType = faceImageInfo.getMimeType();
645
- const converted = convertJP2IfNeeded(buffer, rawMimeType);
657
+ const converted = await convertJP2IfNeeded(buffer, rawMimeType);
646
658
  imageAsBase64 = converted.base64;
647
659
  mimeType = converted.mimeType;
648
660
  }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ import { NativeModules, Platform } from 'react-native';
4
+
5
+ /**
6
+ * Native JPEG2000 decoder bridge.
7
+ *
8
+ * The passport chip portrait (DG2) is frequently stored as JPEG2000
9
+ * (image/jp2). React Native's <Image> renders a JP2 data URI on Android (its
10
+ * Fresco/Skia pipeline decodes it) but NOT on iOS (UIImage-backed <Image>
11
+ * refuses the data URI), so the chip photo showed as a placeholder on iOS.
12
+ *
13
+ * iOS's ImageIO *can* decode JPEG2000, so the native `ImageDecoderModule`
14
+ * round-trips the JP2 to JPEG. We only need this on iOS; on Android the raw
15
+ * JP2 already renders, so there is no native counterpart and we no-op.
16
+ */
17
+
18
+ const ImageDecoder = NativeModules.ImageDecoderModule;
19
+
20
+ /**
21
+ * Decode a JPEG2000 buffer to JPEG using the native iOS decoder.
22
+ *
23
+ * Returns the JPEG bytes + mime type on success, or null when native decode is
24
+ * unavailable (Android, or the module isn't linked) or fails — callers should
25
+ * fall back to their existing behaviour (keep raw JP2).
26
+ */
27
+ export async function decodeJp2ToJpeg(jp2Buffer) {
28
+ if (Platform.OS !== 'ios' || !ImageDecoder?.decodeJp2ToJpeg) {
29
+ return null;
30
+ }
31
+ try {
32
+ const jpegBase64 = await ImageDecoder.decodeJp2ToJpeg(jp2Buffer.toString('base64'));
33
+ if (!jpegBase64) {
34
+ return null;
35
+ }
36
+ return {
37
+ base64: jpegBase64,
38
+ mimeType: 'image/jpeg'
39
+ };
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
@@ -2,4 +2,4 @@
2
2
 
3
3
  // This file is auto-generated. Do not edit manually.
4
4
  // Version is synced from package.json during build.
5
- export const SDK_VERSION = '1.488.0';
5
+ export const SDK_VERSION = '1.489.0';
@@ -1 +1 @@
1
- {"version":3,"file":"EIDScanner.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/EIDScanner.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAiBpD,UAAU,eAAe;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CACd,OAAO,EAAE,SAAS,EAClB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,KACtB,IAAI,CAAC;IACV,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CAC/C;AAED,QAAA,MAAM,UAAU,GAAI,6FAOjB,eAAe,4CAqwBjB,CAAC;AA4NF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"EIDScanner.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/EIDScanner.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAiBpD,UAAU,eAAe;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CACd,OAAO,EAAE,SAAS,EAClB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,KACtB,IAAI,CAAC;IACV,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CAC/C;AAED,QAAA,MAAM,UAAU,GAAI,6FAOjB,eAAe,4CAywBjB,CAAC;AA4NF,eAAe,UAAU,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"IdentityDocumentCamera.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.tsx"],"names":[],"mappings":"AAyEA,OAAO,KAAK,EACV,mBAAmB,EACnB,SAAS,EACT,2BAA2B,EAK5B,MAAM,gCAAgC,CAAC;AAGxC,YAAY,EAAE,mBAAmB,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAMnE,QAAA,MAAM,sBAAsB,GAAI,uDAI7B,2BAA2B,4CAojF7B,CAAC;AAiIF,eAAe,sBAAsB,CAAC"}
1
+ {"version":3,"file":"IdentityDocumentCamera.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.tsx"],"names":[],"mappings":"AAyEA,OAAO,KAAK,EACV,mBAAmB,EACnB,SAAS,EACT,2BAA2B,EAK5B,MAAM,gCAAgC,CAAC;AAGxC,YAAY,EAAE,mBAAmB,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAMnE,QAAA,MAAM,sBAAsB,GAAI,uDAI7B,2BAA2B,4CAmjF7B,CAAC;AAiIF,eAAe,sBAAsB,CAAC"}
@@ -83,6 +83,17 @@ export declare function handleIDBackFlow(mrzText: string | null, mrzFields: MRZF
83
83
  *
84
84
  * PASSPORT: → COMPLETED (no back side)
85
85
  * ID CARD: → SCAN_ID_BACK
86
+ *
87
+ * Routing is PASSPORT-BIASED on purpose: a passport has no back side, so if we
88
+ * route it to SCAN_ID_BACK it deadlocks forever waiting for a back that will
89
+ * never appear. We therefore only go to SCAN_ID_BACK when there is POSITIVE
90
+ * evidence the document is an ID card (a parsed MRZ document code that is
91
+ * present and not 'P'). When the type is ambiguous — e.g. the passport MRZ was
92
+ * never read as 'P' so the front step locked `detectedDocumentType` as
93
+ * 'ID_FRONT' (the torch/tilt during hologram capture makes the MRZ unreadable),
94
+ * or no MRZ code survived at all — we COMPLETE rather than wait for a
95
+ * non-existent back side. A two-sided ID card always yields an 'I'/'A'/'C'
96
+ * (non-'P') MRZ code, so genuine ID cards still route to SCAN_ID_BACK.
86
97
  */
87
98
  export declare function getNextStepAfterHologram(detectedDocumentType: 'ID_FRONT' | 'ID_BACK' | 'PASSPORT' | 'UNKNOWN', currentFrameDocType: 'ID_FRONT' | 'ID_BACK' | 'PASSPORT' | 'UNKNOWN', mrzDocCode: string | undefined): 'COMPLETED' | 'SCAN_ID_BACK';
88
99
  //# sourceMappingURL=IdentityDocumentCamera.flows.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IdentityDocumentCamera.flows.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.flows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAUpD,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EACP,mBAAmB,GACnB,cAAc,GACd,qBAAqB,GACrB,sBAAsB,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EACP,mBAAmB,GACnB,oBAAoB,GACpB,qBAAqB,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,cAAc,GAAG,kBAAkB,GAAG,sBAAsB,CAAC;CAC3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,iBAAiB,EAAE,OAAO,EAC1B,WAAW,EAAE,OAAO,EACpB,iBAAiB,EAAE,OAAO,EAC1B,YAAY,EAAE,OAAO,GACpB,kBAAkB,CAgEpB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,UAAU,EAAE,MAAM,GACjB,iBAAiB,CA0EnB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,QAAQ,EAAE,OAAO,EACjB,iBAAiB,EAAE,OAAO,EAC1B,iBAAiB,EAAE,OAAO,EAC1B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,WAAW,EAAE,OAAO,GACnB,gBAAgB,CA2DlB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,oBAAoB,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,EACrE,mBAAmB,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,EACpE,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,WAAW,GAAG,cAAc,CAY9B"}
1
+ {"version":3,"file":"IdentityDocumentCamera.flows.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.flows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAUpD,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EACP,mBAAmB,GACnB,cAAc,GACd,qBAAqB,GACrB,sBAAsB,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EACP,mBAAmB,GACnB,oBAAoB,GACpB,qBAAqB,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,cAAc,GAAG,kBAAkB,GAAG,sBAAsB,CAAC;CAC3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,iBAAiB,EAAE,OAAO,EAC1B,WAAW,EAAE,OAAO,EACpB,iBAAiB,EAAE,OAAO,EAC1B,YAAY,EAAE,OAAO,GACpB,kBAAkB,CAgEpB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,UAAU,EAAE,MAAM,GACjB,iBAAiB,CA0EnB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,QAAQ,EAAE,OAAO,EACjB,iBAAiB,EAAE,OAAO,EAC1B,iBAAiB,EAAE,OAAO,EAC1B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,WAAW,EAAE,OAAO,GACnB,gBAAgB,CA2DlB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,oBAAoB,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,EACrE,mBAAmB,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,EACpE,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,WAAW,GAAG,cAAc,CAqB9B"}
@@ -42,7 +42,7 @@ export declare function transformBoundsToScreen(bounds: {
42
42
  * Unified status message logic used by both voice guidance and render text.
43
43
  * Returns the appropriate i18n key arguments for the current scan state.
44
44
  */
45
- export declare function getStatusMessage(nextStep: ScanStep, status: ScanStatus, detectedDocumentType: DocumentType, isBrightnessLow: boolean, isFrameBlurry: boolean, allElementsDetected: boolean, elementsOutsideScanArea: string[], t: (key: string, params?: Record<string, unknown>) => string, mrzReliable?: boolean): string;
45
+ export declare function getStatusMessage(nextStep: ScanStep, status: ScanStatus, detectedDocumentType: DocumentType, isBrightnessLow: boolean, isFrameBlurry: boolean, allElementsDetected: boolean, elementsOutsideScanArea: string[], t: (key: string, params?: Record<string, unknown>) => string): string;
46
46
  /**
47
47
  * Calculate angle from two points in degrees
48
48
  */
@@ -1 +1 @@
1
- {"version":3,"file":"IdentityDocumentCamera.utils.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"IdentityDocumentCamera.utils.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.utils.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"eidReader.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/EIDReader/eidReader.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AA0X7C,QAAA,MAAM,SAAS,GACb,gBAAgB,MAAM,EACtB,aAAa,MAAM,EACnB,cAAc,MAAM,EACpB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,KAC5C,OAAO,CACN;IACE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GACD,SAAS,CA+XZ,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"eidReader.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/EIDReader/eidReader.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAyY7C,QAAA,MAAM,SAAS,GACb,gBAAgB,MAAM,EACtB,aAAa,MAAM,EACnB,cAAc,MAAM,EACpB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,KAC5C,OAAO,CACN;IACE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GACD,SAAS,CA+XZ,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { Buffer } from 'buffer';
2
+ /**
3
+ * Decode a JPEG2000 buffer to JPEG using the native iOS decoder.
4
+ *
5
+ * Returns the JPEG bytes + mime type on success, or null when native decode is
6
+ * unavailable (Android, or the module isn't linked) or fails — callers should
7
+ * fall back to their existing behaviour (keep raw JP2).
8
+ */
9
+ export declare function decodeJp2ToJpeg(jp2Buffer: Buffer): Promise<{
10
+ base64: string;
11
+ mimeType: string;
12
+ } | null>;
13
+ //# sourceMappingURL=jp2Decode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jp2Decode.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/jp2Decode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAsBhC;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAetD"}
@@ -1,2 +1,2 @@
1
- export declare const SDK_VERSION = "1.488.0";
1
+ export declare const SDK_VERSION = "1.489.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trustchex/react-native-sdk",
3
- "version": "1.488.0",
3
+ "version": "1.489.0",
4
4
  "description": "Trustchex mobile app react native SDK for android or ios devices",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -602,7 +602,11 @@ const EIDScanner = ({
602
602
  <View style={styles.idCardPhotoFrame}>
603
603
  {documentFaceImage &&
604
604
  (documentFaceImageMimeType === 'image/jpeg' ||
605
- documentFaceImageMimeType === 'image/png') ? (
605
+ documentFaceImageMimeType === 'image/png' ||
606
+ // Android's <Image> decodes a raw JP2 data URI; on iOS
607
+ // the chip JP2 is converted to JPEG before this point
608
+ // (native ImageIO), so iOS never reaches here as jp2.
609
+ documentFaceImageMimeType === 'image/jp2') ? (
606
610
  <Image
607
611
  source={{
608
612
  uri: `data:${documentFaceImageMimeType};base64,${documentFaceImage}`,
@@ -325,13 +325,24 @@ export function handleIDBackFlow(
325
325
  *
326
326
  * PASSPORT: → COMPLETED (no back side)
327
327
  * ID CARD: → SCAN_ID_BACK
328
+ *
329
+ * Routing is PASSPORT-BIASED on purpose: a passport has no back side, so if we
330
+ * route it to SCAN_ID_BACK it deadlocks forever waiting for a back that will
331
+ * never appear. We therefore only go to SCAN_ID_BACK when there is POSITIVE
332
+ * evidence the document is an ID card (a parsed MRZ document code that is
333
+ * present and not 'P'). When the type is ambiguous — e.g. the passport MRZ was
334
+ * never read as 'P' so the front step locked `detectedDocumentType` as
335
+ * 'ID_FRONT' (the torch/tilt during hologram capture makes the MRZ unreadable),
336
+ * or no MRZ code survived at all — we COMPLETE rather than wait for a
337
+ * non-existent back side. A two-sided ID card always yields an 'I'/'A'/'C'
338
+ * (non-'P') MRZ code, so genuine ID cards still route to SCAN_ID_BACK.
328
339
  */
329
340
  export function getNextStepAfterHologram(
330
341
  detectedDocumentType: 'ID_FRONT' | 'ID_BACK' | 'PASSPORT' | 'UNKNOWN',
331
342
  currentFrameDocType: 'ID_FRONT' | 'ID_BACK' | 'PASSPORT' | 'UNKNOWN',
332
343
  mrzDocCode: string | undefined
333
344
  ): 'COMPLETED' | 'SCAN_ID_BACK' {
334
- // Check multiple sources to determine if it's a passport
345
+ // Any positive passport signal done (no back side).
335
346
  const isPassport =
336
347
  detectedDocumentType === 'PASSPORT' ||
337
348
  currentFrameDocType === 'PASSPORT' ||
@@ -341,5 +352,14 @@ export function getNextStepAfterHologram(
341
352
  return 'COMPLETED';
342
353
  }
343
354
 
344
- return 'SCAN_ID_BACK';
355
+ // Only continue to the back side when we have POSITIVE proof it's an ID card:
356
+ // a real MRZ document code that is present and not a passport. Without that
357
+ // proof, default to COMPLETED so a misclassified passport can't hang waiting
358
+ // for a back side that doesn't exist.
359
+ const hasIdCardMrzCode = !!mrzDocCode && mrzDocCode !== 'P';
360
+ if (hasIdCardMrzCode) {
361
+ return 'SCAN_ID_BACK';
362
+ }
363
+
364
+ return 'COMPLETED';
345
365
  }
@@ -2624,8 +2624,7 @@ const IdentityDocumentCamera = ({
2624
2624
  isFrameBlurry,
2625
2625
  allElementsDetected,
2626
2626
  elementsOutsideScanArea,
2627
- t,
2628
- mrzReliable
2627
+ t
2629
2628
  )}
2630
2629
  </AnimatedText>
2631
2630
  </View>
@@ -96,19 +96,17 @@ export function getStatusMessage(
96
96
  isFrameBlurry: boolean,
97
97
  allElementsDetected: boolean,
98
98
  elementsOutsideScanArea: string[],
99
- t: (key: string, params?: Record<string, unknown>) => string,
100
- mrzReliable: boolean = false
99
+ t: (key: string, params?: Record<string, unknown>) => string
101
100
  ): string {
102
101
  if (nextStep === 'COMPLETED') {
103
102
  return t('identityDocumentCamera.scanCompleted');
104
103
  }
105
104
 
106
- // MRZ reliably detected (stable consensus) the green "GO" message. Ranks
107
- // above the in-progress states so the user gets a clear positive signal the
108
- // moment the reading has settled, but never on an early/transient valid read.
109
- if (mrzReliable) {
110
- return t('identityDocumentCamera.mrzDetected');
111
- }
105
+ // NOTE: the "MRZ detected — hold steady" message was intentionally removed
106
+ // (along with its former `mrzReliable` parameter). User-facing guidance
107
+ // should only describe the SCAN STEP (front side, back side, hologram, etc.),
108
+ // not low-level MRZ-detection state. We fall through to the step-specific
109
+ // messages below.
112
110
 
113
111
  if (status === 'INCORRECT') {
114
112
  if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
@@ -12,6 +12,7 @@ import { MRZInfo } from './lds/icao/mrzInfo';
12
12
  import { InputStream } from './java/inputStream';
13
13
  import { debugLog } from '../Libs/debug.utils';
14
14
  import { diagnostics } from '../Libs/diagnostics';
15
+ import { decodeJp2ToJpeg } from '../Libs/jp2Decode';
15
16
 
16
17
  /**
17
18
  * Extract an APDU status word (hex, e.g. "6982") from an error message if the
@@ -239,18 +240,32 @@ function pixelsToPng(
239
240
  // gate BEFORE decoding. ~12 KB decodes fast; beyond that we keep the raw JP2.
240
241
  const MAX_JP2_DECODE_BYTES = 12 * 1024;
241
242
 
242
- function convertJP2IfNeeded(
243
+ async function convertJP2IfNeeded(
243
244
  imageBuffer: Buffer,
244
245
  mimeType: string
245
- ): { base64: string; mimeType: string } {
246
+ ): Promise<{ base64: string; mimeType: string }> {
246
247
  if (mimeType !== 'image/jp2') {
247
248
  return { base64: imageBuffer.toString('base64'), mimeType };
248
249
  }
249
250
 
251
+ // Prefer the native decoder (iOS ImageIO). It decodes JPEG2000 of any size
252
+ // without blocking the JS thread, so it both fixes the iOS preview (RN's
253
+ // <Image> can't render a raw image/jp2 data URI) and avoids the slow pure-JS
254
+ // decoder entirely. Returns null on Android / when unavailable → fall through.
255
+ const native = await decodeJp2ToJpeg(imageBuffer);
256
+ if (native) {
257
+ debugLog(
258
+ 'EID',
259
+ `[EID] JP2 decoded natively → ${native.mimeType} (${native.base64.length} base64 chars)`
260
+ );
261
+ return native;
262
+ }
263
+
250
264
  // Too large to decode synchronously without freezing the UI — keep the raw
251
265
  // JP2. The face image is still captured (the verification backend / face-match
252
266
  // accept JPEG2000); only the on-screen preview can't render it. This lets the
253
- // NFC read COMPLETE instead of hanging.
267
+ // NFC read COMPLETE instead of hanging. (On Android the raw JP2 still renders
268
+ // in <Image>; on iOS the native path above normally handles it.)
254
269
  if (imageBuffer.length > MAX_JP2_DECODE_BYTES) {
255
270
  debugLog(
256
271
  'EID',
@@ -739,7 +754,7 @@ const eidReader = async (
739
754
  const buffer = Buffer.alloc(imageLength);
740
755
  await imageInputStream.readBytesWithOffset(buffer, 0, imageLength);
741
756
  const rawMimeType = faceImageInfo.getMimeType();
742
- const converted = convertJP2IfNeeded(buffer, rawMimeType);
757
+ const converted = await convertJP2IfNeeded(buffer, rawMimeType);
743
758
  imageAsBase64 = converted.base64;
744
759
  mimeType = converted.mimeType;
745
760
  }
@@ -0,0 +1,48 @@
1
+ import { NativeModules, Platform } from 'react-native';
2
+ import { Buffer } from 'buffer';
3
+
4
+ /**
5
+ * Native JPEG2000 decoder bridge.
6
+ *
7
+ * The passport chip portrait (DG2) is frequently stored as JPEG2000
8
+ * (image/jp2). React Native's <Image> renders a JP2 data URI on Android (its
9
+ * Fresco/Skia pipeline decodes it) but NOT on iOS (UIImage-backed <Image>
10
+ * refuses the data URI), so the chip photo showed as a placeholder on iOS.
11
+ *
12
+ * iOS's ImageIO *can* decode JPEG2000, so the native `ImageDecoderModule`
13
+ * round-trips the JP2 to JPEG. We only need this on iOS; on Android the raw
14
+ * JP2 already renders, so there is no native counterpart and we no-op.
15
+ */
16
+ interface ImageDecoderNativeModule {
17
+ decodeJp2ToJpeg(base64Jp2: string): Promise<string>;
18
+ }
19
+
20
+ const ImageDecoder = (
21
+ NativeModules as { ImageDecoderModule?: ImageDecoderNativeModule }
22
+ ).ImageDecoderModule;
23
+
24
+ /**
25
+ * Decode a JPEG2000 buffer to JPEG using the native iOS decoder.
26
+ *
27
+ * Returns the JPEG bytes + mime type on success, or null when native decode is
28
+ * unavailable (Android, or the module isn't linked) or fails — callers should
29
+ * fall back to their existing behaviour (keep raw JP2).
30
+ */
31
+ export async function decodeJp2ToJpeg(
32
+ jp2Buffer: Buffer
33
+ ): Promise<{ base64: string; mimeType: string } | null> {
34
+ if (Platform.OS !== 'ios' || !ImageDecoder?.decodeJp2ToJpeg) {
35
+ return null;
36
+ }
37
+ try {
38
+ const jpegBase64 = await ImageDecoder.decodeJp2ToJpeg(
39
+ jp2Buffer.toString('base64')
40
+ );
41
+ if (!jpegBase64) {
42
+ return null;
43
+ }
44
+ return { base64: jpegBase64, mimeType: 'image/jpeg' };
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
2
  // Version is synced from package.json during build.
3
- export const SDK_VERSION = '1.488.0';
3
+ export const SDK_VERSION = '1.489.0';