From dbc2fe8bde4fbde3c54f5c3067abe52ab6df01ba Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Thu, 2 Oct 2025 12:28:17 +0200 Subject: [PATCH 1/5] separate MRZ data from userStore --- .../screens/document/DocumentCameraScreen.tsx | 8 +-- .../DocumentNFCMethodSelectionScreen.tsx | 42 ++++++++++--- .../document/DocumentNFCScanScreen.tsx | 7 ++- app/src/stores/userStore.ts | 21 ------- app/src/utils/nfcScanner.ts | 2 - packages/mobile-sdk-alpha/src/client.ts | 5 ++ .../mobile-sdk-alpha/src/stores/mrzStore.tsx | 60 +++++++++++++++++++ packages/mobile-sdk-alpha/src/types/public.ts | 3 + 8 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 packages/mobile-sdk-alpha/src/stores/mrzStore.tsx diff --git a/app/src/screens/document/DocumentCameraScreen.tsx b/app/src/screens/document/DocumentCameraScreen.tsx index 521917152..310e8a3f5 100644 --- a/app/src/screens/document/DocumentCameraScreen.tsx +++ b/app/src/screens/document/DocumentCameraScreen.tsx @@ -25,7 +25,6 @@ import { Title } from '@/components/typography/Title'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import Scan from '@/images/icons/passport_camera_scan.svg'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; -import useUserStore from '@/stores/userStore'; import analytics from '@/utils/analytics'; import { black, slate400, slate800, white } from '@/utils/colors'; import { dinot } from '@/utils/fonts'; @@ -35,9 +34,10 @@ const { trackEvent } = analytics(); const DocumentCameraScreen: React.FC = () => { const client = useSelfClient(); + const { useMRZStore } = client; + const { setMRZForNFC } = useMRZStore(); const navigation = useNavigation(); const isFocused = useIsFocused(); - const store = useUserStore(); // Add a ref to track when the camera screen is mounted const scanStartTimeRef = useRef(Date.now()); @@ -102,7 +102,7 @@ const DocumentCameraScreen: React.FC = () => { return; } - store.update({ + setMRZForNFC({ passportNumber: documentNumber, dateOfBirth: formattedDateOfBirth, dateOfExpiry: formattedDateOfExpiry, @@ -116,7 +116,7 @@ const DocumentCameraScreen: React.FC = () => { navigation.navigate('DocumentNFCScan'); }, - [store, navigation], + [setMRZForNFC, navigation], ); const navigateToLaunch = useHapticNavigation('Launch', { action: 'cancel', diff --git a/app/src/screens/document/DocumentNFCMethodSelectionScreen.tsx b/app/src/screens/document/DocumentNFCMethodSelectionScreen.tsx index 6e56cd35a..9c51b5beb 100644 --- a/app/src/screens/document/DocumentNFCMethodSelectionScreen.tsx +++ b/app/src/screens/document/DocumentNFCMethodSelectionScreen.tsx @@ -7,6 +7,8 @@ import { Platform, ScrollView } from 'react-native'; import { Input, YStack } from 'tamagui'; import { useNavigation } from '@react-navigation/native'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; + import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import { SecondaryButton } from '@/components/buttons/SecondaryButton'; import ButtonsContainer from '@/components/ButtonsContainer'; @@ -14,7 +16,6 @@ import { BodyText } from '@/components/typography/BodyText'; import Description from '@/components/typography/Description'; import { Title } from '@/components/typography/Title'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; -import useUserStore from '@/stores/userStore'; import { white } from '@/utils/colors'; type NFCParams = { @@ -93,10 +94,17 @@ const DocumentNFCMethodSelectionScreen: React.FC = () => { const [selectedMethod, setSelectedMethod] = useState('standard'); const [canValue, setCanValue] = useState(''); const [error, setError] = useState(''); - const updatePassport = useUserStore(state => state.update); - const passportNumber = useUserStore(state => state.passportNumber); - const dateOfBirth = useUserStore(state => state.dateOfBirth); - const dateOfExpiry = useUserStore(state => state.dateOfExpiry); + + const selfClient = useSelfClient(); + const { useMRZStore } = selfClient; + const { + passportNumber, + dateOfBirth, + dateOfExpiry, + documentType, + countryCode, + setMRZForNFC, + } = useMRZStore(); const handleSelect = (key: string) => { setSelectedMethod(key); @@ -104,15 +112,33 @@ const DocumentNFCMethodSelectionScreen: React.FC = () => { }; const onPassportNumberChange = (text: string) => { - updatePassport({ passportNumber: text }); + setMRZForNFC({ + passportNumber: text, + dateOfBirth, + dateOfExpiry, + documentType, + countryCode, + }); }; const onDateOfBirthChange = (text: string) => { - updatePassport({ dateOfBirth: text }); + setMRZForNFC({ + passportNumber, + dateOfBirth: text, + dateOfExpiry, + documentType, + countryCode, + }); }; const onDateOfExpiryChange = (text: string) => { - updatePassport({ dateOfExpiry: text }); + setMRZForNFC({ + passportNumber, + dateOfBirth, + dateOfExpiry: text, + documentType, + countryCode, + }); }; const handleProceed = () => { diff --git a/app/src/screens/document/DocumentNFCScanScreen.tsx b/app/src/screens/document/DocumentNFCScanScreen.tsx index 5943a5924..21af6beaf 100644 --- a/app/src/screens/document/DocumentNFCScanScreen.tsx +++ b/app/src/screens/document/DocumentNFCScanScreen.tsx @@ -53,8 +53,8 @@ import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import { useFeedback } from '@/providers/feedbackProvider'; import { storePassportData } from '@/providers/passportDataProvider'; import { logNFCEvent } from '@/Sentry'; -import useUserStore from '@/stores/userStore'; import { + configureNfcAnalytics, flushAllAnalytics, setNfcScanningActive, trackNfcEvent, @@ -92,7 +92,7 @@ type DocumentNFCScanRoute = RouteProp< const DocumentNFCScanScreen: React.FC = () => { const selfClient = useSelfClient(); - const { trackEvent } = selfClient; + const { trackEvent, useMRZStore } = selfClient; const navigation = useNavigation(); const route = useRoute(); @@ -104,7 +104,7 @@ const DocumentNFCScanScreen: React.FC = () => { dateOfExpiry, documentType, countryCode, - } = useUserStore(); + } = useMRZStore(); const [isNfcSupported, setIsNfcSupported] = useState(true); const [isNfcEnabled, setIsNfcEnabled] = useState(true); @@ -324,6 +324,7 @@ const DocumentNFCScanScreen: React.FC = () => { const { canNumber, useCan, skipPACE, skipCA, extendedMode } = route.params ?? {}; + await configureNfcAnalytics(); const scanResponse = await scan({ passportNumber, dateOfBirth, diff --git a/app/src/stores/userStore.ts b/app/src/stores/userStore.ts index c1b6398f0..d4a68de80 100644 --- a/app/src/stores/userStore.ts +++ b/app/src/stores/userStore.ts @@ -3,16 +3,10 @@ // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. import { create } from 'zustand'; -import { DEFAULT_DOB, DEFAULT_DOE, DEFAULT_PNUMBER } from '@env'; import type { IdDocInput } from '@selfxyz/common/utils'; interface UserState { - documentType: string; - countryCode: string; - passportNumber: string; - dateOfBirth: string; - dateOfExpiry: string; deepLinkName?: string; deepLinkSurname?: string; deepLinkNationality?: IdDocInput['nationality']; @@ -20,7 +14,6 @@ interface UserState { deepLinkGender?: string; idDetailsDocumentId?: string; update: (patch: Partial) => void; - deleteMrzFields: () => void; setIdDetailsDocumentId: (documentId: string) => void; setDeepLinkUserDetails: (details: { name?: string; @@ -33,11 +26,6 @@ interface UserState { } const useUserStore = create((set, _get) => ({ - passportNumber: DEFAULT_PNUMBER ?? '', - documentType: '', - countryCode: '', - dateOfBirth: DEFAULT_DOB ?? '', - dateOfExpiry: DEFAULT_DOE ?? '', deepLinkName: undefined, deepLinkSurname: undefined, deepLinkNationality: undefined, @@ -49,15 +37,6 @@ const useUserStore = create((set, _get) => ({ set(state => ({ ...state, ...patch })); }, - deleteMrzFields: () => - set({ - documentType: '', - passportNumber: '', - countryCode: '', - dateOfBirth: '', - dateOfExpiry: '', - }), - setDeepLinkUserDetails: details => set({ deepLinkName: details.name, diff --git a/app/src/utils/nfcScanner.ts b/app/src/utils/nfcScanner.ts index 4e75e45c6..0ece35034 100644 --- a/app/src/utils/nfcScanner.ts +++ b/app/src/utils/nfcScanner.ts @@ -52,8 +52,6 @@ export const parseScanResponse = (response: unknown) => { }; export const scan = async (inputs: Inputs) => { - await configureNfcAnalytics(); - const baseContext = { sessionId: inputs.sessionId, userId: inputs.userId, diff --git a/packages/mobile-sdk-alpha/src/client.ts b/packages/mobile-sdk-alpha/src/client.ts index 40f9b773b..db65ba481 100644 --- a/packages/mobile-sdk-alpha/src/client.ts +++ b/packages/mobile-sdk-alpha/src/client.ts @@ -8,6 +8,7 @@ import { notImplemented } from './errors'; import { extractMRZInfo as parseMRZInfo } from './processing/mrz'; import { ProofContext } from './proving/internal/logging'; import { useProvingStore } from './proving/provingMachine'; +import { useMRZStore } from './stores/mrzStore'; import { useProtocolStore } from './stores/protocolStore'; import { useSelfAppStore } from './stores/selfAppStore'; import { SDKEvent, SDKEventMap, SdkEvents } from './types/events'; @@ -180,10 +181,14 @@ export function createSelfClient({ getProtocolState: () => { return useProtocolStore.getState(); }, + getMRZState: () => { + return useMRZStore.getState(); + }, // for reactivity (if needed) useProvingStore, useSelfAppStore, useProtocolStore, + useMRZStore, }; } diff --git a/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx b/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx new file mode 100644 index 000000000..2a8bd0f6d --- /dev/null +++ b/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import { create } from 'zustand'; + +export interface MRZState { + // Fields needed for NFC scanning + passportNumber: string; + dateOfBirth: string; + dateOfExpiry: string; + countryCode: string; + documentType: string; + + // Store actions + setMRZForNFC: (data: { + passportNumber: string; + dateOfBirth: string; + dateOfExpiry: string; + countryCode: string; + documentType: string; + }) => void; + clearMRZ: () => void; + update: (patch: Partial) => void; +} + +// TODO: what about the defaults from @env? +const initialState = { + passportNumber: '', + dateOfBirth: '', + dateOfExpiry: '', + countryCode: '', + documentType: '', +}; + +/* + Never export outside of the mobile sdk. It can cause multiple instances of the store to be created. + Use the functions above to interact with the store. +*/ +export const useMRZStore = create((set, get) => ({ + ...initialState, + + setMRZForNFC: data => { + set({ + passportNumber: data.passportNumber, + dateOfBirth: data.dateOfBirth, + dateOfExpiry: data.dateOfExpiry, + countryCode: data.countryCode, + documentType: data.documentType, + }); + }, + + clearMRZ: () => { + set(initialState); + }, + + update: (patch: Partial) => { + set(state => ({ ...state, ...patch })); + }, +})); diff --git a/packages/mobile-sdk-alpha/src/types/public.ts b/packages/mobile-sdk-alpha/src/types/public.ts index 70a11f774..1bc6d6ea2 100644 --- a/packages/mobile-sdk-alpha/src/types/public.ts +++ b/packages/mobile-sdk-alpha/src/types/public.ts @@ -8,6 +8,7 @@ import type { DocumentCatalog, IDDocument, PassportData } from '@selfxyz/common' import type { ProofContext } from '../proving/internal/logging'; import { ProvingState } from '../proving/provingMachine'; +import { MRZState } from '../stores/mrzStore'; import { ProtocolState } from '../stores/protocolStore'; import { SelfAppState } from '../stores/selfAppStore'; import { SDKEvent, SDKEventMap } from './events'; @@ -183,10 +184,12 @@ export interface SelfClient { getProvingState: () => ProvingState; getSelfAppState: () => SelfAppState; getProtocolState: () => ProtocolState; + getMRZState: () => MRZState; useProvingStore: ReturnType>; useSelfAppStore: ReturnType>; useProtocolStore: ReturnType>; + useMRZStore: ReturnType>; } export type Unsubscribe = () => void; export interface StorageAdapter { From fcfa08a59d1b90b10220d36145aabf6f00af3fff Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Thu, 2 Oct 2025 12:48:39 +0200 Subject: [PATCH 2/5] lint --- packages/mobile-sdk-alpha/src/stores/mrzStore.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx b/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx index 2a8bd0f6d..330bc0761 100644 --- a/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx +++ b/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx @@ -37,7 +37,7 @@ const initialState = { Never export outside of the mobile sdk. It can cause multiple instances of the store to be created. Use the functions above to interact with the store. */ -export const useMRZStore = create((set, get) => ({ +export const useMRZStore = create(set => ({ ...initialState, setMRZForNFC: data => { From a62a9aa29f16b76f1537a6836680b595b70127e6 Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Thu, 2 Oct 2025 14:26:31 +0200 Subject: [PATCH 3/5] fix test --- app/tests/utils/nfcScanner.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/tests/utils/nfcScanner.test.ts b/app/tests/utils/nfcScanner.test.ts index 832acf5bf..244fb173c 100644 --- a/app/tests/utils/nfcScanner.test.ts +++ b/app/tests/utils/nfcScanner.test.ts @@ -268,8 +268,6 @@ describe('scan', () => { await scan(mockInputs); - // Should configure analytics before scanning - expect(mockConfigureNfcAnalytics).toHaveBeenCalled(); expect(mockScanPassport).toHaveBeenCalled(); }); }); From cb857f5723d536cf8b433e4fa882a4f9ad2194e1 Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Thu, 2 Oct 2025 17:30:15 +0200 Subject: [PATCH 4/5] apply suggestion --- .../DocumentNFCMethodSelectionScreen.tsx | 33 +++---------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/app/src/screens/document/DocumentNFCMethodSelectionScreen.tsx b/app/src/screens/document/DocumentNFCMethodSelectionScreen.tsx index 9c51b5beb..0791b34d7 100644 --- a/app/src/screens/document/DocumentNFCMethodSelectionScreen.tsx +++ b/app/src/screens/document/DocumentNFCMethodSelectionScreen.tsx @@ -97,14 +97,7 @@ const DocumentNFCMethodSelectionScreen: React.FC = () => { const selfClient = useSelfClient(); const { useMRZStore } = selfClient; - const { - passportNumber, - dateOfBirth, - dateOfExpiry, - documentType, - countryCode, - setMRZForNFC, - } = useMRZStore(); + const { update, passportNumber, dateOfBirth, dateOfExpiry } = useMRZStore(); const handleSelect = (key: string) => { setSelectedMethod(key); @@ -112,33 +105,15 @@ const DocumentNFCMethodSelectionScreen: React.FC = () => { }; const onPassportNumberChange = (text: string) => { - setMRZForNFC({ - passportNumber: text, - dateOfBirth, - dateOfExpiry, - documentType, - countryCode, - }); + update({ passportNumber: text }); }; const onDateOfBirthChange = (text: string) => { - setMRZForNFC({ - passportNumber, - dateOfBirth: text, - dateOfExpiry, - documentType, - countryCode, - }); + update({ dateOfBirth: text }); }; const onDateOfExpiryChange = (text: string) => { - setMRZForNFC({ - passportNumber, - dateOfBirth, - dateOfExpiry: text, - documentType, - countryCode, - }); + update({ dateOfExpiry: text }); }; const handleProceed = () => { From 8d9074b34dbed88310c1d40df41ea9533456ae93 Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Thu, 2 Oct 2025 18:54:48 +0200 Subject: [PATCH 5/5] Update packages/mobile-sdk-alpha/src/stores/mrzStore.tsx Co-authored-by: Aaron DeRuvo --- packages/mobile-sdk-alpha/src/stores/mrzStore.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx b/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx index 330bc0761..4fb877bd7 100644 --- a/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx +++ b/packages/mobile-sdk-alpha/src/stores/mrzStore.tsx @@ -35,7 +35,7 @@ const initialState = { /* Never export outside of the mobile sdk. It can cause multiple instances of the store to be created. - Use the functions above to interact with the store. + interact with the store thru the self client */ export const useMRZStore = create(set => ({ ...initialState,