mirror of
https://github.com/selfxyz/self.git
synced 2026-01-10 15:18:18 -05:00
[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 commita5c97210a5. * ignore false positive * Revert "Revert "try rebuild deps every time"" This reverts commit4f44615fd6. * 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:
@@ -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')
|
||||
|
||||
@@ -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'],
|
||||
};
|
||||
|
||||
@@ -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..."
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user