@trustchex/react-native-sdk 1.501.1 → 1.503.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.
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import React, { useContext, useEffect, useRef, useState } from 'react';
4
- import { SafeAreaView, View, Text, TextInput, StyleSheet, ScrollView, Image, TouchableOpacity } from 'react-native';
4
+ import { SafeAreaView, View, Text, StyleSheet, ScrollView, Image, TouchableOpacity } from 'react-native';
5
5
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { TrustchexCamera } from "../../Shared/Components/TrustchexCamera.js";
@@ -18,7 +18,7 @@ import { useKeepAwake } from "../../Shared/Libs/native-keep-awake.utils.js";
18
18
  * captures a proof document (residence cert / utility bill ≤3 months / public-
19
19
  * authority doc). The address is verified later by an operator (manual review).
20
20
  */
21
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
21
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
22
22
  const AddressCaptureScreen = () => {
23
23
  useKeepAwake();
24
24
  useStatusBarWhiteBackground();
@@ -30,16 +30,14 @@ const AddressCaptureScreen = () => {
30
30
  } = useTranslation();
31
31
  const insets = useSafeAreaInsets();
32
32
 
33
- // Pre-fill from the chip's DG11 address when the NFC read provided one.
34
- const chipAddress = appContext.lastDocumentRead?.document.address ?? '';
35
- const [addressText, setAddressText] = useState(chipAddress);
36
- const [fromChip] = useState(!!chipAddress);
33
+ // The address text is NOT collected here — the chip's DG11 address (read on
34
+ // the NFC step) is the source of truth and is always sent. This step only
35
+ // collects a PROOF-DOCUMENT photo for the operator's manual review. We show
36
+ // the chip address read-only for context when available.
37
+ const chipAddress = appContext.identificationInfo.address?.addressText ?? appContext.lastDocumentRead?.document.address ?? '';
37
38
  const [proofImage, setProofImage] = useState();
38
39
  const [isCameraOpen, setIsCameraOpen] = useState(false);
39
40
  const captureNextFrame = useRef(false);
40
-
41
- // Whether the proof document is required is configurable per workflow step.
42
- const proofRequired = appContext.currentWorkflowStep?.data?.addressProofRequired !== false;
43
41
  useEffect(() => {
44
42
  trackVerificationStart('ADDRESS_CAPTURE');
45
43
  }, []);
@@ -52,11 +50,17 @@ const AddressCaptureScreen = () => {
52
50
  setIsCameraOpen(false);
53
51
  }
54
52
  };
55
- const canContinue = addressText.trim().length > 0 && (!proofRequired || !!proofImage);
53
+
54
+ // A proof photo is the whole point of this step.
55
+ const canContinue = !!proofImage;
56
56
  const onContinue = () => {
57
+ // Attach the proof to the existing (chip-sourced) address without changing
58
+ // the address text or fromChip flag. If for some reason no address object
59
+ // exists yet, keep whatever chip text we have for display continuity.
60
+ const existing = appContext.identificationInfo.address;
57
61
  appContext.identificationInfo.address = {
58
- addressText: addressText.trim(),
59
- fromChip,
62
+ addressText: existing?.addressText ?? chipAddress,
63
+ fromChip: existing?.fromChip ?? !!chipAddress,
60
64
  proofImage,
61
65
  proofImageMimeType: proofImage ? 'image/jpeg' : undefined
62
66
  };
@@ -103,20 +107,21 @@ const AddressCaptureScreen = () => {
103
107
  }), /*#__PURE__*/_jsx(Text, {
104
108
  style: styles.subtitle,
105
109
  children: t('addressCaptureScreen.subtitle')
106
- }), fromChip ? /*#__PURE__*/_jsx(Text, {
107
- style: styles.chipHint,
108
- children: t('addressCaptureScreen.prefilledFromChip')
110
+ }), chipAddress ? /*#__PURE__*/_jsxs(_Fragment, {
111
+ children: [/*#__PURE__*/_jsx(Text, {
112
+ style: styles.label,
113
+ children: t('addressCaptureScreen.addressLabel')
114
+ }), /*#__PURE__*/_jsx(View, {
115
+ style: styles.readonlyAddress,
116
+ children: /*#__PURE__*/_jsx(Text, {
117
+ style: styles.readonlyAddressText,
118
+ children: chipAddress
119
+ })
120
+ }), /*#__PURE__*/_jsx(Text, {
121
+ style: styles.chipHint,
122
+ children: t('addressCaptureScreen.prefilledFromChip')
123
+ })]
109
124
  }) : null, /*#__PURE__*/_jsx(Text, {
110
- style: styles.label,
111
- children: t('addressCaptureScreen.addressLabel')
112
- }), /*#__PURE__*/_jsx(TextInput, {
113
- style: styles.input,
114
- value: addressText,
115
- onChangeText: setAddressText,
116
- placeholder: t('addressCaptureScreen.addressPlaceholder'),
117
- multiline: true,
118
- textAlignVertical: "top"
119
- }), /*#__PURE__*/_jsx(Text, {
120
125
  style: styles.label,
121
126
  children: t('addressCaptureScreen.proofLabel')
122
127
  }), /*#__PURE__*/_jsx(Text, {
@@ -177,12 +182,15 @@ const styles = StyleSheet.create({
177
182
  marginTop: 16,
178
183
  marginBottom: 6
179
184
  },
180
- input: {
185
+ readonlyAddress: {
181
186
  borderWidth: 1,
182
- borderColor: '#CCC',
187
+ borderColor: '#E0E0E0',
188
+ backgroundColor: '#F7F7F7',
183
189
  borderRadius: 8,
184
190
  padding: 12,
185
- minHeight: 88,
191
+ minHeight: 56
192
+ },
193
+ readonlyAddressText: {
186
194
  fontSize: 15,
187
195
  color: '#111'
188
196
  },
@@ -104,13 +104,16 @@ const IdentityDocumentEIDScanningScreen = () => {
104
104
  // Passive Auth artifacts for server-side verification.
105
105
  appContext.identificationInfo.scannedDocument.paSodBase64 = appContext.lastDocumentRead?.passiveAuth?.sodBase64 ?? null;
106
106
  appContext.identificationInfo.scannedDocument.paIntegrityStatus = appContext.lastDocumentRead?.passiveAuth?.integrityStatus ?? null;
107
- // MASAK 4/C(1)(d): if the chip's DG11 carried an address and no
108
- // address was captured otherwise, seed it from the chip so it is
109
- // sent to the backend (fromChip=true). A later ADDRESS_CAPTURE
110
- // step can still let the user confirm/edit it.
107
+ // MASAK 4/C(1)(d): the chip's DG11 address is the source of truth
108
+ // and is ALWAYS sent on the NFC read independent of any
109
+ // ADDRESS_CAPTURE step. The address text is never user-edited; an
110
+ // ADDRESS_CAPTURE step only ADDS a proof-document photo for manual
111
+ // operator review (it does not change this address text).
111
112
  const chipAddress = appContext.lastDocumentRead?.document.address;
112
- if (chipAddress && !appContext.identificationInfo.address) {
113
+ if (chipAddress) {
113
114
  appContext.identificationInfo.address = {
115
+ // Preserve any proof image already captured for this id.
116
+ ...appContext.identificationInfo.address,
114
117
  addressText: chipAddress,
115
118
  fromChip: true
116
119
  };
@@ -110,6 +110,11 @@ const VerificationSessionCheckScreen = () => {
110
110
  getSessionRef.current(sid).then(session => {
111
111
  const ctx = appContextRef.current;
112
112
  ctx.workflowSteps = session.workflowSteps;
113
+ // Start the loaded workflow from its FIRST step. Without clearing this,
114
+ // a currentWorkflowStep left over from a prior flow makes the navigator
115
+ // resolve "next" past the last step and jump straight to the result
116
+ // screen (which uploads + completes the session immediately).
117
+ ctx.currentWorkflowStep = undefined;
113
118
  if (session.branding) {
114
119
  ctx.branding = ctx.branding || session.branding;
115
120
  }
@@ -200,6 +205,9 @@ const VerificationSessionCheckScreen = () => {
200
205
  }
201
206
  const session = await getSession(sessionResponse.id, isDemoMode);
202
207
  appContext.workflowSteps = session?.workflowSteps;
208
+ // Start from the first step (see note above) — clear
209
+ // any stale step so we don't jump straight to result.
210
+ appContext.currentWorkflowStep = undefined;
203
211
  if (session?.branding) {
204
212
  appContext.branding = appContext.branding || session.branding;
205
213
  }
@@ -29,19 +29,21 @@ const QrCodeScannerCamera = ({
29
29
  const lastScanTime = useRef(0);
30
30
  const hasScanned = useRef(false);
31
31
  const isInScanningFrame = (boundingBox, width, height) => {
32
- // Match UI scanArea: top: '35%', left: '3%', right: '3%', bottom: '35%'
33
- // Add 2% margin buffer to ensure code is comfortably inside frame
34
- const marginBuffer = 0.02;
32
+ // Match the UI scanArea (top/bottom 35%, left/right 3%) but accept a QR
33
+ // whose CENTER falls inside it rather than requiring the WHOLE bounding box.
34
+ // Requiring full containment silently rejected perfectly readable codes that
35
+ // were merely close to an edge or slightly larger than the band — the most
36
+ // common "QR won't scan" failure. The center test keeps the scan scoped to
37
+ // the framed area without being brittle about size/position.
35
38
  const scanningFrame = {
36
- left: width * (0.03 + marginBuffer),
37
- top: height * (0.35 + marginBuffer),
38
- right: width * (0.97 - marginBuffer),
39
- bottom: height * (0.65 - marginBuffer)
39
+ left: width * 0.03,
40
+ top: height * 0.35,
41
+ right: width * 0.97,
42
+ bottom: height * 0.65
40
43
  };
41
-
42
- // Check if entire bounding box is within scanning frame (with margin buffer)
43
- const isInFrame = boundingBox.left >= scanningFrame.left && boundingBox.top >= scanningFrame.top && boundingBox.right <= scanningFrame.right && boundingBox.bottom <= scanningFrame.bottom;
44
- return isInFrame;
44
+ const centerX = (boundingBox.left + boundingBox.right) / 2;
45
+ const centerY = (boundingBox.top + boundingBox.bottom) / 2;
46
+ return centerX >= scanningFrame.left && centerX <= scanningFrame.right && centerY >= scanningFrame.top && centerY <= scanningFrame.bottom;
45
47
  };
46
48
  const handleFrame = useCallback(async event => {
47
49
  if (hasScanned.current) return;
@@ -55,13 +57,13 @@ const QrCodeScannerCamera = ({
55
57
  frame
56
58
  } = event.nativeEvent;
57
59
  try {
58
- // Barcodes are already detected natively — read directly from frame
60
+ // Barcodes are already detected natively — read directly from frame.
61
+ // Accept the first code that is readable AND framed; don't bail just
62
+ // because more than one code happens to be in view.
59
63
  const barcodes = frame.barcodes;
60
- if (!barcodes || barcodes.length !== 1) return;
61
- const code = barcodes[0];
62
-
63
- // Use frame checking on both platforms for consistent behavior
64
- if (code.boundingBox && isInScanningFrame(code.boundingBox, frame.width, frame.height) && code.rawValue) {
64
+ if (!barcodes || barcodes.length === 0) return;
65
+ const code = barcodes.find(b => b.rawValue && b.boundingBox && isInScanningFrame(b.boundingBox, frame.width, frame.height));
66
+ if (code?.rawValue) {
65
67
  hasScanned.current = true;
66
68
  onQrCodeScanned(code.rawValue);
67
69
  }
@@ -34,12 +34,32 @@ const handleDeepLink = ({
34
34
  sessionId = segments[i + 1] ?? '';
35
35
  debugLog('handleDeepLink', 'Found sessionId:', sessionId);
36
36
  } else if (segments[i] === 'app-url') {
37
- const host = segments[i + 1] ?? '';
37
+ // The app-url value can be EITHER a bare host (`app-url/host[:port]`) OR a
38
+ // full embedded URL (`app-url/https://host/...`). The embedded form splits
39
+ // across multiple segments because the inner `https://` contains a `//`,
40
+ // e.g. ['app-url', 'https:', '', 'host', ...] — so segments[i+1] is the
41
+ // inner scheme, not the host. Detect that and derive the host from the
42
+ // first non-empty segment after the inner scheme.
43
+ let host = '';
44
+ let embeddedScheme = '';
45
+ const next = segments[i + 1] ?? '';
46
+ const schemeMatch = next.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):$/);
47
+ if (schemeMatch) {
48
+ // Embedded-URL form: skip the inner scheme + the empty segment from `//`.
49
+ embeddedScheme = schemeMatch[1].toLowerCase();
50
+ let j = i + 2;
51
+ while (j < segments.length && segments[j] === '') j += 1;
52
+ host = segments[j] ?? '';
53
+ } else {
54
+ // Bare-host form.
55
+ host = next;
56
+ }
38
57
  if (!HOST_PATTERN.test(host)) {
39
58
  logWarn('handleDeepLink', 'Rejected invalid app-url host');
40
59
  continue;
41
60
  }
42
- const hostScheme = scheme === 'http' ? 'http' : 'https';
61
+ // Prefer the embedded URL's own scheme; otherwise inherit the link's.
62
+ const hostScheme = embeddedScheme || (scheme === 'http' ? 'http' : 'https');
43
63
  if (!isAllowedScheme(hostScheme)) {
44
64
  logWarn('handleDeepLink', 'Rejected non-HTTPS app-url');
45
65
  continue;
@@ -95,9 +95,9 @@ export default {
95
95
  'eidScannerScreen.nfcAuthFailed': 'Could not establish a secure connection to the chip. Make sure the document number, date of birth and expiry date are correct, then try again.',
96
96
  'eidScannerScreen.nfcTagLost': 'Connection to the document was lost. Keep the document still against the device and try again.',
97
97
  'eidScannerScreen.nfcTimeout': 'Reading the chip timed out. Hold the document steady against the device and try again.',
98
- 'addressCaptureScreen.title': 'Confirm your address',
99
- 'addressCaptureScreen.subtitle': 'Enter your current residential address and provide a supporting document.',
100
- 'addressCaptureScreen.prefilledFromChip': 'We pre-filled your address from your document chip. Please review and correct it if needed.',
98
+ 'addressCaptureScreen.title': 'Proof of address',
99
+ 'addressCaptureScreen.subtitle': 'Take a clear photo of a document that shows your address. It will be reviewed by an operator.',
100
+ 'addressCaptureScreen.prefilledFromChip': 'This address was read from your document chip and is used as-is.',
101
101
  'addressCaptureScreen.addressLabel': 'Address',
102
102
  'addressCaptureScreen.addressPlaceholder': 'Street, number, city, country',
103
103
  'addressCaptureScreen.proofLabel': 'Proof of address',
@@ -95,9 +95,9 @@ export default {
95
95
  'eidScannerScreen.nfcAuthFailed': 'Çip ile güvenli bağlantı kurulamadı. Belge numarası, doğum tarihi ve son geçerlilik tarihinin doğru olduğundan emin olup tekrar deneyin.',
96
96
  'eidScannerScreen.nfcTagLost': 'Belge ile bağlantı kesildi. Belgeyi cihaza sabit tutun ve tekrar deneyin.',
97
97
  'eidScannerScreen.nfcTimeout': 'Çip okuması zaman aşımına uğradı. Belgeyi cihaza sabit tutup tekrar deneyin.',
98
- 'addressCaptureScreen.title': 'Adresinizi doğrulayın',
99
- 'addressCaptureScreen.subtitle': 'Güncel yerleşim adresinizi girin ve destekleyici bir belge sağlayın.',
100
- 'addressCaptureScreen.prefilledFromChip': 'Adresiniz belge yongasından önceden dolduruldu. Lütfen kontrol edin ve gerekirse düzeltin.',
98
+ 'addressCaptureScreen.title': 'Adres kanıtı',
99
+ 'addressCaptureScreen.subtitle': 'Adresinizi gösteren bir belgenin net bir fotoğrafını çekin. Bir operatör tarafından incelenecektir.',
100
+ 'addressCaptureScreen.prefilledFromChip': 'Bu adres belge yongasından okundu ve olduğu gibi kullanılır.',
101
101
  'addressCaptureScreen.addressLabel': 'Adres',
102
102
  'addressCaptureScreen.addressPlaceholder': 'Cadde, numara, şehir, ülke',
103
103
  'addressCaptureScreen.proofLabel': 'Adres kanıtı',
@@ -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.501.1';
5
+ export const SDK_VERSION = '1.503.0';
@@ -1 +1 @@
1
- {"version":3,"file":"AddressCaptureScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/AddressCaptureScreen.tsx"],"names":[],"mappings":"AA8BA;;;;;GAKG;AACH,QAAA,MAAM,oBAAoB,+CA6HzB,CAAC;AAkCF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"AddressCaptureScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/AddressCaptureScreen.tsx"],"names":[],"mappings":"AA6BA;;;;;GAKG;AACH,QAAA,MAAM,oBAAoB,+CA8HzB,CAAC;AAkCF,eAAe,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"IdentityDocumentEIDScanningScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx"],"names":[],"mappings":"AAsBA,QAAA,MAAM,iCAAiC,+CAuPtC,CAAC;AAoBF,eAAe,iCAAiC,CAAC"}
1
+ {"version":3,"file":"IdentityDocumentEIDScanningScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx"],"names":[],"mappings":"AAsBA,QAAA,MAAM,iCAAiC,+CA0PtC,CAAC;AAoBF,eAAe,iCAAiC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"VerificationSessionCheckScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Static/VerificationSessionCheckScreen.tsx"],"names":[],"mappings":"AAkDA,QAAA,MAAM,8BAA8B,+CAmUnC,CAAC;AAiHF,eAAe,8BAA8B,CAAC"}
1
+ {"version":3,"file":"VerificationSessionCheckScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Static/VerificationSessionCheckScreen.tsx"],"names":[],"mappings":"AAkDA,QAAA,MAAM,8BAA8B,+CA2UnC,CAAC;AAiHF,eAAe,8BAA8B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"QrCodeScannerCamera.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/QrCodeScannerCamera.tsx"],"names":[],"mappings":"AA0BA,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,mBAAmB,GAAI,+BAG1B,wBAAwB,4CA6K1B,CAAC;AAwGF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"QrCodeScannerCamera.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/QrCodeScannerCamera.tsx"],"names":[],"mappings":"AA0BA,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,mBAAmB,GAAI,+BAG1B,wBAAwB,4CAmL1B,CAAC;AAwGF,eAAe,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"deeplink.utils.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/deeplink.utils.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAGxC,CAAC;AAEF,QAAA,MAAM,cAAc,GAAI,SAAS;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,aA4C/C,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"deeplink.utils.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/deeplink.utils.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAGxC,CAAC;AAEF,QAAA,MAAM,cAAc,GAAI,SAAS;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,aAkE/C,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare const SDK_VERSION = "1.501.1";
1
+ export declare const SDK_VERSION = "1.503.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.501.1",
3
+ "version": "1.503.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",
@@ -3,7 +3,6 @@ import {
3
3
  SafeAreaView,
4
4
  View,
5
5
  Text,
6
- TextInput,
7
6
  StyleSheet,
8
7
  ScrollView,
9
8
  Image,
@@ -44,20 +43,19 @@ const AddressCaptureScreen = () => {
44
43
  const { t } = useTranslation();
45
44
  const insets = useSafeAreaInsets();
46
45
 
47
- // Pre-fill from the chip's DG11 address when the NFC read provided one.
46
+ // The address text is NOT collected here — the chip's DG11 address (read on
47
+ // the NFC step) is the source of truth and is always sent. This step only
48
+ // collects a PROOF-DOCUMENT photo for the operator's manual review. We show
49
+ // the chip address read-only for context when available.
48
50
  const chipAddress =
49
- appContext.lastDocumentRead?.document.address ?? '';
50
- const [addressText, setAddressText] = useState<string>(chipAddress);
51
- const [fromChip] = useState<boolean>(!!chipAddress);
51
+ appContext.identificationInfo.address?.addressText ??
52
+ appContext.lastDocumentRead?.document.address ??
53
+ '';
52
54
 
53
55
  const [proofImage, setProofImage] = useState<string | undefined>();
54
56
  const [isCameraOpen, setIsCameraOpen] = useState(false);
55
57
  const captureNextFrame = useRef(false);
56
58
 
57
- // Whether the proof document is required is configurable per workflow step.
58
- const proofRequired =
59
- appContext.currentWorkflowStep?.data?.addressProofRequired !== false;
60
-
61
59
  useEffect(() => {
62
60
  trackVerificationStart('ADDRESS_CAPTURE');
63
61
  }, []);
@@ -72,13 +70,17 @@ const AddressCaptureScreen = () => {
72
70
  }
73
71
  };
74
72
 
75
- const canContinue =
76
- addressText.trim().length > 0 && (!proofRequired || !!proofImage);
73
+ // A proof photo is the whole point of this step.
74
+ const canContinue = !!proofImage;
77
75
 
78
76
  const onContinue = () => {
77
+ // Attach the proof to the existing (chip-sourced) address without changing
78
+ // the address text or fromChip flag. If for some reason no address object
79
+ // exists yet, keep whatever chip text we have for display continuity.
80
+ const existing = appContext.identificationInfo.address;
79
81
  appContext.identificationInfo.address = {
80
- addressText: addressText.trim(),
81
- fromChip,
82
+ addressText: existing?.addressText ?? chipAddress,
83
+ fromChip: existing?.fromChip ?? !!chipAddress,
82
84
  proofImage,
83
85
  proofImageMimeType: proofImage ? 'image/jpeg' : undefined,
84
86
  };
@@ -118,22 +120,20 @@ const AddressCaptureScreen = () => {
118
120
  <Text style={styles.title}>{t('addressCaptureScreen.title')}</Text>
119
121
  <Text style={styles.subtitle}>{t('addressCaptureScreen.subtitle')}</Text>
120
122
 
121
- {fromChip ? (
122
- <Text style={styles.chipHint}>
123
- {t('addressCaptureScreen.prefilledFromChip')}
124
- </Text>
123
+ {chipAddress ? (
124
+ <>
125
+ <Text style={styles.label}>
126
+ {t('addressCaptureScreen.addressLabel')}
127
+ </Text>
128
+ <View style={styles.readonlyAddress}>
129
+ <Text style={styles.readonlyAddressText}>{chipAddress}</Text>
130
+ </View>
131
+ <Text style={styles.chipHint}>
132
+ {t('addressCaptureScreen.prefilledFromChip')}
133
+ </Text>
134
+ </>
125
135
  ) : null}
126
136
 
127
- <Text style={styles.label}>{t('addressCaptureScreen.addressLabel')}</Text>
128
- <TextInput
129
- style={styles.input}
130
- value={addressText}
131
- onChangeText={setAddressText}
132
- placeholder={t('addressCaptureScreen.addressPlaceholder')}
133
- multiline
134
- textAlignVertical="top"
135
- />
136
-
137
137
  <Text style={styles.label}>{t('addressCaptureScreen.proofLabel')}</Text>
138
138
  <Text style={styles.proofHint}>{t('addressCaptureScreen.proofHint')}</Text>
139
139
  {proofImage ? (
@@ -168,15 +168,15 @@ const styles = StyleSheet.create({
168
168
  subtitle: { fontSize: 15, color: '#555', marginBottom: 16 },
169
169
  chipHint: { fontSize: 13, color: '#2E7D32', marginBottom: 12 },
170
170
  label: { fontSize: 14, fontWeight: '600', color: '#222', marginTop: 16, marginBottom: 6 },
171
- input: {
171
+ readonlyAddress: {
172
172
  borderWidth: 1,
173
- borderColor: '#CCC',
173
+ borderColor: '#E0E0E0',
174
+ backgroundColor: '#F7F7F7',
174
175
  borderRadius: 8,
175
176
  padding: 12,
176
- minHeight: 88,
177
- fontSize: 15,
178
- color: '#111',
177
+ minHeight: 56,
179
178
  },
179
+ readonlyAddressText: { fontSize: 15, color: '#111' },
180
180
  proofHint: { fontSize: 13, color: '#777', marginBottom: 10 },
181
181
  proofPreview: {
182
182
  width: '100%',
@@ -144,14 +144,17 @@ const IdentityDocumentEIDScanningScreen = () => {
144
144
  appContext.identificationInfo.scannedDocument.paIntegrityStatus =
145
145
  appContext.lastDocumentRead?.passiveAuth?.integrityStatus ??
146
146
  null;
147
- // MASAK 4/C(1)(d): if the chip's DG11 carried an address and no
148
- // address was captured otherwise, seed it from the chip so it is
149
- // sent to the backend (fromChip=true). A later ADDRESS_CAPTURE
150
- // step can still let the user confirm/edit it.
147
+ // MASAK 4/C(1)(d): the chip's DG11 address is the source of truth
148
+ // and is ALWAYS sent on the NFC read independent of any
149
+ // ADDRESS_CAPTURE step. The address text is never user-edited; an
150
+ // ADDRESS_CAPTURE step only ADDS a proof-document photo for manual
151
+ // operator review (it does not change this address text).
151
152
  const chipAddress =
152
153
  appContext.lastDocumentRead?.document.address;
153
- if (chipAddress && !appContext.identificationInfo.address) {
154
+ if (chipAddress) {
154
155
  appContext.identificationInfo.address = {
156
+ // Preserve any proof image already captured for this id.
157
+ ...appContext.identificationInfo.address,
155
158
  addressText: chipAddress,
156
159
  fromChip: true,
157
160
  };
@@ -181,6 +181,11 @@ const VerificationSessionCheckScreen = () => {
181
181
  .then((session) => {
182
182
  const ctx = appContextRef.current;
183
183
  ctx.workflowSteps = session.workflowSteps;
184
+ // Start the loaded workflow from its FIRST step. Without clearing this,
185
+ // a currentWorkflowStep left over from a prior flow makes the navigator
186
+ // resolve "next" past the last step and jump straight to the result
187
+ // screen (which uploads + completes the session immediately).
188
+ ctx.currentWorkflowStep = undefined;
184
189
  if (session.branding) {
185
190
  ctx.branding = ctx.branding || session.branding;
186
191
  }
@@ -299,6 +304,9 @@ const VerificationSessionCheckScreen = () => {
299
304
  isDemoMode
300
305
  );
301
306
  appContext.workflowSteps = session?.workflowSteps;
307
+ // Start from the first step (see note above) — clear
308
+ // any stale step so we don't jump straight to result.
309
+ appContext.currentWorkflowStep = undefined;
302
310
  if (session?.branding) {
303
311
  appContext.branding =
304
312
  appContext.branding || session.branding;
@@ -50,24 +50,28 @@ const QrCodeScannerCamera = ({
50
50
  width: number,
51
51
  height: number
52
52
  ) => {
53
- // Match UI scanArea: top: '35%', left: '3%', right: '3%', bottom: '35%'
54
- // Add 2% margin buffer to ensure code is comfortably inside frame
55
- const marginBuffer = 0.02;
53
+ // Match the UI scanArea (top/bottom 35%, left/right 3%) but accept a QR
54
+ // whose CENTER falls inside it rather than requiring the WHOLE bounding box.
55
+ // Requiring full containment silently rejected perfectly readable codes that
56
+ // were merely close to an edge or slightly larger than the band — the most
57
+ // common "QR won't scan" failure. The center test keeps the scan scoped to
58
+ // the framed area without being brittle about size/position.
56
59
  const scanningFrame = {
57
- left: width * (0.03 + marginBuffer),
58
- top: height * (0.35 + marginBuffer),
59
- right: width * (0.97 - marginBuffer),
60
- bottom: height * (0.65 - marginBuffer),
60
+ left: width * 0.03,
61
+ top: height * 0.35,
62
+ right: width * 0.97,
63
+ bottom: height * 0.65,
61
64
  };
62
65
 
63
- // Check if entire bounding box is within scanning frame (with margin buffer)
64
- const isInFrame =
65
- boundingBox.left >= scanningFrame.left &&
66
- boundingBox.top >= scanningFrame.top &&
67
- boundingBox.right <= scanningFrame.right &&
68
- boundingBox.bottom <= scanningFrame.bottom;
66
+ const centerX = (boundingBox.left + boundingBox.right) / 2;
67
+ const centerY = (boundingBox.top + boundingBox.bottom) / 2;
69
68
 
70
- return isInFrame;
69
+ return (
70
+ centerX >= scanningFrame.left &&
71
+ centerX <= scanningFrame.right &&
72
+ centerY >= scanningFrame.top &&
73
+ centerY <= scanningFrame.bottom
74
+ );
71
75
  };
72
76
 
73
77
  const handleFrame = useCallback(
@@ -84,18 +88,20 @@ const QrCodeScannerCamera = ({
84
88
  const { frame } = event.nativeEvent;
85
89
 
86
90
  try {
87
- // Barcodes are already detected natively — read directly from frame
91
+ // Barcodes are already detected natively — read directly from frame.
92
+ // Accept the first code that is readable AND framed; don't bail just
93
+ // because more than one code happens to be in view.
88
94
  const barcodes = frame.barcodes;
89
- if (!barcodes || barcodes.length !== 1) return;
95
+ if (!barcodes || barcodes.length === 0) return;
90
96
 
91
- const code = barcodes[0];
97
+ const code = barcodes.find(
98
+ (b) =>
99
+ b.rawValue &&
100
+ b.boundingBox &&
101
+ isInScanningFrame(b.boundingBox, frame.width, frame.height)
102
+ );
92
103
 
93
- // Use frame checking on both platforms for consistent behavior
94
- if (
95
- code.boundingBox &&
96
- isInScanningFrame(code.boundingBox, frame.width, frame.height) &&
97
- code.rawValue
98
- ) {
104
+ if (code?.rawValue) {
99
105
  hasScanned.current = true;
100
106
  onQrCodeScanned(code.rawValue);
101
107
  }
@@ -37,12 +37,34 @@ const handleDeepLink = ({ url }: { url: string }) => {
37
37
  sessionId = segments[i + 1] ?? '';
38
38
  debugLog('handleDeepLink', 'Found sessionId:', sessionId);
39
39
  } else if (segments[i] === 'app-url') {
40
- const host = segments[i + 1] ?? '';
40
+ // The app-url value can be EITHER a bare host (`app-url/host[:port]`) OR a
41
+ // full embedded URL (`app-url/https://host/...`). The embedded form splits
42
+ // across multiple segments because the inner `https://` contains a `//`,
43
+ // e.g. ['app-url', 'https:', '', 'host', ...] — so segments[i+1] is the
44
+ // inner scheme, not the host. Detect that and derive the host from the
45
+ // first non-empty segment after the inner scheme.
46
+ let host = '';
47
+ let embeddedScheme = '';
48
+ const next = segments[i + 1] ?? '';
49
+ const schemeMatch = next.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):$/);
50
+ if (schemeMatch) {
51
+ // Embedded-URL form: skip the inner scheme + the empty segment from `//`.
52
+ embeddedScheme = schemeMatch[1].toLowerCase();
53
+ let j = i + 2;
54
+ while (j < segments.length && segments[j] === '') j += 1;
55
+ host = segments[j] ?? '';
56
+ } else {
57
+ // Bare-host form.
58
+ host = next;
59
+ }
60
+
41
61
  if (!HOST_PATTERN.test(host)) {
42
62
  logWarn('handleDeepLink', 'Rejected invalid app-url host');
43
63
  continue;
44
64
  }
45
- const hostScheme = scheme === 'http' ? 'http' : 'https';
65
+ // Prefer the embedded URL's own scheme; otherwise inherit the link's.
66
+ const hostScheme =
67
+ embeddedScheme || (scheme === 'http' ? 'http' : 'https');
46
68
  if (!isAllowedScheme(hostScheme)) {
47
69
  logWarn('handleDeepLink', 'Rejected non-HTTPS app-url');
48
70
  continue;
@@ -124,11 +124,11 @@ export default {
124
124
  'Connection to the document was lost. Keep the document still against the device and try again.',
125
125
  'eidScannerScreen.nfcTimeout':
126
126
  'Reading the chip timed out. Hold the document steady against the device and try again.',
127
- 'addressCaptureScreen.title': 'Confirm your address',
127
+ 'addressCaptureScreen.title': 'Proof of address',
128
128
  'addressCaptureScreen.subtitle':
129
- 'Enter your current residential address and provide a supporting document.',
129
+ 'Take a clear photo of a document that shows your address. It will be reviewed by an operator.',
130
130
  'addressCaptureScreen.prefilledFromChip':
131
- 'We pre-filled your address from your document chip. Please review and correct it if needed.',
131
+ 'This address was read from your document chip and is used as-is.',
132
132
  'addressCaptureScreen.addressLabel': 'Address',
133
133
  'addressCaptureScreen.addressPlaceholder': 'Street, number, city, country',
134
134
  'addressCaptureScreen.proofLabel': 'Proof of address',
@@ -124,11 +124,11 @@ export default {
124
124
  'Belge ile bağlantı kesildi. Belgeyi cihaza sabit tutun ve tekrar deneyin.',
125
125
  'eidScannerScreen.nfcTimeout':
126
126
  'Çip okuması zaman aşımına uğradı. Belgeyi cihaza sabit tutup tekrar deneyin.',
127
- 'addressCaptureScreen.title': 'Adresinizi doğrulayın',
127
+ 'addressCaptureScreen.title': 'Adres kanıtı',
128
128
  'addressCaptureScreen.subtitle':
129
- 'Güncel yerleşim adresinizi girin ve destekleyici bir belge sağlayın.',
129
+ 'Adresinizi gösteren bir belgenin net bir fotoğrafını çekin. Bir operatör tarafından incelenecektir.',
130
130
  'addressCaptureScreen.prefilledFromChip':
131
- 'Adresiniz belge yongasından önceden dolduruldu. Lütfen kontrol edin ve gerekirse düzeltin.',
131
+ 'Bu adres belge yongasından okundu ve olduğu gibi kullanılır.',
132
132
  'addressCaptureScreen.addressLabel': 'Adres',
133
133
  'addressCaptureScreen.addressPlaceholder': 'Cadde, numara, şehir, ülke',
134
134
  'addressCaptureScreen.proofLabel': 'Adres kanıtı',
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.501.1';
3
+ export const SDK_VERSION = '1.503.0';