mirror of
https://github.com/selfxyz/self.git
synced 2026-01-10 23:27:56 -05:00
move common aadhaar functions to msdk (#1200)
* move common aadhaar functions to msdk * remove comment * add license headers
This commit is contained in:
@@ -754,6 +754,7 @@ async function storeDocumentDirectlyToKeychain(
|
||||
});
|
||||
}
|
||||
|
||||
// Duplicate funciton. prefer one on mobile sdk
|
||||
export async function storeDocumentWithDeduplication(
|
||||
passportData: PassportData | AadhaarData,
|
||||
): Promise<string> {
|
||||
@@ -801,7 +802,7 @@ export async function storeDocumentWithDeduplication(
|
||||
|
||||
return contentHash;
|
||||
}
|
||||
|
||||
// Duplicate function. prefer one in mobile sdk
|
||||
export async function storePassportData(
|
||||
passportData: PassportData | AadhaarData,
|
||||
) {
|
||||
|
||||
@@ -201,6 +201,18 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
|
||||
}
|
||||
});
|
||||
|
||||
addListener(SdkEvents.PROVING_AADHAAR_UPLOAD_SUCCESS, () => {
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('AadhaarUploadSuccess');
|
||||
}
|
||||
});
|
||||
addListener(SdkEvents.PROVING_AADHAAR_UPLOAD_FAILURE, ({ errorType }) => {
|
||||
if (navigationRef.isReady()) {
|
||||
// @ts-expect-error
|
||||
navigationRef.navigate('AadhaarUploadError', { errorType });
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
|
||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { AadhaarEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
import { getErrorMessages } from '@selfxyz/mobile-sdk-alpha/onboarding/import-aadhaar';
|
||||
|
||||
import { PrimaryButton } from '@/components/buttons/PrimaryButton';
|
||||
import { SecondaryButton } from '@/components/buttons/SecondaryButton';
|
||||
@@ -34,24 +35,7 @@ const AadhaarUploadErrorScreen: React.FC = () => {
|
||||
const { trackEvent } = useSelfClient();
|
||||
const errorType = route.params?.errorType || 'general';
|
||||
|
||||
// Define error messages based on error type
|
||||
const getErrorMessages = () => {
|
||||
if (errorType === 'expired') {
|
||||
return {
|
||||
title: 'QR Code Has Expired',
|
||||
description:
|
||||
'You uploaded a valid Aadhaar QR code, but unfortunately it has expired. Please generate a new QR code from the mAadhaar app and try again.',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: 'There was a problem reading the code',
|
||||
description:
|
||||
'Please ensure the QR code is clear and well-lit, then try again. For best results, take a screenshot of the QR code instead of photographing it.',
|
||||
};
|
||||
};
|
||||
|
||||
const { title, description } = getErrorMessages();
|
||||
const { title, description } = getErrorMessages(errorType);
|
||||
|
||||
return (
|
||||
<YStack flex={1} backgroundColor={slate100}>
|
||||
|
||||
@@ -8,13 +8,9 @@ import { Image, XStack, YStack } from 'tamagui';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import {
|
||||
extractQRDataFields,
|
||||
getAadharRegistrationWindow,
|
||||
} from '@selfxyz/common/utils';
|
||||
import type { AadhaarData } from '@selfxyz/common/utils/types';
|
||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { AadhaarEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
import { useAadhaar } from '@selfxyz/mobile-sdk-alpha/onboarding/import-aadhaar';
|
||||
|
||||
import { PrimaryButton } from '@/components/buttons/PrimaryButton';
|
||||
import { BodyText } from '@/components/typography/BodyText';
|
||||
@@ -22,7 +18,6 @@ import { useModal } from '@/hooks/useModal';
|
||||
import AadhaarImage from '@/images/512w.png';
|
||||
import { useSafeAreaInsets } from '@/mocks/react-native-safe-area-context';
|
||||
import type { RootStackParamList } from '@/navigation';
|
||||
import { storePassportData } from '@/providers/passportDataProvider';
|
||||
import { slate100, slate200, slate400, slate500, white } from '@/utils/colors';
|
||||
import { extraYPadding } from '@/utils/constants';
|
||||
import {
|
||||
@@ -32,6 +27,7 @@ import {
|
||||
|
||||
const AadhaarUploadScreen: React.FC = () => {
|
||||
const { bottom } = useSafeAreaInsets();
|
||||
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
const { trackEvent } = useSelfClient();
|
||||
@@ -65,110 +61,7 @@ const AadhaarUploadScreen: React.FC = () => {
|
||||
}
|
||||
}, [trackEvent]);
|
||||
|
||||
const validateAAdhaarTimestamp = useCallback(
|
||||
async (timestamp: string) => {
|
||||
//timestamp is in YYYY-MM-DD HH:MM format
|
||||
trackEvent(AadhaarEvents.TIMESTAMP_VALIDATION_STARTED);
|
||||
|
||||
const currentTimestamp = new Date().getTime();
|
||||
const timestampDate = new Date(timestamp);
|
||||
const timestampTimestamp = timestampDate.getTime();
|
||||
const diff = currentTimestamp - timestampTimestamp;
|
||||
const diffMinutes = diff / (1000 * 60);
|
||||
|
||||
const allowedWindow = await getAadharRegistrationWindow();
|
||||
const isValid = diffMinutes <= allowedWindow;
|
||||
|
||||
if (isValid) {
|
||||
trackEvent(AadhaarEvents.TIMESTAMP_VALIDATION_SUCCESS);
|
||||
} else {
|
||||
trackEvent(AadhaarEvents.TIMESTAMP_VALIDATION_FAILED);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
},
|
||||
[trackEvent],
|
||||
);
|
||||
|
||||
const processAadhaarQRCode = useCallback(
|
||||
async (qrCodeData: string) => {
|
||||
try {
|
||||
if (
|
||||
!qrCodeData ||
|
||||
typeof qrCodeData !== 'string' ||
|
||||
qrCodeData.length < 100
|
||||
) {
|
||||
trackEvent(AadhaarEvents.QR_CODE_INVALID_FORMAT);
|
||||
throw new Error('Invalid QR code format - too short or not a string');
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(qrCodeData)) {
|
||||
trackEvent(AadhaarEvents.QR_CODE_INVALID_FORMAT);
|
||||
throw new Error('Invalid QR code format - not a numeric string');
|
||||
}
|
||||
|
||||
if (qrCodeData.length < 100) {
|
||||
trackEvent(AadhaarEvents.QR_CODE_INVALID_FORMAT);
|
||||
throw new Error(
|
||||
'QR code too short - likely not a valid Aadhaar QR code',
|
||||
);
|
||||
}
|
||||
|
||||
trackEvent(AadhaarEvents.QR_DATA_EXTRACTION_STARTED);
|
||||
let extractedFields;
|
||||
try {
|
||||
extractedFields = extractQRDataFields(qrCodeData);
|
||||
trackEvent(AadhaarEvents.QR_DATA_EXTRACTION_SUCCESS);
|
||||
} catch {
|
||||
trackEvent(AadhaarEvents.QR_CODE_PARSE_FAILED);
|
||||
throw new Error('Failed to parse Aadhaar QR code - invalid format');
|
||||
}
|
||||
|
||||
if (
|
||||
!extractedFields.name ||
|
||||
!extractedFields.dob ||
|
||||
!extractedFields.gender
|
||||
) {
|
||||
trackEvent(AadhaarEvents.QR_CODE_MISSING_FIELDS);
|
||||
throw new Error('Invalid Aadhaar QR code - missing required fields');
|
||||
}
|
||||
|
||||
if (!(await validateAAdhaarTimestamp(extractedFields.timestamp))) {
|
||||
trackEvent(AadhaarEvents.QR_CODE_EXPIRED);
|
||||
throw new Error('QRCODE_EXPIRED');
|
||||
}
|
||||
|
||||
const aadhaarData: AadhaarData = {
|
||||
documentType: 'aadhaar',
|
||||
documentCategory: 'aadhaar',
|
||||
mock: false,
|
||||
qrData: qrCodeData,
|
||||
extractedFields: extractedFields,
|
||||
signature: [],
|
||||
publicKey: '',
|
||||
photoHash: '',
|
||||
};
|
||||
|
||||
trackEvent(AadhaarEvents.DATA_STORAGE_STARTED);
|
||||
await storePassportData(aadhaarData);
|
||||
trackEvent(AadhaarEvents.DATA_STORAGE_SUCCESS);
|
||||
|
||||
trackEvent(AadhaarEvents.QR_UPLOAD_SUCCESS);
|
||||
|
||||
navigation.navigate('AadhaarUploadSuccess');
|
||||
} catch (error) {
|
||||
// Check if it's a QR code expiration error
|
||||
const errorType: 'expired' | 'general' =
|
||||
error instanceof Error && error.message === 'QRCODE_EXPIRED'
|
||||
? 'expired'
|
||||
: 'general';
|
||||
|
||||
trackEvent(AadhaarEvents.ERROR_SCREEN_NAVIGATED, { errorType });
|
||||
(navigation.navigate as any)('AadhaarUploadError', { errorType });
|
||||
}
|
||||
},
|
||||
[navigation, trackEvent, validateAAdhaarTimestamp],
|
||||
);
|
||||
const { processAadhaarQRCode } = useAadhaar();
|
||||
|
||||
const onPhotoLibraryPress = useCallback(async () => {
|
||||
if (isProcessing) {
|
||||
|
||||
131
packages/mobile-sdk-alpha/src/flows/onboarding/import-aadhaar.ts
Normal file
131
packages/mobile-sdk-alpha/src/flows/onboarding/import-aadhaar.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
// 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 { useCallback } from 'react';
|
||||
|
||||
import { extractQRDataFields, getAadharRegistrationWindow } from '@selfxyz/common/utils';
|
||||
import type { AadhaarData } from '@selfxyz/common/utils/types';
|
||||
|
||||
import { AadhaarEvents } from '../../constants/analytics';
|
||||
import { useSelfClient } from '../../context';
|
||||
import { storePassportData } from '../../documents/utils';
|
||||
import { SdkEvents } from '../../types/events';
|
||||
|
||||
export const getErrorMessages = (errorType: 'general' | 'expired') => {
|
||||
if (errorType === 'expired') {
|
||||
return {
|
||||
title: 'QR Code Has Expired',
|
||||
description:
|
||||
'You uploaded a valid Aadhaar QR code, but unfortunately it has expired. Please generate a new QR code from the mAadhaar app and try again.',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: 'There was a problem reading the code',
|
||||
description:
|
||||
'Please ensure the QR code is clear and well-lit, then try again. For best results, take a screenshot of the QR code instead of photographing it.',
|
||||
};
|
||||
};
|
||||
|
||||
export function useAadhaar() {
|
||||
const selfClient = useSelfClient();
|
||||
|
||||
const validateAAdhaarTimestamp = useCallback(
|
||||
async (timestamp: string) => {
|
||||
//timestamp is in YYYY-MM-DD HH:MM format
|
||||
selfClient.trackEvent(AadhaarEvents.TIMESTAMP_VALIDATION_STARTED);
|
||||
|
||||
const currentTimestamp = new Date().getTime();
|
||||
const timestampDate = new Date(timestamp);
|
||||
const timestampTimestamp = timestampDate.getTime();
|
||||
const diff = currentTimestamp - timestampTimestamp;
|
||||
const diffMinutes = diff / (1000 * 60);
|
||||
|
||||
const allowedWindow = await getAadharRegistrationWindow();
|
||||
const isValid = diffMinutes <= allowedWindow;
|
||||
|
||||
if (isValid) {
|
||||
selfClient.trackEvent(AadhaarEvents.TIMESTAMP_VALIDATION_SUCCESS);
|
||||
} else {
|
||||
selfClient.trackEvent(AadhaarEvents.TIMESTAMP_VALIDATION_FAILED);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
},
|
||||
[selfClient.trackEvent],
|
||||
);
|
||||
|
||||
const processAadhaarQRCode = useCallback(
|
||||
async (qrCodeData: string) => {
|
||||
try {
|
||||
if (!qrCodeData || typeof qrCodeData !== 'string' || qrCodeData.length < 100) {
|
||||
selfClient.trackEvent(AadhaarEvents.QR_CODE_INVALID_FORMAT);
|
||||
throw new Error('Invalid QR code format - too short or not a string');
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(qrCodeData)) {
|
||||
selfClient.trackEvent(AadhaarEvents.QR_CODE_INVALID_FORMAT);
|
||||
throw new Error('Invalid QR code format - not a numeric string');
|
||||
}
|
||||
|
||||
if (qrCodeData.length < 100) {
|
||||
selfClient.trackEvent(AadhaarEvents.QR_CODE_INVALID_FORMAT);
|
||||
throw new Error('QR code too short - likely not a valid Aadhaar QR code');
|
||||
}
|
||||
|
||||
selfClient.trackEvent(AadhaarEvents.QR_DATA_EXTRACTION_STARTED);
|
||||
let extractedFields;
|
||||
try {
|
||||
extractedFields = extractQRDataFields(qrCodeData);
|
||||
selfClient.trackEvent(AadhaarEvents.QR_DATA_EXTRACTION_SUCCESS);
|
||||
} catch {
|
||||
selfClient.trackEvent(AadhaarEvents.QR_CODE_PARSE_FAILED);
|
||||
throw new Error('Failed to parse Aadhaar QR code - invalid format');
|
||||
}
|
||||
|
||||
if (!extractedFields.name || !extractedFields.dob || !extractedFields.gender) {
|
||||
selfClient.trackEvent(AadhaarEvents.QR_CODE_MISSING_FIELDS);
|
||||
throw new Error('Invalid Aadhaar QR code - missing required fields');
|
||||
}
|
||||
|
||||
if (!(await validateAAdhaarTimestamp(extractedFields.timestamp))) {
|
||||
selfClient.trackEvent(AadhaarEvents.QR_CODE_EXPIRED);
|
||||
throw new Error('QRCODE_EXPIRED');
|
||||
}
|
||||
|
||||
const aadhaarData: AadhaarData = {
|
||||
documentType: 'aadhaar',
|
||||
documentCategory: 'aadhaar',
|
||||
mock: false,
|
||||
qrData: qrCodeData,
|
||||
extractedFields: extractedFields,
|
||||
signature: [],
|
||||
publicKey: '',
|
||||
photoHash: '',
|
||||
};
|
||||
|
||||
selfClient.trackEvent(AadhaarEvents.DATA_STORAGE_STARTED);
|
||||
await storePassportData(selfClient, aadhaarData);
|
||||
selfClient.trackEvent(AadhaarEvents.DATA_STORAGE_SUCCESS);
|
||||
|
||||
selfClient.trackEvent(AadhaarEvents.QR_UPLOAD_SUCCESS);
|
||||
selfClient.emit(SdkEvents.PROVING_AADHAAR_UPLOAD_SUCCESS);
|
||||
} catch (error) {
|
||||
// Check if it's a QR code expiration error
|
||||
const errorType: 'expired' | 'general' =
|
||||
error instanceof Error && error.message === 'QRCODE_EXPIRED' ? 'expired' : 'general';
|
||||
|
||||
selfClient.trackEvent(AadhaarEvents.ERROR_SCREEN_NAVIGATED, {
|
||||
errorType,
|
||||
});
|
||||
selfClient.emit(SdkEvents.PROVING_AADHAAR_UPLOAD_FAILURE, {
|
||||
errorType,
|
||||
});
|
||||
}
|
||||
},
|
||||
[selfClient, validateAAdhaarTimestamp],
|
||||
);
|
||||
|
||||
return { processAadhaarQRCode };
|
||||
}
|
||||
@@ -23,6 +23,20 @@ export enum SdkEvents {
|
||||
*/
|
||||
PROGRESS = 'PROGRESS',
|
||||
|
||||
/**
|
||||
* Emitted when Aadhaar QR code upload is successful.
|
||||
*
|
||||
* **Required:** Navigate to the AadhaarUploadSuccess screen to inform users of the successful upload.
|
||||
*/
|
||||
PROVING_AADHAAR_UPLOAD_SUCCESS = 'PROVING_AADHAAR_UPLOAD_SUCCESS',
|
||||
|
||||
/**
|
||||
* Emitted when Aadhaar QR code upload fails.
|
||||
*
|
||||
* **Required:** Navigate to the AadhaarUploadError screen to inform users of the failure and provide troubleshooting steps.
|
||||
*/
|
||||
PROVING_AADHAAR_UPLOAD_FAILURE = 'PROVING_AADHAAR_UPLOAD_FAILURE',
|
||||
|
||||
/**
|
||||
* Emitted when no passport data is found on the device during initialization.
|
||||
*
|
||||
@@ -72,7 +86,7 @@ export enum SdkEvents {
|
||||
/**
|
||||
* Emitted when a user selects a country in the document flow.
|
||||
*
|
||||
* **Recommended:** Use this event to track user selection patterns and analytics.
|
||||
* **Required:** Navigate the user to the screen where they will select the document type.
|
||||
* The event includes the selected country code and available document types.
|
||||
*/
|
||||
DOCUMENT_COUNTRY_SELECTED = 'DOCUMENT_COUNTRY_SELECTED',
|
||||
@@ -80,7 +94,7 @@ export enum SdkEvents {
|
||||
/**
|
||||
* Emitted when a user selects a document type for verification.
|
||||
*
|
||||
* **Recommended:** Use this event to track document type preferences and analytics.
|
||||
* **Required:** Navigate the user to the document type screen that was selected.
|
||||
* The event includes the selected document type, country code, and document name.
|
||||
*/
|
||||
DOCUMENT_TYPE_SELECTED = 'DOCUMENT_TYPE_SELECTED',
|
||||
@@ -153,6 +167,8 @@ export interface SDKEventMap {
|
||||
isMock: boolean;
|
||||
context: ProofContext;
|
||||
};
|
||||
[SdkEvents.PROVING_AADHAAR_UPLOAD_SUCCESS]: undefined;
|
||||
[SdkEvents.PROVING_AADHAAR_UPLOAD_FAILURE]: { errorType: 'expired' | 'general' };
|
||||
|
||||
[SdkEvents.PROGRESS]: Progress;
|
||||
[SdkEvents.ERROR]: Error;
|
||||
|
||||
Reference in New Issue
Block a user