[SELF-830] feat: demo app nfc scanning (#1236)

* save wip demo app nfc scanning

* save wip

* fix types

* Fix Android NFC scanning in demo app (#1241)

* fix tests

* fix pipelines

* fix linting

* WIP move to flows/onboarding/scan-nfc

* prettier and fix test

* fix test

* update lock

* update deps

* Feat/android prebuilt modules (#1292)

* move entire screen

* remove redundancy in components and utils

* fixes

* lint

* ignore

* remove unneeded

* fix imports

* remove unused

* Update packages/mobile-sdk-alpha/src/types/events.ts

Co-authored-by: Aaron DeRuvo <aaron.deruvo@clabs.co>

* uuid not needed for demo app

* android: update ci check

* timeout fix, image temp fix

* prettier fix

* try rebuild deps every time

* Temporarily disable cache check in CI

* Revert "try rebuild deps every time"

This reverts commit a5c97210a5.

* ignore false positive

* Revert "Revert "try rebuild deps every time""

This reverts commit 4f44615fd6.

* fix?

* sanitize error message first

* remove TODO that has been taken care of

* MSDK: add ios prebuilts (#1308)

* add ios prebuilt

* remove outdate readme

* remove duplicates

* comment out unused

* add prettier ignore

* Update .gitguardian.yml to ignore iOS frameworks and build artifacts

* update gitguardian ignore paths

* migrate config version

* add ignored-matches

---------

Co-authored-by: Justin Hernandez <justin.hernandez@self.xyz>

* remove duplicated code

* exclude mobile-sdk native modules when `E2E_TESTING` flag is set

* app: disable ios msdk auto-linking

* add E2E_TESTING flag

---------

Co-authored-by: Leszek Stachowski <leszek.stachowski@self.xyz>
Co-authored-by: seshanthS <seshanth@protonmail.com>
Co-authored-by: Seshanth.S <35675963+seshanthS@users.noreply.github.com>
Co-authored-by: Aaron DeRuvo <aaron.deruvo@clabs.co>
This commit is contained in:
Justin Hernandez
2025-10-23 10:44:32 -07:00
committed by GitHub
parent 6ff50ae987
commit 077dcc47b4
586 changed files with 218992 additions and 3868 deletions

View File

@@ -10,3 +10,5 @@ include ':react-native-passport-reader'
project(':react-native-passport-reader').projectDir = new File(rootProject.projectDir, './react-native-passport-reader/android')
include ':passportreader'
project(':passportreader').projectDir = new File(rootProject.projectDir, './android-passport-nfc-reader/app')
include ':mobile-sdk-alpha'
project(':mobile-sdk-alpha').projectDir = new File(rootProject.projectDir, '../../packages/mobile-sdk-alpha/android')

View File

@@ -5,7 +5,7 @@
module.exports = {
project: { ios: {}, android: {} },
dependencies: {
'@selfxyz/mobile-sdk-alpha': { platforms: { android: null } },
'@selfxyz/mobile-sdk-alpha': { platforms: { android: null, ios: null } },
},
assets: ['../src/assets/fonts'],
};

View File

@@ -123,15 +123,15 @@ else
log "📁 android-passport-nfc-reader already exists - preserving existing directory"
fi
# Build and package the SDK with timeout
log "Building SDK..."
# Build and package the SDK with timeout (including dependencies)
log "Building SDK and dependencies..."
if is_ci; then
timeout 300 yarn workspace @selfxyz/mobile-sdk-alpha build || {
timeout 300 yarn workspaces foreach --from @selfxyz/mobile-sdk-alpha --topological --recursive run build || {
log "SDK build timed out after 5 minutes"
exit 1
}
else
yarn workspace @selfxyz/mobile-sdk-alpha build
yarn workspaces foreach --from @selfxyz/mobile-sdk-alpha --topological --recursive run build
fi
log "Creating SDK tarball..."
@@ -189,20 +189,20 @@ else
env -u SELFXYZ_INTERNAL_REPO_PAT yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH"
fi
# Verify installation (check both local and hoisted locations)
SDK_ANDROID_PATH=""
if [[ -d "node_modules/@selfxyz/mobile-sdk-alpha/android/src/main/res" ]]; then
SDK_ANDROID_PATH="node_modules/@selfxyz/mobile-sdk-alpha/android/src/main/res"
elif [[ -d "../node_modules/@selfxyz/mobile-sdk-alpha/android/src/main/res" ]]; then
SDK_ANDROID_PATH="../node_modules/@selfxyz/mobile-sdk-alpha/android/src/main/res"
# Verify installation (check for AAR file in both local and hoisted locations)
SDK_AAR_PATH=""
if [[ -f "node_modules/@selfxyz/mobile-sdk-alpha/dist/android/mobile-sdk-alpha-release.aar" ]]; then
SDK_AAR_PATH="node_modules/@selfxyz/mobile-sdk-alpha/dist/android/mobile-sdk-alpha-release.aar"
elif [[ -f "../node_modules/@selfxyz/mobile-sdk-alpha/dist/android/mobile-sdk-alpha-release.aar" ]]; then
SDK_AAR_PATH="../node_modules/@selfxyz/mobile-sdk-alpha/dist/android/mobile-sdk-alpha-release.aar"
else
log "ERROR: SDK Android resources not found after installation"
log "Checked: node_modules/@selfxyz/mobile-sdk-alpha/android/src/main/res"
log "Checked: ../node_modules/@selfxyz/mobile-sdk-alpha/android/src/main/res"
log "ERROR: SDK AAR file not found after installation"
log "Checked: node_modules/@selfxyz/mobile-sdk-alpha/dist/android/mobile-sdk-alpha-release.aar"
log "Checked: ../node_modules/@selfxyz/mobile-sdk-alpha/dist/android/mobile-sdk-alpha-release.aar"
exit 1
fi
log "SDK Android resources found at: $SDK_ANDROID_PATH"
log "SDK AAR file found at: $SDK_AAR_PATH"
# Build Android APK (don't install to device)
log "Building Android APK..."

View File

@@ -1,26 +0,0 @@
// 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 React from 'react';
import { StyleSheet, View } from 'react-native';
interface ButtonsContainerProps {
children: React.ReactNode;
}
const ButtonsContainer = ({ children }: ButtonsContainerProps) => {
return <View style={styles.buttonsContainer}>{children}</View>;
};
export default ButtonsContainer;
const styles = StyleSheet.create({
buttonsContainer: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: 10,
},
});

View File

@@ -1,29 +0,0 @@
// 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 React from 'react';
import type { ViewStyle } from 'react-native';
import { StyleSheet, View } from 'react-native';
interface TextsContainerProps {
children: React.ReactNode;
style?: ViewStyle;
}
const TextsContainer = ({ children, style }: TextsContainerProps) => {
return <View style={[styles.textsContainer, style]}>{children}</View>;
};
export default TextsContainer;
const styles = StyleSheet.create({
textsContainer: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
gap: 10,
},
});

View File

@@ -9,6 +9,8 @@ import { Platform } from 'react-native';
import {
type Adapters,
createListenersMap,
type LogLevel,
type NFCScanContext,
reactNativeScannerAdapter,
SdkEvents,
SelfClientProvider as SDKSelfClientProvider,
@@ -23,7 +25,7 @@ import { unsafe_getPrivateKey } from '@/providers/authProvider';
import { selfClientDocumentsAdapter } from '@/providers/passportDataProvider';
import { logNFCEvent, logProofEvent } from '@/Sentry';
import { useSettingStore } from '@/stores/settingStore';
import analytics from '@/utils/analytics';
import analytics, { trackNfcEvent } from '@/utils/analytics';
type GlobalCrypto = { crypto?: { subtle?: Crypto['subtle'] } };
/**
@@ -112,6 +114,17 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
trackEvent: (event: string, data?: TrackEventParams) => {
analytics().trackEvent(event, data);
},
trackNfcEvent: (name: string, data?: Record<string, unknown>) => {
trackNfcEvent(name, data);
},
logNFCEvent: (
level: LogLevel,
message: string,
context: NFCScanContext,
details?: Record<string, unknown>,
) => {
logNFCEvent(level, message, context, details);
},
},
auth: {
getPrivateKey: () => unsafe_getPrivateKey(),

View File

@@ -29,10 +29,13 @@ import {
signatureAlgorithmToStrictSignatureAlgorithm,
useSelfClient,
} from '@selfxyz/mobile-sdk-alpha';
import { Caption, PrimaryButton } from '@selfxyz/mobile-sdk-alpha/components';
import {
ButtonsContainer,
Caption,
PrimaryButton,
} from '@selfxyz/mobile-sdk-alpha/components';
import { MockDataEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import ButtonsContainer from '@/components/ButtonsContainer';
import { useMockDataForm } from '@/hooks/useMockDataForm';
import SelfDevCard from '@/images/card-dev.svg';
import IdIcon from '@/images/icons/id_icon.svg';

View File

@@ -16,13 +16,13 @@ import type { IdDocInput } from '@selfxyz/common/utils';
import { genMockIdDocAndInitDataParsing } from '@selfxyz/common/utils/passports';
import {
BodyText,
ButtonsContainer,
Description,
PrimaryButton,
Title,
} from '@selfxyz/mobile-sdk-alpha/components';
import { MockDataEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import ButtonsContainer from '@/components/ButtonsContainer';
import type { RootStackParamList } from '@/navigation';
import { storePassportData } from '@/providers/passportDataProvider';
import useUserStore from '@/stores/userStore';

View File

@@ -16,12 +16,12 @@ import type {
} from '@selfxyz/common/utils/types';
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
import {
ButtonsContainer,
PrimaryButton,
SecondaryButton,
} from '@selfxyz/mobile-sdk-alpha/components';
import { DocumentEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import ButtonsContainer from '@/components/ButtonsContainer';
import type { RootStackParamList } from '@/navigation';
import { usePassport } from '@/providers/passportDataProvider';
import { borderColor, textBlack, white } from '@/utils/colors';

View File

@@ -11,13 +11,13 @@ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
import {
BodyText,
ButtonsContainer,
Description,
PrimaryButton,
SecondaryButton,
Title,
} from '@selfxyz/mobile-sdk-alpha/components';
import ButtonsContainer from '@/components/ButtonsContainer';
import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout';
import type { RootStackParamList } from '@/navigation';
import { white } from '@/utils/colors';

View File

@@ -34,19 +34,20 @@ import { CircleHelp } from '@tamagui/lucide-icons';
import type { PassportData } from '@selfxyz/common/types';
import {
hasAnyValidRegisteredDocument,
sanitizeErrorMessage,
useSelfClient,
} from '@selfxyz/mobile-sdk-alpha';
import {
BodyText,
ButtonsContainer,
PrimaryButton,
SecondaryButton,
TextsContainer,
Title,
} from '@selfxyz/mobile-sdk-alpha/components';
import { PassportEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import passportVerifyAnimation from '@/assets/animations/passport_verify.json';
import ButtonsContainer from '@/components/ButtonsContainer';
import TextsContainer from '@/components/TextsContainer';
import { useFeedbackAutoHide } from '@/hooks/useFeedbackAutoHide';
import useHapticNavigation from '@/hooks/useHapticNavigation';
import NFC_IMAGE from '@/images/nfc.png';
@@ -71,7 +72,6 @@ import {
impactLight,
} from '@/utils/haptic';
import { parseScanResponse, scan } from '@/utils/nfcScanner';
import { sanitizeErrorMessage } from '@/utils/utils';
const emitter =
Platform.OS === 'android'

View File

@@ -11,13 +11,13 @@ import {
} from '@selfxyz/mobile-sdk-alpha';
import {
BodyText,
ButtonsContainer,
SecondaryButton,
TextsContainer,
Title,
} from '@selfxyz/mobile-sdk-alpha/components';
import { PassportEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import ButtonsContainer from '@/components/ButtonsContainer';
import TextsContainer from '@/components/TextsContainer';
import useHapticNavigation from '@/hooks/useHapticNavigation';
import NFC_IMAGE from '@/images/nfc.png';
import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout';

View File

@@ -10,16 +10,16 @@ import { useNavigation } from '@react-navigation/native';
import {
Additional,
ButtonsContainer,
Description,
PrimaryButton,
SecondaryButton,
TextsContainer,
Title,
} from '@selfxyz/mobile-sdk-alpha/components';
import { PassportEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import passportOnboardingAnimation from '@/assets/animations/passport_onboarding.json';
import ButtonsContainer from '@/components/ButtonsContainer';
import TextsContainer from '@/components/TextsContainer';
import useHapticNavigation from '@/hooks/useHapticNavigation';
import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout';
import { black, slate100, white } from '@/utils/colors';

View File

@@ -5,7 +5,7 @@
import { Linking, Platform } from 'react-native';
import { getCountry, getLocales, getTimeZone } from 'react-native-localize';
import { sanitizeErrorMessage } from '@/utils/utils';
import { sanitizeErrorMessage } from '@selfxyz/mobile-sdk-alpha';
import { version } from '../../package.json';

View File

@@ -1,14 +0,0 @@
// 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.
// Redacts 9+ consecutive digits and MRZ-like blocks to reduce PII exposure
export const sanitizeErrorMessage = (msg: string): string => {
try {
return msg
.replace(/\b\d{9,}\b/g, '[REDACTED]')
.replace(/[A-Z0-9<]{30,}/g, '[MRZ_REDACTED]');
} catch {
return 'redacted';
}
};

View File

@@ -1,56 +0,0 @@
// 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 { sanitizeErrorMessage } from '@/utils/utils';
describe('sanitizeErrorMessage', () => {
it('redacts sequences of 9+ digits', () => {
const input = 'Passport number 123456789 should be hidden';
const result = sanitizeErrorMessage(input);
expect(result).toBe('Passport number [REDACTED] should be hidden');
});
it('does not redact short numbers (<9 digits)', () => {
const input = 'Retry in 120 seconds. Code 12345678 only';
const result = sanitizeErrorMessage(input);
expect(result).toBe('Retry in 120 seconds. Code 12345678 only');
});
it('redacts MRZ-like long blocks (>=30 chars of A-Z0-9<)', () => {
const mrzLike = 'P<USADOE<<JOHN<<<<<<<<<<<<<<<<<<<<<<<<<<<<';
const suffix = ' additional context';
const input = `${mrzLike}${suffix}`;
const result = sanitizeErrorMessage(input);
expect(result).toBe('[MRZ_REDACTED]' + suffix);
});
it('redacts multi-line MRZ (two lines of 44 chars)', () => {
const line1 = 'A'.repeat(44);
const line2 = 'B'.repeat(44);
const suffix = ' context';
const input = `${line1}\n${line2}${suffix}`;
const result = sanitizeErrorMessage(input);
expect(result).toBe('[MRZ_REDACTED]\n[MRZ_REDACTED]' + suffix);
});
it('redacts multiple occurrences in the same string', () => {
const input = 'ids 123456789 and 987654321 are present';
const result = sanitizeErrorMessage(input);
expect(result).toBe('ids [REDACTED] and [REDACTED] are present');
});
it('returns "redacted" on unexpected errors', () => {
// Simulate a failure by monkey-patching String.prototype.replace temporarily
const originalReplace = (String.prototype as any).replace;
(String.prototype as any).replace = () => {
throw new Error('boom');
};
try {
const result = sanitizeErrorMessage('any');
expect(result).toBe('redacted');
} finally {
(String.prototype as any).replace = originalReplace;
}
});
});