mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
add common sdk (#537)
* add common sdk * remove sdk backend api * remove registry * regenerate sha256 rsa dsc each time * download ski-pem dynamically on staging, refactor initpassportDataParsing * add state machine for button on prove screen, improve ux on splash screen * fetch ski-pem in production * fix linter issues * fix prove screen button bugs * update podfile.lock and yarn.lock * run linter in circuits repo * bump build * bump version for sentry debugging * bump ios to version 118 --------- Co-authored-by: Justin Hernandez <transphorm@gmail.com>
This commit is contained in:
committed by
GitHub
parent
5163761a52
commit
cc169061bd
266
app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx
Normal file
266
app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx
Normal file
@@ -0,0 +1,266 @@
|
||||
import { useMachine } from '@xstate/react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ActivityIndicator, View } from 'react-native';
|
||||
import { assign, createMachine } from 'xstate';
|
||||
|
||||
import { black } from '../../utils/colors';
|
||||
import Description from '../typography/Description';
|
||||
import { HeldPrimaryButton } from './PrimaryButtonLongHold';
|
||||
|
||||
interface HeldPrimaryButtonProveScreenProps {
|
||||
onVerify: () => void;
|
||||
selectedAppSessionId: string | undefined | null;
|
||||
hasScrolledToBottom: boolean;
|
||||
isReadyToProve: boolean;
|
||||
}
|
||||
|
||||
interface ButtonContext {
|
||||
selectedAppSessionId: string | undefined | null;
|
||||
hasScrolledToBottom: boolean;
|
||||
isReadyToProve: boolean;
|
||||
onVerify: () => void;
|
||||
}
|
||||
|
||||
type ButtonEvent =
|
||||
| {
|
||||
type: 'PROPS_UPDATED';
|
||||
selectedAppSessionId: string | undefined | null;
|
||||
hasScrolledToBottom: boolean;
|
||||
isReadyToProve: boolean;
|
||||
}
|
||||
| { type: 'VERIFY' };
|
||||
|
||||
const buttonMachine = createMachine(
|
||||
{
|
||||
id: 'proveButton',
|
||||
types: {} as {
|
||||
context: ButtonContext;
|
||||
events: ButtonEvent;
|
||||
actions: { type: 'callOnVerify' } | { type: 'updateContext' };
|
||||
},
|
||||
initial: 'waitingForSession',
|
||||
context: ({ input }: { input: { onVerify: () => void } }) => ({
|
||||
selectedAppSessionId: null as string | undefined | null,
|
||||
hasScrolledToBottom: false,
|
||||
isReadyToProve: false,
|
||||
onVerify: input.onVerify,
|
||||
}),
|
||||
on: {
|
||||
PROPS_UPDATED: {
|
||||
actions: 'updateContext',
|
||||
},
|
||||
},
|
||||
states: {
|
||||
waitingForSession: {
|
||||
always: {
|
||||
target: 'needsScroll',
|
||||
guard: ({ context }) => !!context.selectedAppSessionId,
|
||||
},
|
||||
},
|
||||
needsScroll: {
|
||||
always: [
|
||||
{
|
||||
target: 'waitingForSession',
|
||||
guard: ({ context }) => !context.selectedAppSessionId,
|
||||
},
|
||||
{
|
||||
target: 'preparing',
|
||||
guard: ({ context }) => context.hasScrolledToBottom,
|
||||
},
|
||||
],
|
||||
},
|
||||
preparing: {
|
||||
always: [
|
||||
{
|
||||
target: 'waitingForSession',
|
||||
guard: ({ context }) => !context.selectedAppSessionId,
|
||||
},
|
||||
{
|
||||
target: 'needsScroll',
|
||||
guard: ({ context }) => !context.hasScrolledToBottom,
|
||||
},
|
||||
{
|
||||
target: 'ready',
|
||||
guard: ({ context }) => context.isReadyToProve,
|
||||
},
|
||||
],
|
||||
after: {
|
||||
500: { target: 'preparing2' },
|
||||
},
|
||||
},
|
||||
preparing2: {
|
||||
always: [
|
||||
{
|
||||
target: 'waitingForSession',
|
||||
guard: ({ context }) => !context.selectedAppSessionId,
|
||||
},
|
||||
{
|
||||
target: 'needsScroll',
|
||||
guard: ({ context }) => !context.hasScrolledToBottom,
|
||||
},
|
||||
{
|
||||
target: 'ready',
|
||||
guard: ({ context }) => context.isReadyToProve,
|
||||
},
|
||||
],
|
||||
after: {
|
||||
500: { target: 'preparing3' },
|
||||
},
|
||||
},
|
||||
preparing3: {
|
||||
always: [
|
||||
{
|
||||
target: 'waitingForSession',
|
||||
guard: ({ context }) => !context.selectedAppSessionId,
|
||||
},
|
||||
{
|
||||
target: 'needsScroll',
|
||||
guard: ({ context }) => !context.hasScrolledToBottom,
|
||||
},
|
||||
{
|
||||
target: 'ready',
|
||||
guard: ({ context }) => context.isReadyToProve,
|
||||
},
|
||||
],
|
||||
},
|
||||
ready: {
|
||||
on: {
|
||||
VERIFY: 'verifying',
|
||||
},
|
||||
always: [
|
||||
{
|
||||
target: 'waitingForSession',
|
||||
guard: ({ context }) => !context.selectedAppSessionId,
|
||||
},
|
||||
{
|
||||
target: 'needsScroll',
|
||||
guard: ({ context }) => !context.hasScrolledToBottom,
|
||||
},
|
||||
{
|
||||
target: 'preparing',
|
||||
guard: ({ context }) => !context.isReadyToProve,
|
||||
},
|
||||
],
|
||||
},
|
||||
verifying: {
|
||||
entry: 'callOnVerify',
|
||||
// Remove always transitions checking hasScrolledToBottom and isReadyToProve
|
||||
// Keep the button visually verifying until the component unmounts or session changes
|
||||
always: {
|
||||
target: 'waitingForSession',
|
||||
guard: ({ context }) => !context.selectedAppSessionId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
updateContext: assign(({ context, event }) => {
|
||||
if (event.type === 'PROPS_UPDATED') {
|
||||
if (
|
||||
context.selectedAppSessionId !== event.selectedAppSessionId ||
|
||||
context.hasScrolledToBottom !== event.hasScrolledToBottom ||
|
||||
context.isReadyToProve !== event.isReadyToProve
|
||||
) {
|
||||
return {
|
||||
selectedAppSessionId: event.selectedAppSessionId,
|
||||
hasScrolledToBottom: event.hasScrolledToBottom,
|
||||
isReadyToProve: event.isReadyToProve,
|
||||
};
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}),
|
||||
callOnVerify: ({ context }) => {
|
||||
context.onVerify();
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const HeldPrimaryButtonProveScreen: React.FC<
|
||||
HeldPrimaryButtonProveScreenProps
|
||||
> = ({
|
||||
onVerify,
|
||||
selectedAppSessionId,
|
||||
hasScrolledToBottom,
|
||||
isReadyToProve,
|
||||
}) => {
|
||||
const [state, send] = useMachine(buttonMachine, {
|
||||
input: { onVerify },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
send({
|
||||
type: 'PROPS_UPDATED',
|
||||
selectedAppSessionId,
|
||||
hasScrolledToBottom,
|
||||
isReadyToProve,
|
||||
});
|
||||
}, [selectedAppSessionId, hasScrolledToBottom, isReadyToProve, send]);
|
||||
|
||||
const isDisabled = !state.matches('ready');
|
||||
|
||||
const renderButtonContent = () => {
|
||||
if (state.matches('waitingForSession')) {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<ActivityIndicator color={black} style={{ marginRight: 8 }} />
|
||||
<Description color={black}>Waiting for app...</Description>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (state.matches('needsScroll')) {
|
||||
return 'Please read all disclosures';
|
||||
}
|
||||
if (state.matches('preparing')) {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<ActivityIndicator color={black} style={{ marginRight: 8 }} />
|
||||
<Description color={black}>Accessing to Keychain data</Description>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (state.matches('preparing2')) {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<ActivityIndicator color={black} style={{ marginRight: 8 }} />
|
||||
<Description color={black}>Parsing passport data</Description>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (state.matches('preparing3')) {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<ActivityIndicator color={black} style={{ marginRight: 8 }} />
|
||||
<Description color={black}>Preparing for verification</Description>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (state.matches('ready')) {
|
||||
return 'Hold to verify';
|
||||
}
|
||||
if (state.matches('verifying')) {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<ActivityIndicator color={black} style={{ marginRight: 8 }} />
|
||||
<Description color={black}>Generating proof</Description>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<HeldPrimaryButton
|
||||
onLongPress={() => {
|
||||
if (state.matches('ready')) {
|
||||
send({ type: 'VERIFY' });
|
||||
}
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{renderButtonContent()}
|
||||
</HeldPrimaryButton>
|
||||
);
|
||||
};
|
||||
@@ -16,9 +16,9 @@ const ACTION_TIMER = 600; // time in ms
|
||||
const COLORS: RGBA[] = ['rgba(30, 41, 59, 0.3)', 'rgba(30, 41, 59, 1)'];
|
||||
export function HeldPrimaryButton({
|
||||
children,
|
||||
onPress,
|
||||
onLongPress,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
}: ButtonProps & { onLongPress: () => void }) {
|
||||
const animation = useAnimatedValue(0);
|
||||
const [hasTriggered, setHasTriggered] = useState(false);
|
||||
const [size, setSize] = useState({ width: 0, height: 0 });
|
||||
@@ -68,14 +68,13 @@ export function HeldPrimaryButton({
|
||||
animation.addListener(({ value }) => {
|
||||
if (value >= 0.95 && !hasTriggered) {
|
||||
setHasTriggered(true);
|
||||
// @ts-expect-error
|
||||
onPress();
|
||||
onLongPress();
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
animation.removeAllListeners();
|
||||
};
|
||||
}, [animation, hasTriggered]);
|
||||
}, [animation, hasTriggered, onLongPress]);
|
||||
|
||||
return (
|
||||
<PrimaryButton
|
||||
|
||||
@@ -19,7 +19,8 @@ import {
|
||||
} from 'tamagui';
|
||||
|
||||
import { countryCodes } from '../../../common/src/constants/constants';
|
||||
import { genMockPassportData } from '../../../common/src/utils/passports/genMockPassportData';
|
||||
import { getSKIPEM } from '../../../common/src/utils/csca';
|
||||
import { genAndInitMockPassportData } from '../../../common/src/utils/passports/genMockPassportData';
|
||||
import { initPassportDataParsing } from '../../../common/src/utils/passports/passport';
|
||||
import { PrimaryButton } from '../components/buttons/PrimaryButton';
|
||||
import { SecondaryButton } from '../components/buttons/SecondaryButton';
|
||||
@@ -170,7 +171,7 @@ const MockDataScreen: React.FC<MockDataScreenProps> = ({}) => {
|
||||
];
|
||||
|
||||
if (isInOfacList) {
|
||||
mockPassportData = genMockPassportData(
|
||||
mockPassportData = genAndInitMockPassportData(
|
||||
hashFunction1,
|
||||
hashFunction2,
|
||||
signatureAlgorithm,
|
||||
@@ -183,7 +184,7 @@ const MockDataScreen: React.FC<MockDataScreenProps> = ({}) => {
|
||||
'ARCANGEL DE JESUS',
|
||||
);
|
||||
} else {
|
||||
mockPassportData = genMockPassportData(
|
||||
mockPassportData = genAndInitMockPassportData(
|
||||
hashFunction1,
|
||||
hashFunction2,
|
||||
signatureAlgorithm,
|
||||
@@ -193,7 +194,8 @@ const MockDataScreen: React.FC<MockDataScreenProps> = ({}) => {
|
||||
randomPassportNumber,
|
||||
);
|
||||
}
|
||||
mockPassportData = initPassportDataParsing(mockPassportData);
|
||||
const skiPem = await getSKIPEM('staging');
|
||||
mockPassportData = initPassportDataParsing(mockPassportData, skiPem);
|
||||
await storePassportData(mockPassportData);
|
||||
resolve(null);
|
||||
}, 0),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { StaticScreenProps, usePreventRemove } from '@react-navigation/native';
|
||||
import LottieView from 'lottie-react-native';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ActivityIndicator, View } from 'react-native';
|
||||
|
||||
import successAnimation from '../../assets/animations/loading/success.json';
|
||||
import { PrimaryButton } from '../../components/buttons/PrimaryButton';
|
||||
@@ -30,6 +31,8 @@ const ConfirmBelongingScreen: React.FC<ConfirmBelongingScreenProps> = ({
|
||||
},
|
||||
});
|
||||
const provingStore = useProvingStore();
|
||||
const currentState = useProvingStore(state => state.currentState);
|
||||
const isReadyToProve = currentState === 'ready_to_prove';
|
||||
|
||||
useEffect(() => {
|
||||
notificationSuccess();
|
||||
@@ -78,7 +81,16 @@ const ConfirmBelongingScreen: React.FC<ConfirmBelongingScreenProps> = ({
|
||||
By continuing, you certify that this passport belongs to you and is
|
||||
not stolen or forged.
|
||||
</Description>
|
||||
<PrimaryButton onPress={onOkPress}>Confirm</PrimaryButton>
|
||||
<PrimaryButton onPress={onOkPress} disabled={!isReadyToProve}>
|
||||
{isReadyToProve ? (
|
||||
'Confirm'
|
||||
) : (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<ActivityIndicator color={black} style={{ marginRight: 8 }} />
|
||||
<Description color={black}>Preparing verification</Description>
|
||||
</View>
|
||||
)}
|
||||
</PrimaryButton>
|
||||
</ExpandableBottomLayout.BottomSection>
|
||||
</ExpandableBottomLayout.Layout>
|
||||
</>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import NfcManager from 'react-native-nfc-manager';
|
||||
import { Image } from 'tamagui';
|
||||
|
||||
import { getSKIPEM } from '../../../../common/src/utils/csca';
|
||||
import { initPassportDataParsing } from '../../../../common/src/utils/passports/passport';
|
||||
import { PassportData } from '../../../../common/src/utils/types';
|
||||
import passportVerifyAnimation from '../../assets/animations/passport_verify.json';
|
||||
@@ -77,7 +78,6 @@ const PassportNFCScanScreen: React.FC<PassportNFCScanScreenProps> = ({}) => {
|
||||
buttonTap();
|
||||
if (isNfcEnabled) {
|
||||
setIsNfcSheetOpen(true);
|
||||
|
||||
// Add timestamp when scan starts
|
||||
const scanStartTime = Date.now();
|
||||
|
||||
@@ -112,7 +112,8 @@ const PassportNFCScanScreen: React.FC<PassportNFCScanScreenProps> = ({}) => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
parsedPassportData = initPassportDataParsing(passportData);
|
||||
const skiPem = await getSKIPEM('production');
|
||||
parsedPassportData = initPassportDataParsing(passportData, skiPem);
|
||||
const passportMetadata = parsedPassportData.passportMetadata!;
|
||||
trackEvent('Passport Parsed', {
|
||||
success: true,
|
||||
|
||||
@@ -48,37 +48,37 @@ const ProofHistoryDetailScreen: React.FC<ProofHistoryDetailScreenProps> = ({
|
||||
const result: string[] = [];
|
||||
|
||||
Object.entries(parsedDisclosures).forEach(([key, value]) => {
|
||||
if (key == DisclosureType.MINIMUM_AGE && value) {
|
||||
if (key === DisclosureType.MINIMUM_AGE && value) {
|
||||
result.push(`Age is over ${value}`);
|
||||
}
|
||||
if (key == DisclosureType.NAME && value) {
|
||||
if (key === DisclosureType.NAME && value) {
|
||||
result.push(`Disclosed Name to ${data.appName}`);
|
||||
}
|
||||
if (key == DisclosureType.OFAC && value) {
|
||||
if (key === DisclosureType.OFAC && value) {
|
||||
result.push(`Not on the OFAC list`);
|
||||
}
|
||||
if (key == DisclosureType.AGE && value) {
|
||||
if (key === DisclosureType.AGE && value) {
|
||||
result.push(`Disclosed Age to ${data.appName}`);
|
||||
}
|
||||
if (key == DisclosureType.ISSUING_STATE && value) {
|
||||
if (key === DisclosureType.ISSUING_STATE && value) {
|
||||
result.push(`Disclosed Issuing State to ${data.appName}`);
|
||||
}
|
||||
if (key == DisclosureType.PASSPORT_NUMBER && value) {
|
||||
if (key === DisclosureType.PASSPORT_NUMBER && value) {
|
||||
result.push(`Disclosed Passport Number to ${data.appName}`);
|
||||
}
|
||||
if (key == DisclosureType.NATIONALITY && value) {
|
||||
if (key === DisclosureType.NATIONALITY && value) {
|
||||
result.push(`Disclosed Nationality to ${data.appName}`);
|
||||
}
|
||||
if (key == DisclosureType.DATE_OF_BIRTH && value) {
|
||||
if (key === DisclosureType.DATE_OF_BIRTH && value) {
|
||||
result.push(`Disclosed Date of Birth to ${data.appName}`);
|
||||
}
|
||||
if (key == DisclosureType.GENDER && value) {
|
||||
if (key === DisclosureType.GENDER && value) {
|
||||
result.push(`Disclosed Gender to ${data.appName}`);
|
||||
}
|
||||
if (key == DisclosureType.EXPIRY_DATE && value) {
|
||||
if (key === DisclosureType.EXPIRY_DATE && value) {
|
||||
result.push(`Disclosed Expiry Date to ${data.appName}`);
|
||||
}
|
||||
if (key == DisclosureType.EXCLUDED_COUNTRIES) {
|
||||
if (key === DisclosureType.EXCLUDED_COUNTRIES) {
|
||||
if (value && Array.isArray(value) && value.length > 0) {
|
||||
result.push(`Disclosed - Not from excluded countries`);
|
||||
}
|
||||
@@ -98,9 +98,9 @@ const ProofHistoryDetailScreen: React.FC<ProofHistoryDetailScreenProps> = ({
|
||||
}, [data.timestamp]);
|
||||
|
||||
const proofStatus = useMemo(() => {
|
||||
if (data.status == 'success') {
|
||||
if (data.status === 'success') {
|
||||
return 'PROOF GRANTED';
|
||||
} else if (data.status == ProofStatus.PENDING) {
|
||||
} else if (data.status === ProofStatus.PENDING) {
|
||||
return 'PROOF PENDING';
|
||||
} else {
|
||||
return 'PROOF FAILED';
|
||||
@@ -128,8 +128,8 @@ const ProofHistoryDetailScreen: React.FC<ProofHistoryDetailScreenProps> = ({
|
||||
const isEthereumAddress = useMemo(() => {
|
||||
return (
|
||||
/^0x[a-fA-F0-9]+$/.test(data.userId) &&
|
||||
(data.endpointType == 'staging_celo' || data.endpointType == 'celo') &&
|
||||
data.userIdType == 'hex'
|
||||
(data.endpointType === 'staging_celo' || data.endpointType === 'celo') &&
|
||||
data.userIdType === 'hex'
|
||||
);
|
||||
}, [data.userId, data.endpointType, data.userIdType]);
|
||||
|
||||
@@ -263,7 +263,9 @@ const ProofHistoryDetailScreen: React.FC<ProofHistoryDetailScreenProps> = ({
|
||||
>
|
||||
<YStack
|
||||
backgroundColor={
|
||||
data.status == ProofStatus.SUCCESS ? emerald500 : red500
|
||||
data.status === ProofStatus.SUCCESS
|
||||
? emerald500
|
||||
: red500
|
||||
}
|
||||
width={8}
|
||||
height={8}
|
||||
|
||||
@@ -219,8 +219,8 @@ const ProofHistoryScreen: React.FC = () => {
|
||||
{formatDate(item.timestamp)}
|
||||
</BodyText>
|
||||
</YStack>
|
||||
{(item.endpointType == 'staging_celo' ||
|
||||
item.endpointType == 'celo') && (
|
||||
{(item.endpointType === 'staging_celo' ||
|
||||
item.endpointType === 'celo') && (
|
||||
<XStack
|
||||
backgroundColor={blue100}
|
||||
paddingVertical={2}
|
||||
|
||||
@@ -45,8 +45,10 @@ const SuccessScreen: React.FC = () => {
|
||||
|
||||
function onOkPress() {
|
||||
buttonTap();
|
||||
cleanSelfApp();
|
||||
goHome();
|
||||
setTimeout(() => {
|
||||
cleanSelfApp();
|
||||
}, 2000); // Wait 2 seconds to user coming back to the home screen. If we don't wait the appname will change and user will see it.
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -19,7 +19,7 @@ import { Image, Text, View, YStack } from 'tamagui';
|
||||
import { SelfAppDisclosureConfig } from '../../../../common/src/utils/appType';
|
||||
import { formatEndpoint } from '../../../../common/src/utils/scope';
|
||||
import miscAnimation from '../../assets/animations/loading/misc.json';
|
||||
import { HeldPrimaryButton } from '../../components/buttons/PrimaryButtonLongHold';
|
||||
import { HeldPrimaryButtonProveScreen } from '../../components/buttons/HeldPrimaryButtonProveScreen';
|
||||
import Disclosures from '../../components/Disclosures';
|
||||
import { BodyText } from '../../components/typography/BodyText';
|
||||
import { Caption } from '../../components/typography/Caption';
|
||||
@@ -48,7 +48,11 @@ const ProveScreen: React.FC = () => {
|
||||
() => scrollViewContentHeight <= scrollViewHeight,
|
||||
[scrollViewContentHeight, scrollViewHeight],
|
||||
);
|
||||
|
||||
const provingStore = useProvingStore();
|
||||
const currentState = useProvingStore(state => state.currentState);
|
||||
const isReadyToProve = currentState === 'ready_to_prove';
|
||||
|
||||
const { addProofHistory } = useProofHistoryStore();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -129,7 +133,7 @@ const ProveScreen: React.FC = () => {
|
||||
buttonTap();
|
||||
setTimeout(() => {
|
||||
navigate('ProofRequestStatusScreen');
|
||||
}, 200);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const handleScroll = useCallback(
|
||||
@@ -227,14 +231,12 @@ const ProveScreen: React.FC = () => {
|
||||
</Caption>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<HeldPrimaryButton
|
||||
onPress={onVerify}
|
||||
disabled={!selectedApp?.sessionId || !hasScrolledToBottom}
|
||||
>
|
||||
{hasScrolledToBottom
|
||||
? 'Hold To Verify'
|
||||
: 'Please read all disclosures'}
|
||||
</HeldPrimaryButton>
|
||||
<HeldPrimaryButtonProveScreen
|
||||
onVerify={onVerify}
|
||||
selectedAppSessionId={selectedApp?.sessionId}
|
||||
hasScrolledToBottom={hasScrolledToBottom}
|
||||
isReadyToProve={isReadyToProve}
|
||||
/>
|
||||
</ExpandableBottomLayout.BottomSection>
|
||||
</ExpandableBottomLayout.Layout>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
YStack,
|
||||
} from 'tamagui';
|
||||
|
||||
import { genMockPassportData } from '../../../../common/src/utils/passports/genMockPassportData';
|
||||
import { genAndInitMockPassportData } from '../../../../common/src/utils/passports/genMockPassportData';
|
||||
import { RootStackParamList } from '../../Navigation';
|
||||
import {
|
||||
unsafe_clearSecrets,
|
||||
@@ -160,7 +160,7 @@ const DevSettingsScreen: React.FC<DevSettingsScreenProps> = ({}) => {
|
||||
}
|
||||
|
||||
function handleGenerateMockPassportData() {
|
||||
const passportData = genMockPassportData(
|
||||
const passportData = genAndInitMockPassportData(
|
||||
'sha256',
|
||||
'sha256',
|
||||
'rsa_sha256_65537_2048',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import LottieView from 'lottie-react-native';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { PassportData } from '../../../common/src/utils/types';
|
||||
@@ -17,60 +17,82 @@ const SplashScreen: React.FC = ({}) => {
|
||||
const navigation = useNavigation();
|
||||
const { checkBiometricsAvailable } = useAuth();
|
||||
const { setBiometricsAvailable } = useSettingStore();
|
||||
const [isAnimationFinished, setIsAnimationFinished] = React.useState(false);
|
||||
const [nextScreen, setNextScreen] = React.useState<string | null>(null);
|
||||
const dataLoadInitiatedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
checkBiometricsAvailable()
|
||||
.then(setBiometricsAvailable)
|
||||
.catch(err => {
|
||||
console.warn('Error checking biometrics availability', err);
|
||||
navigation.navigate('Launch');
|
||||
throw new Error(`Error checking biometrics availability: ${err}`);
|
||||
});
|
||||
}, [navigation]);
|
||||
if (!dataLoadInitiatedRef.current) {
|
||||
dataLoadInitiatedRef.current = true;
|
||||
console.log('Starting data loading and validation...');
|
||||
|
||||
checkBiometricsAvailable()
|
||||
.then(setBiometricsAvailable)
|
||||
.catch(err => {
|
||||
console.warn('Error checking biometrics availability', err);
|
||||
});
|
||||
|
||||
const loadDataAndDetermineNextScreen = async () => {
|
||||
try {
|
||||
const passportDataAndSecret = await loadPassportDataAndSecret();
|
||||
|
||||
if (!passportDataAndSecret) {
|
||||
setNextScreen('Launch');
|
||||
return;
|
||||
}
|
||||
|
||||
const { passportData, secret } = JSON.parse(passportDataAndSecret);
|
||||
if (!isPassportDataValid(passportData)) {
|
||||
setNextScreen('Launch');
|
||||
return;
|
||||
}
|
||||
const environment =
|
||||
(passportData as PassportData).documentType &&
|
||||
(passportData as PassportData).documentType !== 'passport'
|
||||
? 'stg'
|
||||
: 'prod';
|
||||
await useProtocolStore.getState().passport.fetch_all(environment);
|
||||
const isRegistered = await isUserRegistered(passportData, secret);
|
||||
console.log('User is registered:', isRegistered);
|
||||
if (isRegistered) {
|
||||
console.log(
|
||||
'Passport is registered already. Setting next screen to Home',
|
||||
);
|
||||
setNextScreen('Home');
|
||||
return;
|
||||
}
|
||||
// Currently, we dont check isPassportNullified(passportData);
|
||||
// This could lead to AccountRecoveryChoice just like in LoadingScreen
|
||||
// But it looks better right now to keep the LaunchScreen flow
|
||||
// In case user wants to try with another passport.
|
||||
// Long term, we could also show a modal instead that prompts the user to recover or scan a new passport.
|
||||
|
||||
// Rest of the time, keep the LaunchScreen flow
|
||||
|
||||
setNextScreen('Launch');
|
||||
} catch (error) {
|
||||
console.error(`Error in SplashScreen data loading: ${error}`);
|
||||
setNextScreen('Launch');
|
||||
}
|
||||
};
|
||||
|
||||
loadDataAndDetermineNextScreen();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAnimationFinish = useCallback(() => {
|
||||
try {
|
||||
setTimeout(async () => {
|
||||
impactLight();
|
||||
const passportDataAndSecret = await loadPassportDataAndSecret();
|
||||
impactLight();
|
||||
setIsAnimationFinished(true);
|
||||
}, []);
|
||||
|
||||
if (!passportDataAndSecret) {
|
||||
navigation.navigate('Launch');
|
||||
return;
|
||||
}
|
||||
|
||||
const { passportData, secret } = JSON.parse(passportDataAndSecret);
|
||||
if (!isPassportDataValid(passportData)) {
|
||||
navigation.navigate('Launch');
|
||||
return;
|
||||
}
|
||||
const environment =
|
||||
(passportData as PassportData).documentType &&
|
||||
(passportData as PassportData).documentType !== 'passport'
|
||||
? 'stg'
|
||||
: 'prod';
|
||||
await useProtocolStore.getState().passport.fetch_all(environment);
|
||||
const isRegistered = await isUserRegistered(passportData, secret);
|
||||
console.log('User is registered:', isRegistered);
|
||||
if (isRegistered) {
|
||||
console.log('Passport is registered already. Skipping to HomeScreen');
|
||||
navigation.navigate('Home');
|
||||
return;
|
||||
}
|
||||
// Currently, we dont check isPassportNullified(passportData);
|
||||
// This could lead to AccountRecoveryChoice just like in LoadingScreen
|
||||
// But it looks better right now to keep the LaunchScreen flow
|
||||
// In case user wants to try with another passport.
|
||||
// Long term, we could also show a modal instead that prompts the user to recover or scan a new passport.
|
||||
|
||||
// Rest of the time, keep the LaunchScreen flow
|
||||
navigation.navigate('Launch');
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
navigation.navigate('Launch');
|
||||
throw new Error(`Error in SplashScreen: ${error}`);
|
||||
useEffect(() => {
|
||||
if (isAnimationFinished && nextScreen) {
|
||||
console.log(`Navigating to ${nextScreen}`);
|
||||
requestAnimationFrame(() => {
|
||||
navigation.navigate(nextScreen as any);
|
||||
});
|
||||
}
|
||||
}, [navigation]);
|
||||
}, [isAnimationFinished, nextScreen, navigation]);
|
||||
|
||||
return (
|
||||
<LottieView
|
||||
|
||||
@@ -15,7 +15,7 @@ interface ProtocolState {
|
||||
passport: {
|
||||
commitment_tree: any;
|
||||
dsc_tree: any;
|
||||
csca_tree: any;
|
||||
csca_tree: string[][] | null;
|
||||
deployed_circuits: any;
|
||||
circuits_dns_mapping: any;
|
||||
fetch_deployed_circuits: (environment: 'prod' | 'stg') => Promise<void>;
|
||||
@@ -44,45 +44,106 @@ export const useProtocolStore = create<ProtocolState>((set, get) => ({
|
||||
]);
|
||||
},
|
||||
fetch_deployed_circuits: async (environment: 'prod' | 'stg') => {
|
||||
const response = await fetch(
|
||||
`${
|
||||
environment === 'prod' ? API_URL : API_URL_STAGING
|
||||
}/deployed-circuits`,
|
||||
);
|
||||
const data = await response.json();
|
||||
set({ passport: { ...get().passport, deployed_circuits: data.data } });
|
||||
const url = `${environment === 'prod' ? API_URL : API_URL_STAGING}/deployed-circuits`;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`HTTP error fetching ${url}! status: ${response.status}`,
|
||||
);
|
||||
}
|
||||
const responseText = await response.text();
|
||||
const data = JSON.parse(responseText);
|
||||
set({ passport: { ...get().passport, deployed_circuits: data.data } });
|
||||
} catch (error) {
|
||||
console.error(`Failed fetching deployed circuits from ${url}:`, error);
|
||||
// Optionally handle error state
|
||||
}
|
||||
},
|
||||
fetch_circuits_dns_mapping: async (environment: 'prod' | 'stg') => {
|
||||
const response = await fetch(
|
||||
`${
|
||||
environment === 'prod' ? API_URL : API_URL_STAGING
|
||||
}/circuit-dns-mapping`,
|
||||
);
|
||||
const data = await response.json();
|
||||
set({ passport: { ...get().passport, circuits_dns_mapping: data.data } });
|
||||
const url = `${environment === 'prod' ? API_URL : API_URL_STAGING}/circuit-dns-mapping`;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`HTTP error fetching ${url}! status: ${response.status}`,
|
||||
);
|
||||
}
|
||||
const responseText = await response.text();
|
||||
const data = JSON.parse(responseText);
|
||||
set({
|
||||
passport: { ...get().passport, circuits_dns_mapping: data.data },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed fetching circuit DNS mapping from ${url}:`,
|
||||
error,
|
||||
);
|
||||
// Optionally handle error state
|
||||
}
|
||||
},
|
||||
fetch_csca_tree: async (environment: 'prod' | 'stg') => {
|
||||
const response = await fetch(
|
||||
`${environment === 'prod' ? CSCA_TREE_URL : CSCA_TREE_URL_STAGING}`,
|
||||
);
|
||||
const data = await response.json();
|
||||
set({ passport: { ...get().passport, csca_tree: data.data } });
|
||||
const url =
|
||||
environment === 'prod' ? CSCA_TREE_URL : CSCA_TREE_URL_STAGING;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`HTTP error fetching ${url}! status: ${response.status}`,
|
||||
);
|
||||
}
|
||||
const responseText = await response.text();
|
||||
const rawData = JSON.parse(responseText);
|
||||
|
||||
let treeData: any;
|
||||
if (rawData && rawData.data) {
|
||||
treeData =
|
||||
typeof rawData.data === 'string'
|
||||
? JSON.parse(rawData.data)
|
||||
: rawData.data;
|
||||
} else {
|
||||
treeData = rawData; // Assume rawData is the tree if no .data field
|
||||
}
|
||||
set({ passport: { ...get().passport, csca_tree: treeData } });
|
||||
} catch (error) {
|
||||
console.error(`Failed fetching CSCA tree from ${url}:`, error);
|
||||
set({ passport: { ...get().passport, csca_tree: null } }); // Reset on error
|
||||
}
|
||||
},
|
||||
fetch_dsc_tree: async (environment: 'prod' | 'stg') => {
|
||||
const response = await fetch(
|
||||
`${environment === 'prod' ? DSC_TREE_URL : DSC_TREE_URL_STAGING}`,
|
||||
);
|
||||
const data = await response.json();
|
||||
set({ passport: { ...get().passport, dsc_tree: data.data } });
|
||||
const url = environment === 'prod' ? DSC_TREE_URL : DSC_TREE_URL_STAGING;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`HTTP error fetching ${url}! status: ${response.status}`,
|
||||
);
|
||||
}
|
||||
const responseText = await response.text();
|
||||
const data = JSON.parse(responseText);
|
||||
set({ passport: { ...get().passport, dsc_tree: data.data } });
|
||||
} catch (error) {
|
||||
console.error(`Failed fetching DSC tree from ${url}:`, error);
|
||||
// Optionally handle error state
|
||||
}
|
||||
},
|
||||
fetch_identity_tree: async (environment: 'prod' | 'stg') => {
|
||||
const response = await fetch(
|
||||
`${
|
||||
environment === 'prod' ? IDENTITY_TREE_URL : IDENTITY_TREE_URL_STAGING
|
||||
}`,
|
||||
);
|
||||
const data = await response.json();
|
||||
set({ passport: { ...get().passport, commitment_tree: data.data } });
|
||||
const url =
|
||||
environment === 'prod' ? IDENTITY_TREE_URL : IDENTITY_TREE_URL_STAGING;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`HTTP error fetching ${url}! status: ${response.status}`,
|
||||
);
|
||||
}
|
||||
const responseText = await response.text();
|
||||
const data = JSON.parse(responseText);
|
||||
set({ passport: { ...get().passport, commitment_tree: data.data } });
|
||||
} catch (error) {
|
||||
console.error(`Failed fetching identity tree from ${url}:`, error);
|
||||
// Optionally handle error state
|
||||
}
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -40,7 +40,7 @@ export function generateTEEInputsDSC(
|
||||
passportData: PassportData,
|
||||
cscaTree: string[][],
|
||||
) {
|
||||
const inputs = generateCircuitInputsDSC(passportData.dsc, cscaTree);
|
||||
const inputs = generateCircuitInputsDSC(passportData, cscaTree);
|
||||
const circuitName = getCircuitNameFromPassportData(passportData, 'dsc');
|
||||
const endpointType =
|
||||
passportData.documentType && passportData.documentType !== 'passport'
|
||||
@@ -78,7 +78,7 @@ export function generateTEEInputsDisclose(
|
||||
|
||||
const { passportNoAndNationalitySMT, nameAndDobSMT, nameAndYobSMT } =
|
||||
getOfacSMTs();
|
||||
const serialized_tree = useProtocolStore.getState().passport.commitment_tree; //await getCommitmentTree(passportData.documentType);
|
||||
const serialized_tree = useProtocolStore.getState().passport.commitment_tree;
|
||||
const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serialized_tree);
|
||||
|
||||
const inputs = generateCircuitInputsVCandDisclose(
|
||||
|
||||
@@ -124,7 +124,10 @@ interface ProvingState {
|
||||
error_code: string | null;
|
||||
reason: string | null;
|
||||
endpointType: EndpointType | null;
|
||||
init: (circuitType: 'dsc' | 'disclose' | 'register') => Promise<void>;
|
||||
init: (
|
||||
circuitType: 'dsc' | 'disclose' | 'register',
|
||||
userConfirmed?: boolean,
|
||||
) => Promise<void>;
|
||||
startFetchingData: () => Promise<void>;
|
||||
validatingDocument: () => Promise<void>;
|
||||
initTeeConnection: () => Promise<boolean>;
|
||||
@@ -432,7 +435,10 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
}
|
||||
},
|
||||
|
||||
init: async (circuitType: 'dsc' | 'disclose' | 'register') => {
|
||||
init: async (
|
||||
circuitType: 'dsc' | 'disclose' | 'register',
|
||||
userConfirmed: boolean = false,
|
||||
) => {
|
||||
get()._closeConnections();
|
||||
|
||||
if (actor) {
|
||||
@@ -450,7 +456,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
wsConnection: null,
|
||||
socketConnection: null,
|
||||
uuid: null,
|
||||
userConfirmed: false,
|
||||
userConfirmed: userConfirmed,
|
||||
passportData: null,
|
||||
secret: null,
|
||||
circuitType,
|
||||
@@ -641,7 +647,9 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
_checkActorInitialized(actor);
|
||||
const { circuitType } = get();
|
||||
if (circuitType === 'dsc') {
|
||||
get().init('register');
|
||||
setTimeout(() => {
|
||||
get().init('register', true);
|
||||
}, 1500);
|
||||
} else if (circuitType === 'register') {
|
||||
actor!.send({ type: 'COMPLETED' });
|
||||
} else if (circuitType === 'disclose') {
|
||||
@@ -700,7 +708,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
({ inputs, circuitName, endpointType, endpoint } =
|
||||
generateTEEInputsDSC(
|
||||
passportData,
|
||||
protocolStore.passport.csca_tree,
|
||||
protocolStore.passport.csca_tree as string[][],
|
||||
));
|
||||
break;
|
||||
case 'disclose':
|
||||
|
||||
Reference in New Issue
Block a user