mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-09 13:38:01 -05:00
Merge remote-tracking branch 'idpass/develop' into develop-idpass-sync
This commit is contained in:
2
.env
2
.env
@@ -1,3 +1,3 @@
|
||||
MIMOTO_HOST=https://api.qa4.mosip.net/residentmobileapp
|
||||
|
||||
#MIMOTO_HOST=http://mock.mimoto.newlogic.dev
|
||||
GOOGLE_NEARBY_MESSAGES_API_KEY=
|
||||
@@ -14,6 +14,10 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
|
||||
item.onPress();
|
||||
};
|
||||
|
||||
const filteredItems = (idType: string, items: Item[]) => {
|
||||
return items.filter((item) => !item.idType || item.idType === idType);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => {
|
||||
return (
|
||||
<View
|
||||
@@ -51,7 +55,7 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
|
||||
content={
|
||||
<View>
|
||||
<FlatList
|
||||
data={props.items}
|
||||
data={filteredItems(props.idType, props.items)}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item, index) => index.toString()}
|
||||
/>
|
||||
@@ -63,11 +67,13 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
|
||||
);
|
||||
};
|
||||
interface Item {
|
||||
idType?: string;
|
||||
label: string;
|
||||
onPress?: () => void;
|
||||
}
|
||||
|
||||
interface DropdownProps {
|
||||
idType: string;
|
||||
icon: string;
|
||||
items: Item[];
|
||||
}
|
||||
|
||||
87
components/Message.tsx
Normal file
87
components/Message.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Dimensions, StyleSheet, View } from 'react-native';
|
||||
import { LinearProgress } from 'react-native-elements';
|
||||
import { Button, Centered, Column, Text } from './ui';
|
||||
import { Colors, elevation } from './ui/styleUtils';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
...elevation(5),
|
||||
backgroundColor: Colors.White,
|
||||
padding: 0,
|
||||
},
|
||||
button: {
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
},
|
||||
viewContainer: {
|
||||
backgroundColor: 'rgba(0,0,0,.6)',
|
||||
width: Dimensions.get('screen').width,
|
||||
height: Dimensions.get('screen').height,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
zIndex: 9,
|
||||
},
|
||||
boxContainer: {
|
||||
backgroundColor: Colors.White,
|
||||
padding: 24,
|
||||
elevation: 6,
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
|
||||
export const Message: React.FC<MessageProps> = (props) => {
|
||||
const { t } = useTranslation('common');
|
||||
return (
|
||||
<View style={styles.viewContainer} onTouchStart={props.onBackdropPress}>
|
||||
<Centered fill>
|
||||
<Column
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
style={styles.boxContainer}>
|
||||
<Column>
|
||||
{props.title && (
|
||||
<Text weight="semibold" margin="0 0 12 0">
|
||||
{props.title}
|
||||
</Text>
|
||||
)}
|
||||
{props.message && <Text margin="0 0 12 0">{props.message}</Text>}
|
||||
{props.progress && <Progress progress={props.progress} />}
|
||||
{props.hint && (
|
||||
<Text size="smaller" color={Colors.Grey} margin={[4, 0, 0, 0]}>
|
||||
{props.hint}
|
||||
</Text>
|
||||
)}
|
||||
</Column>
|
||||
{props.onCancel && (
|
||||
<Button
|
||||
title={t('cancel')}
|
||||
onPress={props.onCancel}
|
||||
styles={styles.button}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
</Centered>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const Progress: React.FC<Pick<MessageProps, 'progress'>> = (props) => {
|
||||
return typeof props.progress === 'boolean' ? (
|
||||
props.progress && (
|
||||
<LinearProgress variant="indeterminate" color={Colors.Orange} />
|
||||
)
|
||||
) : (
|
||||
<LinearProgress variant="determinate" value={props.progress} />
|
||||
);
|
||||
};
|
||||
|
||||
export interface MessageProps {
|
||||
title?: string;
|
||||
message?: string;
|
||||
progress?: boolean | number;
|
||||
hint?: string;
|
||||
onCancel?: () => void;
|
||||
onBackdropPress?: () => void;
|
||||
}
|
||||
@@ -1,46 +1,79 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Dimensions, StyleSheet } from 'react-native';
|
||||
import { Overlay, LinearProgress } from 'react-native-elements';
|
||||
import { Column, Text } from './ui';
|
||||
import { Button, Column, Text } from './ui';
|
||||
import { Theme } from './ui/styleUtils';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
...Theme.elevation(5),
|
||||
backgroundColor: Theme.Colors.whiteBackgroundColor,
|
||||
padding: 0,
|
||||
},
|
||||
button: {
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
},
|
||||
});
|
||||
|
||||
export const MessageOverlay: React.FC<MessageOverlayProps> = (props) => {
|
||||
const { t } = useTranslation('common');
|
||||
return (
|
||||
<Overlay
|
||||
isVisible={props.isVisible}
|
||||
overlayStyle={styles.overlay}
|
||||
onBackdropPress={props.onBackdropPress}
|
||||
onShow={props.onShow}>
|
||||
<Column padding="24" width={Dimensions.get('screen').width * 0.8}>
|
||||
{props.title && (
|
||||
<Text weight="semibold" margin="0 0 12 0">
|
||||
{props.title}
|
||||
</Text>
|
||||
)}
|
||||
{props.message && <Text margin="0 0 12 0">{props.message}</Text>}
|
||||
{props.hasProgress && (
|
||||
<LinearProgress
|
||||
variant="indeterminate"
|
||||
color={Theme.Colors.Loading}
|
||||
onShow={props.onShow}
|
||||
onBackdropPress={props.onBackdropPress}>
|
||||
<Column width={Dimensions.get('screen').width * 0.8}>
|
||||
<Column padding="24">
|
||||
{props.title && (
|
||||
<Text weight="semibold" margin="0 0 12 0">
|
||||
{props.title}
|
||||
</Text>
|
||||
)}
|
||||
{props.message && <Text margin="0 0 12 0">{props.message}</Text>}
|
||||
{props.progress && <Progress progress={props.progress} />}
|
||||
{props.hint && (
|
||||
<Text
|
||||
size="smaller"
|
||||
color={Theme.Colors.textLabel}
|
||||
margin={[4, 0, 0, 0]}>
|
||||
{props.hint}
|
||||
</Text>
|
||||
)}
|
||||
{props.children}
|
||||
</Column>
|
||||
{!props.children && props.onCancel ? (
|
||||
<Button
|
||||
title={t('cancel')}
|
||||
onPress={props.onCancel}
|
||||
styles={styles.button}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
</Column>
|
||||
</Overlay>
|
||||
);
|
||||
};
|
||||
|
||||
interface MessageOverlayProps {
|
||||
const Progress: React.FC<Pick<MessageOverlayProps, 'progress'>> = (props) => {
|
||||
return typeof props.progress === 'boolean' ? (
|
||||
props.progress && (
|
||||
<LinearProgress variant="indeterminate" color={Theme.Colors.Loading} />
|
||||
)
|
||||
) : (
|
||||
<LinearProgress variant="determinate" value={props.progress} />
|
||||
);
|
||||
};
|
||||
|
||||
export interface MessageOverlayProps {
|
||||
isVisible: boolean;
|
||||
title?: string;
|
||||
message?: string;
|
||||
hasProgress?: boolean;
|
||||
progress?: boolean | number;
|
||||
hint?: string;
|
||||
onCancel?: () => void;
|
||||
onBackdropPress?: () => void;
|
||||
onShow?: () => void;
|
||||
}
|
||||
|
||||
5
components/OIDcAuth.strings.json
Normal file
5
components/OIDcAuth.strings.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"title": "OIDC Authentication",
|
||||
"text": "To be replaced with the OIDC provider UI",
|
||||
"verify": "Verify"
|
||||
}
|
||||
58
components/OIDcAuth.tsx
Normal file
58
components/OIDcAuth.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { Dimensions, StyleSheet, View } from 'react-native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { Button, Centered, Column, Text } from './ui';
|
||||
import { ModalProps } from './ui/Modal';
|
||||
import { Colors } from './ui/styleUtils';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
viewContainer: {
|
||||
backgroundColor: Colors.White,
|
||||
width: Dimensions.get('window').width,
|
||||
height: Dimensions.get('window').height,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
zIndex: 9,
|
||||
padding: 32,
|
||||
},
|
||||
});
|
||||
|
||||
export const OIDcAuthenticationModal: React.FC<OIDcAuthenticationModalProps> = (
|
||||
props
|
||||
) => {
|
||||
const { t } = useTranslation('OIDcAuth');
|
||||
|
||||
return (
|
||||
<View style={styles.viewContainer}>
|
||||
<Column safe fill align="space-between">
|
||||
<Centered fill>
|
||||
<Icon
|
||||
name="card-account-details-outline"
|
||||
color={Colors.Orange}
|
||||
size={30}
|
||||
/>
|
||||
<Text
|
||||
align="center"
|
||||
weight="bold"
|
||||
margin="8 0 12 0"
|
||||
style={{ fontSize: 24 }}>
|
||||
{t('title')}
|
||||
</Text>
|
||||
<Text align="center">{t('text')}</Text>
|
||||
<Text align="center" color={Colors.Red} margin="16 0 0 0">
|
||||
{props.error}
|
||||
</Text>
|
||||
</Centered>
|
||||
<Column>
|
||||
<Button title={t('verify')} onPress={() => props.onVerify()} />
|
||||
</Column>
|
||||
</Column>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
interface OIDcAuthenticationModalProps extends ModalProps {
|
||||
onVerify: () => void;
|
||||
error?: string;
|
||||
}
|
||||
45
components/OIDcAuthModal.tsx
Normal file
45
components/OIDcAuthModal.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { Button, Centered, Column, Text } from './ui';
|
||||
import { Modal, ModalProps } from './ui/Modal';
|
||||
import { Colors } from './ui/styleUtils';
|
||||
|
||||
export const OIDcAuthenticationOverlay: React.FC<
|
||||
OIDcAuthenticationModalProps
|
||||
> = (props) => {
|
||||
const { t } = useTranslation('OIDcAuth');
|
||||
|
||||
return (
|
||||
<Modal isVisible={props.isVisible} onDismiss={props.onDismiss}>
|
||||
<Column fill padding="32" align="space-between">
|
||||
<Centered fill>
|
||||
<Icon
|
||||
name="card-account-details-outline"
|
||||
color={Colors.Orange}
|
||||
size={30}
|
||||
/>
|
||||
<Text
|
||||
align="center"
|
||||
weight="bold"
|
||||
margin="8 0 12 0"
|
||||
style={{ fontSize: 24 }}>
|
||||
{t('title')}
|
||||
</Text>
|
||||
<Text align="center">{t('text')}</Text>
|
||||
<Text align="center" color={Colors.Red} margin="16 0 0 0">
|
||||
{props.error}
|
||||
</Text>
|
||||
</Centered>
|
||||
<Column>
|
||||
<Button title={t('verify')} onPress={() => props.onVerify()} />
|
||||
</Column>
|
||||
</Column>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
interface OIDcAuthenticationModalProps extends ModalProps {
|
||||
onVerify: () => void;
|
||||
error?: string;
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
|
||||
if (hasPermission === false) {
|
||||
return (
|
||||
<Column fill align="space-between">
|
||||
<Text align="center" color={Theme.Colors.errorMessage}>
|
||||
<Text align="center" margin="16 0" color={Theme.Colors.errorMessage}>
|
||||
{t('missingPermissionText')}
|
||||
</Text>
|
||||
<Button title={t('allowCameraButton')} onPress={openSettings} />
|
||||
|
||||
58
components/RevokeConfirm.tsx
Normal file
58
components/RevokeConfirm.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dimensions, StyleSheet, View } from 'react-native';
|
||||
import { Button, Centered, Column, Row, Text } from './ui';
|
||||
import { Colors } from './ui/styleUtils';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
viewContainer: {
|
||||
backgroundColor: 'rgba(0,0,0,.6)',
|
||||
width: Dimensions.get('screen').width,
|
||||
height: Dimensions.get('screen').height,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
zIndex: 999999,
|
||||
},
|
||||
boxContainer: {
|
||||
backgroundColor: Colors.White,
|
||||
padding: 24,
|
||||
elevation: 6,
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
|
||||
export const RevokeConfirmModal: React.FC<RevokeConfirmModalProps> = (
|
||||
props
|
||||
) => {
|
||||
const { t } = useTranslation('ViewVcModal');
|
||||
|
||||
return (
|
||||
<View style={styles.viewContainer}>
|
||||
<Centered fill>
|
||||
<Column
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
style={styles.boxContainer}>
|
||||
<Text weight="semibold" margin="0 0 12 0">
|
||||
{t('revoke')}
|
||||
</Text>
|
||||
<Text margin="0 0 12 0">{t('revoking', { vid: props.id })}</Text>
|
||||
<Row>
|
||||
<Button
|
||||
fill
|
||||
type="clear"
|
||||
title={t('cancel')}
|
||||
onPress={() => props.onCancel()}
|
||||
/>
|
||||
<Button fill title={t('revoke')} onPress={props.onRevoke} />
|
||||
</Row>
|
||||
</Column>
|
||||
</Centered>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
interface RevokeConfirmModalProps {
|
||||
onCancel: () => void;
|
||||
onRevoke: () => void;
|
||||
id: string;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Dimensions, StyleSheet } from 'react-native';
|
||||
import { Overlay, Input } from 'react-native-elements';
|
||||
import { Button, Column, Row, Text } from './ui';
|
||||
import { Dimensions, StyleSheet, View } from 'react-native';
|
||||
import { Input } from 'react-native-elements';
|
||||
import { Button, Centered, Column, Row, Text } from './ui';
|
||||
import { Theme } from './ui/styleUtils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -11,6 +11,20 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: Theme.Colors.overlayBackgroundColor,
|
||||
padding: 0,
|
||||
},
|
||||
viewContainer: {
|
||||
backgroundColor: 'rgba(0,0,0,.6)',
|
||||
width: Dimensions.get('screen').width,
|
||||
height: Dimensions.get('screen').height,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
zIndex: 9,
|
||||
},
|
||||
boxContainer: {
|
||||
backgroundColor: Theme.Colors.whiteBackgroundColor,
|
||||
padding: 24,
|
||||
elevation: 6,
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
|
||||
export const TextEditOverlay: React.FC<EditOverlayProps> = (props) => {
|
||||
@@ -18,27 +32,32 @@ export const TextEditOverlay: React.FC<EditOverlayProps> = (props) => {
|
||||
const [value, setValue] = useState(props.value);
|
||||
|
||||
return (
|
||||
<Overlay
|
||||
isVisible={props.isVisible}
|
||||
overlayStyle={styles.overlay}
|
||||
onBackdropPress={props.onDismiss}>
|
||||
<Column pX={24} pY={24} width={Dimensions.get('screen').width * 0.8}>
|
||||
<Text weight="semibold" margin="0 0 16 0">
|
||||
{props.label}
|
||||
</Text>
|
||||
<Input autoFocus value={value} onChangeText={setValue} />
|
||||
<Row>
|
||||
<Button
|
||||
fill
|
||||
type="clear"
|
||||
title={t('cancel')}
|
||||
onPress={dismiss}
|
||||
margin="0 8 0 0"
|
||||
/>
|
||||
<Button fill title={t('save')} onPress={() => props.onSave(value)} />
|
||||
</Row>
|
||||
</Column>
|
||||
</Overlay>
|
||||
<View style={styles.viewContainer}>
|
||||
<Centered fill>
|
||||
<Column
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
style={styles.boxContainer}>
|
||||
<Text weight="semibold" margin="0 0 16 0">
|
||||
{props.label}
|
||||
</Text>
|
||||
<Input autoFocus value={value} onChangeText={setValue} />
|
||||
<Row>
|
||||
<Button
|
||||
fill
|
||||
type="clear"
|
||||
title={t('cancel')}
|
||||
onPress={dismiss}
|
||||
margin="0 8 0 0"
|
||||
/>
|
||||
<Button
|
||||
fill
|
||||
title={t('save')}
|
||||
onPress={() => props.onSave(value)}
|
||||
/>
|
||||
</Row>
|
||||
</Column>
|
||||
</Centered>
|
||||
</View>
|
||||
);
|
||||
|
||||
function dismiss() {
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as DateFnsLocale from '../lib/date-fns/locale';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Image, ImageBackground } from 'react-native';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { VC, CredentialSubject } from '../types/vc';
|
||||
import { VC, CredentialSubject, LocalizedField } from '../types/vc';
|
||||
import { Column, Row, Text } from './ui';
|
||||
import { Theme } from './ui/styleUtils';
|
||||
import { TextItem } from './ui/TextItem';
|
||||
@@ -243,11 +243,6 @@ interface VcDetailsProps {
|
||||
vc: VC;
|
||||
}
|
||||
|
||||
interface LocalizedField {
|
||||
language: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function getFullAddress(credential: CredentialSubject) {
|
||||
if (!credential) {
|
||||
return '';
|
||||
@@ -269,7 +264,7 @@ function getFullAddress(credential: CredentialSubject) {
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
function getLocalizedField(rawField: string | LocalizedField) {
|
||||
function getLocalizedField(rawField: string | LocalizedField[]) {
|
||||
if (typeof rawField === 'string') {
|
||||
return rawField;
|
||||
}
|
||||
|
||||
134
components/VidItem.tsx
Normal file
134
components/VidItem.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import React, { useContext, useRef } from 'react';
|
||||
import { useInterpret, useSelector } from '@xstate/react';
|
||||
import { Pressable, StyleSheet } from 'react-native';
|
||||
import { CheckBox } from 'react-native-elements';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { ActorRefFrom } from 'xstate';
|
||||
import {
|
||||
createVcItemMachine,
|
||||
selectVerifiableCredential,
|
||||
selectGeneratedOn,
|
||||
selectTag,
|
||||
selectId,
|
||||
vcItemMachine,
|
||||
} from '../machines/vcItem';
|
||||
import { Column, Row, Text } from './ui';
|
||||
import { Colors } from './ui/styleUtils';
|
||||
import { RotatingIcon } from './RotatingIcon';
|
||||
import { GlobalContext } from '../shared/GlobalContext';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
title: {
|
||||
color: Colors.Black,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
loadingTitle: {
|
||||
color: 'transparent',
|
||||
backgroundColor: Colors.Grey5,
|
||||
borderRadius: 4,
|
||||
},
|
||||
subtitle: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
loadingSubtitle: {
|
||||
backgroundColor: Colors.Grey,
|
||||
borderRadius: 4,
|
||||
},
|
||||
container: {
|
||||
backgroundColor: Colors.White,
|
||||
},
|
||||
loadingContainer: {
|
||||
backgroundColor: Colors.Grey6,
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
|
||||
export const VidItem: React.FC<VcItemProps> = (props) => {
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const machine = useRef(
|
||||
createVcItemMachine(
|
||||
appService.getSnapshot().context.serviceRefs,
|
||||
props.vcKey
|
||||
)
|
||||
);
|
||||
const service = useInterpret(machine.current);
|
||||
const uin = useSelector(service, selectId);
|
||||
const tag = useSelector(service, selectTag);
|
||||
const verifiableCredential = useSelector(service, selectVerifiableCredential);
|
||||
const generatedOn = useSelector(service, selectGeneratedOn);
|
||||
|
||||
const selectableOrCheck = props.selectable ? (
|
||||
<CheckBox
|
||||
checked={props.selected}
|
||||
checkedIcon={<Icon name="checkbox-intermediate" size={24} />}
|
||||
uncheckedIcon={<Icon name="checkbox-blank-outline" size={24} />}
|
||||
onPress={() => props.onPress(service)}
|
||||
/>
|
||||
) : (
|
||||
<Icon name="chevron-right" />
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => props.onPress(service)}
|
||||
disabled={!verifiableCredential}>
|
||||
<Row
|
||||
elevation={!verifiableCredential ? 0 : 2}
|
||||
crossAlign="center"
|
||||
margin={props.margin}
|
||||
backgroundColor={!verifiableCredential ? Colors.Grey6 : Colors.White}
|
||||
padding={[16, 16]}
|
||||
style={
|
||||
!verifiableCredential ? styles.loadingContainer : styles.container
|
||||
}>
|
||||
<Column fill margin="0 24 0 0">
|
||||
<Text
|
||||
weight="semibold"
|
||||
style={!verifiableCredential ? styles.loadingTitle : styles.title}
|
||||
margin="0 0 6 0">
|
||||
{!verifiableCredential ? '' : tag || uin}
|
||||
</Text>
|
||||
<Text
|
||||
size="smaller"
|
||||
numLines={1}
|
||||
style={
|
||||
!verifiableCredential ? styles.loadingSubtitle : styles.subtitle
|
||||
}>
|
||||
{!verifiableCredential
|
||||
? ''
|
||||
: getLocalizedField(
|
||||
verifiableCredential.credentialSubject.fullName
|
||||
) +
|
||||
' · ' +
|
||||
generatedOn}
|
||||
</Text>
|
||||
</Column>
|
||||
{verifiableCredential ? (
|
||||
selectableOrCheck
|
||||
) : (
|
||||
<RotatingIcon name="sync" color={Colors.Grey5} />
|
||||
)}
|
||||
</Row>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
interface VcItemProps {
|
||||
vcKey: string;
|
||||
margin?: string;
|
||||
selectable?: boolean;
|
||||
selected?: boolean;
|
||||
onPress?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
|
||||
}
|
||||
|
||||
function getLocalizedField(rawField: string | LocalizedField) {
|
||||
if (typeof rawField === 'string') {
|
||||
return rawField;
|
||||
}
|
||||
try {
|
||||
const locales: LocalizedField[] = JSON.parse(JSON.stringify(rawField));
|
||||
return locales[0].value;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ export const Button: React.FC<ButtonProps> = (props) => {
|
||||
Theme.ButtonStyles.container,
|
||||
props.disabled ? Theme.ButtonStyles.disabled : null,
|
||||
props.margin ? Theme.spacing('margin', props.margin) : null,
|
||||
props.styles,
|
||||
];
|
||||
|
||||
const handleOnPress = (event: GestureResponderEvent) => {
|
||||
@@ -65,4 +66,5 @@ interface ButtonProps {
|
||||
raised?: boolean;
|
||||
loading?: boolean;
|
||||
icon?: RNEButtonProps['icon'];
|
||||
styles?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
83CBB9F71A601CBA00E9B192 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1130;
|
||||
LastUpgradeCheck = 1340;
|
||||
TargetAttributes = {
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
DevelopmentTeam = 9L83VVTX8B;
|
||||
@@ -250,52 +250,9 @@
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-MOSIPResidentApp/Pods-MOSIPResidentApp-resources.sh",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
|
||||
"${PODS_ROOT}/NearbyMessages/Resources/resources/GNSSharedResources.bundle",
|
||||
"${PODS_ROOT}/NearbyMessages/Resources/resources/ic_expand_more.xcassets",
|
||||
"${PODS_ROOT}/NearbyMessages/Resources/resources/ic_nearby_48pt.xcassets",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GNSSharedResources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
@@ -345,7 +302,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = MOSIPResidentApp/MOSIPResidentApp.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 10.5;
|
||||
DEVELOPMENT_TEAM = 9L83VVTX8B;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -378,7 +335,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = MOSIPResidentApp/MOSIPResidentApp.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 10.5;
|
||||
DEVELOPMENT_TEAM = 9L83VVTX8B;
|
||||
INFOPLIST_FILE = MOSIPResidentApp/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
@@ -421,6 +378,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -479,6 +437,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -503,6 +462,7 @@
|
||||
LIBRARY_SEARCH_PATHS = "\"$(inherited)\"";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1130"
|
||||
LastUpgradeVersion = "1340"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
<string>Bluetooth is used to allow sharing VCs with another device</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your camera</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Location access is required for the scanning functionality</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>Resident app can be unlocked using Face ID</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your microphone</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
install! 'cocoapods', :disable_input_output_paths => true
|
||||
|
||||
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
|
||||
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
|
||||
require File.join(File.dirname(`node --print "require.resolve('@react-native-community/cli-platform-ios/package.json')"`), "native_modules")
|
||||
|
||||
@@ -39,7 +39,7 @@ PODS:
|
||||
- ExpoModulesCore
|
||||
- React-Core
|
||||
- EXStructuredHeaders (2.1.1)
|
||||
- EXUpdates (0.11.6):
|
||||
- EXUpdates (0.11.7):
|
||||
- EXManifests
|
||||
- ExpoModulesCore
|
||||
- EXStructuredHeaders
|
||||
@@ -55,6 +55,26 @@ PODS:
|
||||
- React-jsi (= 0.64.3)
|
||||
- ReactCommon/turbomodule/core (= 0.64.3)
|
||||
- glog (0.3.5)
|
||||
- GoogleInterchangeUtilities (1.2.2):
|
||||
- GoogleSymbolUtilities (~> 1.1)
|
||||
- GoogleNetworkingUtilities (1.2.2):
|
||||
- GoogleSymbolUtilities (~> 1.1)
|
||||
- GoogleSymbolUtilities (1.1.2)
|
||||
- GoogleUtilitiesLegacy (1.3.2):
|
||||
- GoogleSymbolUtilities (~> 1.1)
|
||||
- NearbyMessages (1.1.1):
|
||||
- GoogleInterchangeUtilities (~> 1.2)
|
||||
- GoogleNetworkingUtilities (~> 1.2)
|
||||
- GoogleSymbolUtilities (~> 1.1)
|
||||
- GoogleUtilitiesLegacy (~> 1.3)
|
||||
- Permission-BluetoothPeripheral (3.6.0):
|
||||
- RNPermissions
|
||||
- Permission-Camera (3.6.0):
|
||||
- RNPermissions
|
||||
- Permission-LocationAccuracy (3.6.0):
|
||||
- RNPermissions
|
||||
- Permission-LocationWhenInUse (3.6.0):
|
||||
- RNPermissions
|
||||
- RCT-Folly (2020.01.13.00):
|
||||
- boost-for-react-native
|
||||
- DoubleConversion
|
||||
@@ -65,8 +85,6 @@ PODS:
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCTRequired (0.64.3)
|
||||
- RCTSystemSetting (1.7.6):
|
||||
- React
|
||||
- RCTTypeSafety (0.64.3):
|
||||
- FBLazyVector (= 0.64.3)
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
@@ -330,6 +348,8 @@ PODS:
|
||||
- React-Core
|
||||
- RNKeychain (8.0.0):
|
||||
- React-Core
|
||||
- RNPermissions (3.6.0):
|
||||
- React-Core
|
||||
- RNScreens (3.10.2):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
@@ -339,7 +359,8 @@ PODS:
|
||||
- React
|
||||
- RNVectorIcons (8.1.0):
|
||||
- React-Core
|
||||
- smartshare-react-native (0.2.2):
|
||||
- smartshare-react-native (0.2.3-beta.2):
|
||||
- NearbyMessages
|
||||
- React-Core
|
||||
- Yoga (1.14.0)
|
||||
- ZXingObjC/Core (3.6.5)
|
||||
@@ -372,9 +393,12 @@ DEPENDENCIES:
|
||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- Permission-BluetoothPeripheral (from `../node_modules/react-native-permissions/ios/BluetoothPeripheral`)
|
||||
- Permission-Camera (from `../node_modules/react-native-permissions/ios/Camera`)
|
||||
- Permission-LocationAccuracy (from `../node_modules/react-native-permissions/ios/LocationAccuracy`)
|
||||
- Permission-LocationWhenInUse (from `../node_modules/react-native-permissions/ios/LocationWhenInUse`)
|
||||
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
||||
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
|
||||
- RCTSystemSetting (from `../node_modules/react-native-system-setting`)
|
||||
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
|
||||
- React (from `../node_modules/react-native/`)
|
||||
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
|
||||
@@ -406,6 +430,7 @@ DEPENDENCIES:
|
||||
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNKeychain (from `../node_modules/react-native-keychain`)
|
||||
- RNPermissions (from `../node_modules/react-native-permissions`)
|
||||
- RNScreens (from `../node_modules/react-native-screens`)
|
||||
- RNSecureRandom (from `../node_modules/react-native-securerandom`)
|
||||
- RNSVG (from `../node_modules/react-native-svg`)
|
||||
@@ -416,6 +441,11 @@ DEPENDENCIES:
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- boost-for-react-native
|
||||
- GoogleInterchangeUtilities
|
||||
- GoogleNetworkingUtilities
|
||||
- GoogleSymbolUtilities
|
||||
- GoogleUtilitiesLegacy
|
||||
- NearbyMessages
|
||||
- ZXingObjC
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@@ -465,12 +495,18 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/React/FBReactNativeSpec"
|
||||
glog:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
|
||||
Permission-BluetoothPeripheral:
|
||||
:path: "../node_modules/react-native-permissions/ios/BluetoothPeripheral"
|
||||
Permission-Camera:
|
||||
:path: "../node_modules/react-native-permissions/ios/Camera"
|
||||
Permission-LocationAccuracy:
|
||||
:path: "../node_modules/react-native-permissions/ios/LocationAccuracy"
|
||||
Permission-LocationWhenInUse:
|
||||
:path: "../node_modules/react-native-permissions/ios/LocationWhenInUse"
|
||||
RCT-Folly:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
|
||||
RCTRequired:
|
||||
:path: "../node_modules/react-native/Libraries/RCTRequired"
|
||||
RCTSystemSetting:
|
||||
:path: "../node_modules/react-native-system-setting"
|
||||
RCTTypeSafety:
|
||||
:path: "../node_modules/react-native/Libraries/TypeSafety"
|
||||
React:
|
||||
@@ -529,6 +565,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-gesture-handler"
|
||||
RNKeychain:
|
||||
:path: "../node_modules/react-native-keychain"
|
||||
RNPermissions:
|
||||
:path: "../node_modules/react-native-permissions"
|
||||
RNScreens:
|
||||
:path: "../node_modules/react-native-screens"
|
||||
RNSecureRandom:
|
||||
@@ -562,14 +600,22 @@ SPEC CHECKSUMS:
|
||||
ExpoModulesCore: 32c0ccb47f477d330ee93db72505380adf0de09a
|
||||
EXSplashScreen: 21669e598804ee810547dbb6692c8deb5dd8dbf3
|
||||
EXStructuredHeaders: 4993087b2010dbcc510f5d92555b36f523425e8d
|
||||
EXUpdates: bd5fa64f02685ed3e96be86b5ca350cdc2cd8d02
|
||||
EXUpdates: a83e036243b0f6ece53a8c1cb883b6751c88a5f8
|
||||
EXUpdatesInterface: a9814f422d3cd6e7cfd260d13c27786148ece20e
|
||||
FBLazyVector: c71c5917ec0ad2de41d5d06a5855f6d5eda06971
|
||||
FBReactNativeSpec: b4cea6dceefc7cf1b25592b7aa7d9be51e7ef013
|
||||
FBReactNativeSpec: 2492beb27d7c97438138799b65e5914def868da4
|
||||
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
|
||||
GoogleInterchangeUtilities: d5bc4d88d5b661ab72f9d70c58d02ca8c27ad1f7
|
||||
GoogleNetworkingUtilities: 3edd3a8161347494f2da60ea0deddc8a472d94cb
|
||||
GoogleSymbolUtilities: 631ee17048aa5e9ab133470d768ea997a5ef9b96
|
||||
GoogleUtilitiesLegacy: 5501bedec1646bd284286eb5fc9453f7e23a12f4
|
||||
NearbyMessages: bd9e88f2df7fbab78be58fed58580d5d5bd62cbf
|
||||
Permission-BluetoothPeripheral: 2a5154a9dfdb1cfcf1d546650ced9671904a02af
|
||||
Permission-Camera: 0a0fb4341f50ab242f496fb2f73380e0ec454fe7
|
||||
Permission-LocationAccuracy: 13cbce13607e0738f1339447b4c5f51aa2c2b597
|
||||
Permission-LocationWhenInUse: 51aa065819cd582517e98e89b564e2465a4a83c6
|
||||
RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c
|
||||
RCTRequired: d34bf57e17cb6e3b2681f4809b13843c021feb6c
|
||||
RCTSystemSetting: 5107b7350d63b3f7b42a1277d07e4e5d9df879be
|
||||
RCTTypeSafety: 8dab4933124ed39bb0c1d88d74d61b1eb950f28f
|
||||
React: ef700aeb19afabff83a9cc5799ac955a9c6b5e0f
|
||||
React-callinvoker: 5547633d44f3e114b17c03c660ccb5faefd9ed2d
|
||||
@@ -599,14 +645,15 @@ SPEC CHECKSUMS:
|
||||
RNDeviceInfo: 36286df381fcaf1933ff9d2d3c34ba2abeb2d8d8
|
||||
RNGestureHandler: e1099204721a17a89c81fcd1cc2e92143dc040fb
|
||||
RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94
|
||||
RNPermissions: de7b7c3fe1680d974ac7a85e3e97aa539c0e68ea
|
||||
RNScreens: d6da2b9e29cf523832c2542f47bf1287318b1868
|
||||
RNSecureRandom: 0dcee021fdb3d50cd5cee5db0ebf583c42f5af0e
|
||||
RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f
|
||||
RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4
|
||||
smartshare-react-native: 5c8c6ece55cf7643e4d1d5f8d9edd7235d9b28fc
|
||||
smartshare-react-native: 133dca4c48dea0908649c680701f0948317378c5
|
||||
Yoga: e6ecf3fa25af9d4c87e94ad7d5d292eedef49749
|
||||
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
|
||||
|
||||
PODFILE CHECKSUM: a424a967159c32f02aadbfc51d78057f122a72df
|
||||
PODFILE CHECKSUM: 24c315d126fc2b5fef6a2828f16ec02b341154fe
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
"deviceRefNumber": "Device reference number",
|
||||
"name": "Name"
|
||||
},
|
||||
"OIDcAuth": {
|
||||
"title": "OIDC Authentication",
|
||||
"text": "To be replaced with the OIDC provider UI",
|
||||
"verify": "Verify"
|
||||
},
|
||||
"PasscodeVerify": {
|
||||
"passcodeMismatchError": "Passcode did not match."
|
||||
},
|
||||
@@ -40,7 +45,7 @@
|
||||
}
|
||||
},
|
||||
"BiometricScreen": {
|
||||
"unlock": "Unlock with fingerprint"
|
||||
"unlock": "Unlock with biometrics"
|
||||
},
|
||||
"HistoryTab": {
|
||||
"noHistory": "No history available yet",
|
||||
@@ -124,13 +129,21 @@
|
||||
"noReceivedVcsText": "Tap on Request below to receive {{vcLabel}}"
|
||||
},
|
||||
"ViewVcModal": {
|
||||
"cancel": "Cancel",
|
||||
"lock": "Lock",
|
||||
"unlock": "Unlock",
|
||||
"rename": "Rename",
|
||||
"delete": "Delete",
|
||||
"revoke": "Revoke",
|
||||
"revoking": "Your wallet contains a credential with VID {{vid}}. Revoking this will automatically remove the same from the wallet. Are you sure you want to proceed?",
|
||||
"requestingOtp": "Requesting OTP...",
|
||||
"editTag": "Edit Tag"
|
||||
"editTag": "Rename",
|
||||
"redirecting": "Redirecting...",
|
||||
"success": {
|
||||
"unlocked": "{{vcLabel}} successfully unlocked",
|
||||
"locked": "{{vcLabel}} successfully locked",
|
||||
"revoked": "VID {{vid}} has been revoked. Any credential containing the same will be removed automatically from the wallet"
|
||||
}
|
||||
},
|
||||
"MainLayout": {
|
||||
"home": "Home",
|
||||
@@ -154,7 +167,13 @@
|
||||
"bioUnlock": "Biometric unlock",
|
||||
"authFactorUnlock": "Unlock auth factor",
|
||||
"credits": "Credits and legal notices",
|
||||
"logout": "Log-out"
|
||||
"logout": "Log-out",
|
||||
"revokeLabel": "Revoke VID",
|
||||
"revokeHeader": "REVOKE VID",
|
||||
"revokingVids": "You are about to revoke ({{count}}) VIDs.",
|
||||
"revokingVidsAfter": "This means you will no longer be able to use or view any of the IDs linked to those VID(s). \nAre you sure you want to proceed?",
|
||||
"empty": "Empty",
|
||||
"revokeSuccessful": "VID successfully revoked"
|
||||
},
|
||||
"ReceiveVcScreen": {
|
||||
"header": "{{vcLabel}} details",
|
||||
@@ -180,8 +199,14 @@
|
||||
"message": "The connection was interrupted. Please try again."
|
||||
},
|
||||
"waitingConnection": "Waiting for connection...",
|
||||
"exchangingDeviceInfo": "Exchanging device info...",
|
||||
"connected": "Connected to device. Waiting for {{vcLabel}}..."
|
||||
"exchangingDeviceInfo": {
|
||||
"message": "Exchanging device info...",
|
||||
"timeoutHint": "It's taking too long to exchange device info..."
|
||||
},
|
||||
"connected": {
|
||||
"message": "Connected to device. Waiting for {{vcLabel}}...",
|
||||
"timeoutHint": "No VC data received yet. Is sending device still connected?"
|
||||
}
|
||||
},
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
@@ -192,10 +217,6 @@
|
||||
"noShareableVcs": "No shareable {{vcLabel}} are available.",
|
||||
"sharingVc": "Sharing {{vcLabel}}",
|
||||
"errors": {
|
||||
"flightMode": {
|
||||
"message": "Flight mode must be disabled for the scanning functionality",
|
||||
"button": "Disable flight mode"
|
||||
},
|
||||
"locationDisabled": {
|
||||
"message": "Location services must be enabled for the scanning functionality",
|
||||
"button": "Enable location services"
|
||||
@@ -207,30 +228,42 @@
|
||||
},
|
||||
"status": {
|
||||
"connecting": "Connecting...",
|
||||
"connectingTimeout": "It's taking a while to establish connection. Is the other device open for connections?",
|
||||
"exchangingDeviceInfo": "Exchanging device info...",
|
||||
"exchangingDeviceInfoTimeout": "It's taking a while to exchange device info. You may have to reconnect.",
|
||||
"invalid": "Invalid QR Code"
|
||||
}
|
||||
},
|
||||
"SelectVcOverlay": {
|
||||
"header": "Share {{vcLabel}}",
|
||||
"chooseVc": "Choose the {{vcLabel}} you'd like to share with",
|
||||
"cancel": "Cancel",
|
||||
"share": "Share"
|
||||
"share": "Share",
|
||||
"verifyAndShare": "Verify Identity & Share"
|
||||
},
|
||||
"SendVcModal": {
|
||||
"SendVcScreen": {
|
||||
"reasonForSharing": "Reason for sharing (optional)",
|
||||
"acceptRequest": "Accept request and choose {{vcLabel}}",
|
||||
"reject": "Reject",
|
||||
"statusSharing": {
|
||||
"title": "Sharing..."
|
||||
"status": {
|
||||
"sharing": {
|
||||
"title": "Sharing...",
|
||||
"timeoutHint": "It's taking a while to share VC. There could be a problem with the connection."
|
||||
},
|
||||
"accepted": {
|
||||
"title": "Success!",
|
||||
"message": "Your {{vcLabel}} has been successfully shared with {{receiver}}"
|
||||
},
|
||||
"rejected": {
|
||||
"title": "Notice",
|
||||
"message": "Your {{vcLabel}} was rejected by {{receiver}}"
|
||||
},
|
||||
"verifyingIdentity": "Verifying identity..."
|
||||
},
|
||||
"statusAccepted": {
|
||||
"title": "Success!",
|
||||
"message": "Your {{vcLabel}} has been successfully shared with {{receiver}}"
|
||||
},
|
||||
"statusRejected": {
|
||||
"title": "Notice",
|
||||
"message": "Your {{vcLabel}} was rejected by {{receiver}}"
|
||||
"errors": {
|
||||
"invalidIdentity": {
|
||||
"title": "Unable to verify identity",
|
||||
"message": "An error occured and we couldn't scan your portrait. Try again, make sure your face is visible, devoid of any accessories."
|
||||
}
|
||||
}
|
||||
},
|
||||
"WelcomeScreen": {
|
||||
@@ -241,6 +274,7 @@
|
||||
"common": {
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"editLabel": "Edit {{label}}"
|
||||
"editLabel": "Edit {{label}}",
|
||||
"tryAgain": "Try again"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,11 @@
|
||||
"deviceRefNumber": "Reference number ng device",
|
||||
"name": "Pangalan"
|
||||
},
|
||||
"OIDcAuth": {
|
||||
"title": "OIDC Authentication",
|
||||
"text": "Papalitan ng OIDC service provider UI",
|
||||
"verify": "I-verify"
|
||||
},
|
||||
"PasscodeVerify": {
|
||||
"passcodeMismatchError": "Hindi tumugma ang passcode."
|
||||
},
|
||||
@@ -37,7 +42,7 @@
|
||||
}
|
||||
},
|
||||
"BiometricScreen": {
|
||||
"unlock": "I-unlock gamit ang fingerprint"
|
||||
"unlock": "I-unlock gamit ang biometrics"
|
||||
},
|
||||
"HistoryTab": {
|
||||
"noHistory": "Wala pang kasaysayan",
|
||||
@@ -99,13 +104,21 @@
|
||||
"noReceivedVcsText": "Pindutin ang Humiling sa ibaba para makatanggap ng {{vcLabel}}"
|
||||
},
|
||||
"ViewVcModal": {
|
||||
"cancel": "Kanselahin",
|
||||
"lock": "Isara ang paggamit",
|
||||
"unlock": "Buksan ang paggamit",
|
||||
"rename": "Palitan ang pangalan",
|
||||
"delete": "Tanggalin",
|
||||
"revoke": "Bawiin",
|
||||
"revoking": "Ang iyong wallet ay naglalaman ng kredensyal na may VID {{vid}}. Ang pagkansela nito ay awtomatikong mag-aalis ng pareho sa wallet. Sigurado ka bang gusto mong magpatuloy?",
|
||||
"requestingOtp": "Humihiling ng OTP...",
|
||||
"editTag": "Palitan ang Tag"
|
||||
"editTag": "Palitan ang pangalan",
|
||||
"redirecting": "Redirecting...",
|
||||
"success": {
|
||||
"unlocked": "Ang {{vcLabel}} ay matagumpay na na-unlock",
|
||||
"locked": "Ang {{vcLabel}} ay matagumpay na na-lock",
|
||||
"revoked": "Ang VID {{vid}} ay nakansela. Ang lahat ng mga detalye na naglalaman ng pareho ay awtomatikong aalisin wallet."
|
||||
}
|
||||
},
|
||||
"MainLayout": {
|
||||
"home": "Home",
|
||||
@@ -129,17 +142,24 @@
|
||||
"bioUnlock": "Pagbukas gamit Biometric",
|
||||
"authFactorUnlock": "Pagbukas ng auth factor",
|
||||
"credits": "Mga kredito at legal na abiso",
|
||||
"logout": "Mag log-out"
|
||||
"logout": "Mag log-out",
|
||||
"revokeLabel": "Kanselahin ang VID",
|
||||
"revokeHeader": "KANSELAHIN ANG VID",
|
||||
"revokingVids": "Kakanselahin mo na ang ({{count}}) na mga VID.",
|
||||
"revokingVidsAfter": "Nangangahulugan ito na hindi mo na maa-access o matitingnan ang anumang mga ID na naka-link sa mga VID na ito. Sigurado ka bang gusto mong magpatuloy?",
|
||||
"empty": "Walang laman",
|
||||
"revokeSuccessful": "Matagumpay na nakansela ang VID"
|
||||
},
|
||||
"ReceiveVcModal": {
|
||||
"header": "Detalye ng {{vcLabel}}",
|
||||
"acceptRequest": "Tanggapin ang kahilingan at kunin ang {{vcLabel}}",
|
||||
"ReceiveVcScreen": {
|
||||
"header": "Mga detalye ng {{vcLabel}}",
|
||||
"acceptRequest": "Tanggapin ang kahilingan at tumanggap ng {{vcLabel}}",
|
||||
"reject": "Tanggihan"
|
||||
},
|
||||
"RequestScreen": {
|
||||
"bluetoothDenied": "Mangyaring paganahin ang Bluetooth upang makahiling ng {{vcLabel}}",
|
||||
"showQrCode": "Ipakita ang QR code na ito para makahiling ng {{vcLabel}}",
|
||||
"incomingVc": "Padating na {{vcLabel}}",
|
||||
"request": "Hilingin",
|
||||
"status": {
|
||||
"accepted": {
|
||||
"title": "Tagumpay!",
|
||||
@@ -154,19 +174,24 @@
|
||||
"message": "Naputol ang koneksyon. Pakiulit."
|
||||
},
|
||||
"waitingConnection": "Naghihintay ng koneksyon...",
|
||||
"exchangingDeviceInfo": "Nagpapalitan ng impormasyon ng device...",
|
||||
"connected": "Nakakonekta sa device. Naghihintay ng {{vcLabel}}..."
|
||||
}
|
||||
"exchangingDeviceInfo": {
|
||||
"message": "Pagpapalitan ng impormasyon ng device...",
|
||||
"timeoutHint": "Masyadong matagal ang pagpapalitan ng impormasyon ng device..."
|
||||
},
|
||||
"connected": {
|
||||
"message": "Nakakonektang device. Naghihintay para sa {{vcLabel}}...",
|
||||
"timeoutHint": "Wala pang natanggap na VC. Nakakonekta pa rin ba ang pagpapadala ng device?"
|
||||
}
|
||||
},
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"gotoSettings": "Pumunta sa setting"
|
||||
},
|
||||
"ScanScreen": {
|
||||
"header": "I-scan ang QR Code",
|
||||
"noShareableVcs": "Walang magagamit na maibabahaging {{vcLabel}}.",
|
||||
"sharingVc": "Pagbabahagi ng {{vcLabel}}",
|
||||
"errors": {
|
||||
"flightMode": {
|
||||
"message": "Dapat na hindi nakabukas ang flight mode ng iyong mobile para maaaring makapag-scan",
|
||||
"button": "Isara ang flight mode"
|
||||
},
|
||||
"locationDisabled": {
|
||||
"message": "Dapat na nakabukas ang Location services ng iyong mobile para maaaring makapag-scan",
|
||||
"button": "Buksan ang location services"
|
||||
@@ -178,30 +203,42 @@
|
||||
},
|
||||
"status": {
|
||||
"connecting": "Kumokonekta...",
|
||||
"connectingTimeout": "Medyo nagtatagal bago magtatag ng koneksyon. Bukas ba ang ibang device para sa mga koneksyon?",
|
||||
"exchangingDeviceInfo": "Nagpapalitan ng impormasyon ng device...",
|
||||
"exchangingDeviceInfoTimeout": "Medyo nagtatagal ang paglabas ng impormasyon ng device. Bukas ba ang ibang device para sa mga koneksyon?",
|
||||
"invalid": "Di-wasto ang QR Code"
|
||||
}
|
||||
},
|
||||
"SelectVcOverlay": {
|
||||
"header": "Ibahagi ang {{vcLabel}}",
|
||||
"chooseVc": "Piliin ang {{vcLabel}} na gusto mong ibahagi",
|
||||
"cancel": "Kanselahin",
|
||||
"share": "Ibahagi"
|
||||
"share": "Ibahagi",
|
||||
"verifyAndShare": "I-verify ang Pagkakakilanlan at Ibahagi"
|
||||
},
|
||||
"SendVcModal": {
|
||||
"reasonForSharing": "Dahilan ng pagbabahagi (hindi ubligado)",
|
||||
"SendVcScreen": {
|
||||
"reasonForSharing": "Dahilan ng pagbabahagi (opsyonal)",
|
||||
"acceptRequest": "Tanggapin ang kahilingan at piliin ang {{vcLabel}}",
|
||||
"reject": "Tanggihan",
|
||||
"statusSharing": {
|
||||
"title": "Pagbabahagi..."
|
||||
"status": {
|
||||
"sharing": {
|
||||
"title": "Pagbabahagi",
|
||||
"timeoutHint": "Medyo natatagal ang pagbabahagi ng VC. Maaaring may problema sa koneksyon."
|
||||
},
|
||||
"accepted": {
|
||||
"title": "Tagumpay!",
|
||||
"message": "Ang iyong {{vcLabel}} ay matagumpay na naibahagi kay {{receiver}}"
|
||||
},
|
||||
"rejected": {
|
||||
"title": "Pansinin",
|
||||
"message": "Ang iyong {{vcLabel}} ay tinanggihan ng {{receiver}}"
|
||||
},
|
||||
"verifyingIdentity": "Bine-verify ang pagkakakilanlan..."
|
||||
},
|
||||
"statusAccepted": {
|
||||
"title": "Tagumpay!",
|
||||
"message": "Ang iyong {{vcLabel}} ay matagumpay na naibahagi sa {{receiver}}"
|
||||
},
|
||||
"statusRejected": {
|
||||
"title": "Paunawa",
|
||||
"message": "Ang iyong {{vcLabel}} ay tinanggihan ni {{receiver}}"
|
||||
"errors": {
|
||||
"invalidIdentity": {
|
||||
"title": "Hindi ma-verify ang pagkakakilanlan",
|
||||
"message": "May naganap na error at hindi namin ma-scan ang iyong portrait. Subukang muli, tiyaking nakikita ang iyong mukha, walang anumang mga accessory."
|
||||
}
|
||||
}
|
||||
},
|
||||
"WelcomeScreen": {
|
||||
@@ -212,6 +249,7 @@
|
||||
"common": {
|
||||
"cancel": "Kanselahin",
|
||||
"save": "I-save",
|
||||
"editLabel": "Palitan ang {{label}}"
|
||||
"editLabel": "Palitan ang {{label}}",
|
||||
"tryAgain": "Subukan muli"
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ const model = createModel(
|
||||
{
|
||||
events: {
|
||||
STORE_RESPONSE: (response: unknown) => ({ response }),
|
||||
LOG_ACTIVITY: (log: ActivityLog) => ({ log }),
|
||||
LOG_ACTIVITY: (log: ActivityLog | ActivityLog[]) => ({ log }),
|
||||
REFRESH: () => ({}),
|
||||
},
|
||||
}
|
||||
@@ -93,7 +93,9 @@ export const activityLogMachine =
|
||||
|
||||
prependActivity: model.assign({
|
||||
activities: (context, event) =>
|
||||
[event.response, ...context.activities] as ActivityLog[],
|
||||
(Array.isArray(event.response)
|
||||
? [...event.response, ...context.activities]
|
||||
: [event.response, ...context.activities]) as ActivityLog[],
|
||||
}),
|
||||
},
|
||||
}
|
||||
@@ -118,7 +120,8 @@ export type ActivityLogAction =
|
||||
| 'shared'
|
||||
| 'received'
|
||||
| 'deleted'
|
||||
| 'downloaded';
|
||||
| 'downloaded'
|
||||
| 'revoked';
|
||||
|
||||
type State = StateFrom<typeof activityLogMachine>;
|
||||
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'eventsCausingActions': {
|
||||
setActivities: 'STORE_RESPONSE';
|
||||
prependActivity: 'STORE_RESPONSE';
|
||||
loadActivities: 'REFRESH';
|
||||
storeActivity: 'LOG_ACTIVITY';
|
||||
};
|
||||
'internalEvents': {
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
@@ -18,6 +12,12 @@ export interface Typegen0 {
|
||||
guards: never;
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
loadActivities: 'REFRESH' | 'xstate.init';
|
||||
prependActivity: 'STORE_RESPONSE';
|
||||
setActivities: 'STORE_RESPONSE';
|
||||
storeActivity: 'LOG_ACTIVITY';
|
||||
};
|
||||
'eventsCausingServices': {};
|
||||
'eventsCausingGuards': {};
|
||||
'eventsCausingDelays': {};
|
||||
|
||||
@@ -14,6 +14,8 @@ import { createVcMachine, vcMachine } from './vc';
|
||||
import { createActivityLogMachine, activityLogMachine } from './activityLog';
|
||||
import { createRequestMachine, requestMachine } from './request';
|
||||
import { createScanMachine, scanMachine } from './scan';
|
||||
import { createRevokeMachine, revokeVidsMachine } from './revoke';
|
||||
|
||||
import { pure, respond } from 'xstate/lib/actions';
|
||||
import { AppServices } from '../shared/GlobalContext';
|
||||
import { request } from '../shared/request';
|
||||
@@ -172,15 +174,19 @@ export const appMachine = model.createMachine(
|
||||
const serviceRefs = {
|
||||
...context.serviceRefs,
|
||||
};
|
||||
|
||||
serviceRefs.auth = spawn(
|
||||
createAuthMachine(serviceRefs),
|
||||
authMachine.id
|
||||
);
|
||||
|
||||
serviceRefs.vc = spawn(createVcMachine(serviceRefs), vcMachine.id);
|
||||
|
||||
serviceRefs.settings = spawn(
|
||||
createSettingsMachine(serviceRefs),
|
||||
settingsMachine.id
|
||||
);
|
||||
|
||||
serviceRefs.activityLog = spawn(
|
||||
createActivityLogMachine(serviceRefs),
|
||||
activityLogMachine.id
|
||||
@@ -190,10 +196,17 @@ export const appMachine = model.createMachine(
|
||||
createScanMachine(serviceRefs),
|
||||
scanMachine.id
|
||||
);
|
||||
|
||||
serviceRefs.request = spawn(
|
||||
createRequestMachine(serviceRefs),
|
||||
requestMachine.id
|
||||
);
|
||||
|
||||
serviceRefs.revoke = spawn(
|
||||
createRevokeMachine(serviceRefs),
|
||||
revokeVidsMachine.id
|
||||
);
|
||||
|
||||
return serviceRefs;
|
||||
},
|
||||
}),
|
||||
@@ -206,6 +219,7 @@ export const appMachine = model.createMachine(
|
||||
context.serviceRefs.activityLog.subscribe(logState);
|
||||
context.serviceRefs.scan.subscribe(logState);
|
||||
context.serviceRefs.request.subscribe(logState);
|
||||
context.serviceRefs.revoke.subscribe(logState);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'eventsCausingActions': {
|
||||
setContext: 'STORE_RESPONSE';
|
||||
setPasscode: 'SETUP_PASSCODE';
|
||||
storeContext: 'SETUP_PASSCODE' | 'SETUP_BIOMETRICS' | 'STORE_RESPONSE';
|
||||
setBiometrics: 'SETUP_BIOMETRICS';
|
||||
requestStoredContext: 'xstate.init';
|
||||
};
|
||||
'internalEvents': {
|
||||
'': { type: '' };
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
@@ -20,19 +13,26 @@ export interface Typegen0 {
|
||||
guards: never;
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
requestStoredContext: 'xstate.init';
|
||||
setBiometrics: 'SETUP_BIOMETRICS';
|
||||
setContext: 'STORE_RESPONSE';
|
||||
setPasscode: 'SETUP_PASSCODE';
|
||||
storeContext: 'SETUP_BIOMETRICS' | 'SETUP_PASSCODE' | 'STORE_RESPONSE';
|
||||
};
|
||||
'eventsCausingServices': {};
|
||||
'eventsCausingGuards': {
|
||||
hasBiometricSet: '';
|
||||
hasData: 'STORE_RESPONSE';
|
||||
hasPasscodeSet: '';
|
||||
hasBiometricSet: '';
|
||||
};
|
||||
'eventsCausingDelays': {};
|
||||
'matchesStates':
|
||||
| 'authorized'
|
||||
| 'checkingAuth'
|
||||
| 'init'
|
||||
| 'savingDefaults'
|
||||
| 'checkingAuth'
|
||||
| 'settingUp'
|
||||
| 'unauthorized'
|
||||
| 'authorized';
|
||||
| 'unauthorized';
|
||||
'tags': never;
|
||||
}
|
||||
|
||||
@@ -2,22 +2,11 @@
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'eventsCausingActions': {
|
||||
selectInput: 'FOCUS_INPUT';
|
||||
updateInput: 'UPDATE_INPUT';
|
||||
focusSelected:
|
||||
| 'xstate.after(INITIAL_FOCUS_DELAY)#pinInput.idle'
|
||||
| 'UPDATE_INPUT'
|
||||
| 'KEY_PRESS';
|
||||
selectNextInput: 'UPDATE_INPUT';
|
||||
selectPrevInput: 'KEY_PRESS';
|
||||
clearInput: 'KEY_PRESS';
|
||||
};
|
||||
'internalEvents': {
|
||||
'': { type: '' };
|
||||
'xstate.after(INITIAL_FOCUS_DELAY)#pinInput.idle': {
|
||||
type: 'xstate.after(INITIAL_FOCUS_DELAY)#pinInput.idle';
|
||||
};
|
||||
'': { type: '' };
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
'invokeSrcNameMap': {};
|
||||
@@ -27,14 +16,25 @@ export interface Typegen0 {
|
||||
guards: never;
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
clearInput: 'KEY_PRESS';
|
||||
focusSelected:
|
||||
| 'KEY_PRESS'
|
||||
| 'UPDATE_INPUT'
|
||||
| 'xstate.after(INITIAL_FOCUS_DELAY)#pinInput.idle';
|
||||
selectInput: 'FOCUS_INPUT';
|
||||
selectNextInput: 'UPDATE_INPUT';
|
||||
selectPrevInput: 'KEY_PRESS';
|
||||
updateInput: 'UPDATE_INPUT';
|
||||
};
|
||||
'eventsCausingServices': {};
|
||||
'eventsCausingGuards': {
|
||||
isBlank: 'UPDATE_INPUT';
|
||||
hasNextInput: 'UPDATE_INPUT';
|
||||
canGoBack: 'KEY_PRESS';
|
||||
hasNextInput: 'UPDATE_INPUT';
|
||||
isBlank: 'UPDATE_INPUT';
|
||||
};
|
||||
'eventsCausingDelays': {
|
||||
INITIAL_FOCUS_DELAY: 'xstate.init';
|
||||
INITIAL_FOCUS_DELAY: '' | 'xstate.init';
|
||||
};
|
||||
'matchesStates': 'idle' | 'selectingNext' | 'selectingPrev';
|
||||
'tags': never;
|
||||
|
||||
@@ -31,6 +31,9 @@ import {
|
||||
|
||||
type SharingProtocol = 'OFFLINE' | 'ONLINE';
|
||||
|
||||
const waitingForConnectionId = '#waitingForConnection';
|
||||
const checkingBluetoothServiceId = '#checkingBluetoothService';
|
||||
|
||||
const model = createModel(
|
||||
{
|
||||
serviceRefs: {} as AppServices,
|
||||
@@ -80,7 +83,7 @@ export const requestMachine = model.createMachine(
|
||||
id: 'request',
|
||||
initial: 'inactive',
|
||||
invoke: {
|
||||
src: 'checkConnection',
|
||||
src: 'monitorConnection',
|
||||
},
|
||||
on: {
|
||||
SCREEN_BLUR: 'inactive',
|
||||
@@ -95,6 +98,7 @@ export const requestMachine = model.createMachine(
|
||||
entry: ['removeLoggers'],
|
||||
},
|
||||
checkingBluetoothService: {
|
||||
id: 'checkingBluetoothService',
|
||||
initial: 'checking',
|
||||
states: {
|
||||
checking: {
|
||||
@@ -165,6 +169,23 @@ export const requestMachine = model.createMachine(
|
||||
actions: ['setSenderInfo'],
|
||||
},
|
||||
},
|
||||
initial: 'inProgress',
|
||||
states: {
|
||||
inProgress: {},
|
||||
timeout: {
|
||||
on: {
|
||||
CANCEL: {
|
||||
actions: 'disconnect',
|
||||
target: checkingBluetoothServiceId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
CONNECTION_TIMEOUT: {
|
||||
target: '.timeout',
|
||||
},
|
||||
},
|
||||
},
|
||||
waitingForVc: {
|
||||
invoke: {
|
||||
@@ -177,6 +198,23 @@ export const requestMachine = model.createMachine(
|
||||
actions: ['setIncomingVc'],
|
||||
},
|
||||
},
|
||||
initial: 'inProgress',
|
||||
states: {
|
||||
inProgress: {},
|
||||
timeout: {
|
||||
on: {
|
||||
CANCEL: {
|
||||
actions: 'disconnect',
|
||||
target: checkingBluetoothServiceId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
CONNECTION_TIMEOUT: {
|
||||
target: '.timeout',
|
||||
},
|
||||
},
|
||||
},
|
||||
reviewing: {
|
||||
on: {
|
||||
@@ -251,7 +289,7 @@ export const requestMachine = model.createMachine(
|
||||
},
|
||||
},
|
||||
on: {
|
||||
DISMISS: '#waitingForConnection',
|
||||
DISMISS: waitingForConnectionId,
|
||||
},
|
||||
},
|
||||
navigatingToHome: {},
|
||||
@@ -259,6 +297,7 @@ export const requestMachine = model.createMachine(
|
||||
exit: ['disconnect'],
|
||||
},
|
||||
disconnected: {
|
||||
id: 'disconnected',
|
||||
entry: ['disconnect'],
|
||||
on: {
|
||||
DISMISS: 'waitingForConnection',
|
||||
@@ -269,7 +308,9 @@ export const requestMachine = model.createMachine(
|
||||
{
|
||||
actions: {
|
||||
openSettings: () => {
|
||||
Linking.openURL('App-Prefs:Bluetooth');
|
||||
Platform.OS === 'android'
|
||||
? BluetoothStateManager.openSettings().catch()
|
||||
: Linking.openURL('App-Prefs:Bluetooth');
|
||||
},
|
||||
|
||||
switchProtocol: assign({
|
||||
@@ -467,14 +508,16 @@ export const requestMachine = model.createMachine(
|
||||
}
|
||||
},
|
||||
|
||||
checkConnection: () => (callback) => {
|
||||
const subscription = IdpassSmartshare.handleNearbyEvents((event) => {
|
||||
if (event.type === 'onDisconnected') {
|
||||
callback({ type: 'DISCONNECT' });
|
||||
}
|
||||
});
|
||||
monitorConnection: (context) => (callback) => {
|
||||
if (context.sharingProtocol === 'OFFLINE') {
|
||||
const subscription = IdpassSmartshare.handleNearbyEvents((event) => {
|
||||
if (event.type === 'onDisconnected') {
|
||||
callback({ type: 'DISCONNECT' });
|
||||
}
|
||||
});
|
||||
|
||||
return () => subscription.remove();
|
||||
return () => subscription.remove();
|
||||
}
|
||||
},
|
||||
|
||||
exchangeDeviceInfo: (context) => (callback) => {
|
||||
@@ -570,6 +613,9 @@ export const requestMachine = model.createMachine(
|
||||
|
||||
delays: {
|
||||
CLEAR_DELAY: 250,
|
||||
CONNECTION_TIMEOUT: () => {
|
||||
return (Platform.OS === 'ios' ? 10 : 5) * 1000;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -623,12 +669,24 @@ export function selectIsBluetoothDenied(state: State) {
|
||||
return state.matches('bluetoothDenied');
|
||||
}
|
||||
|
||||
export function selectIsCheckingBluetoothService(state: State) {
|
||||
return state.matches('checkingBluetoothService');
|
||||
}
|
||||
|
||||
export function selectIsExchangingDeviceInfo(state: State) {
|
||||
return state.matches('exchangingDeviceInfo');
|
||||
return state.matches('exchangingDeviceInfo.inProgress');
|
||||
}
|
||||
|
||||
export function selectIsExchangingDeviceInfoTimeout(state: State) {
|
||||
return state.matches('exchangingDeviceInfo.timeout');
|
||||
}
|
||||
|
||||
export function selectIsWaitingForVc(state: State) {
|
||||
return state.matches('waitingForVc');
|
||||
return state.matches('waitingForVc.inProgress');
|
||||
}
|
||||
|
||||
export function selectIsWaitingForVcTimeout(state: State) {
|
||||
return state.matches('waitingForVc.timeout');
|
||||
}
|
||||
|
||||
export function selectIsDone(state: State) {
|
||||
|
||||
@@ -13,8 +13,8 @@ export interface Typegen0 {
|
||||
'invokeSrcNameMap': {
|
||||
advertiseDevice: 'done.invoke.waitingForConnection:invocation[0]';
|
||||
checkBluetoothService: 'done.invoke.request.checkingBluetoothService.checking:invocation[0]';
|
||||
checkConnection: 'done.invoke.request:invocation[0]';
|
||||
exchangeDeviceInfo: 'done.invoke.request.exchangingDeviceInfo:invocation[0]';
|
||||
monitorConnection: 'done.invoke.request:invocation[0]';
|
||||
receiveVc: 'done.invoke.request.waitingForVc:invocation[0]';
|
||||
requestBluetooth: 'done.invoke.request.checkingBluetoothService.requesting:invocation[0]';
|
||||
sendVcResponse:
|
||||
@@ -30,6 +30,7 @@ export interface Typegen0 {
|
||||
'eventsCausingActions': {
|
||||
disconnect:
|
||||
| ''
|
||||
| 'CANCEL'
|
||||
| 'DISCONNECT'
|
||||
| 'DISMISS'
|
||||
| 'SCREEN_BLUR'
|
||||
@@ -63,13 +64,13 @@ export interface Typegen0 {
|
||||
};
|
||||
'eventsCausingServices': {
|
||||
advertiseDevice: 'DISMISS' | 'xstate.after(CLEAR_DELAY)#clearingConnection';
|
||||
checkBluetoothService: 'SCREEN_FOCUS' | 'SWITCH_PROTOCOL';
|
||||
checkConnection:
|
||||
checkBluetoothService: 'CANCEL' | 'SCREEN_FOCUS' | 'SWITCH_PROTOCOL';
|
||||
exchangeDeviceInfo: 'RECEIVE_DEVICE_INFO';
|
||||
monitorConnection:
|
||||
| 'SCREEN_BLUR'
|
||||
| 'SCREEN_FOCUS'
|
||||
| 'SWITCH_PROTOCOL'
|
||||
| 'xstate.init';
|
||||
exchangeDeviceInfo: 'RECEIVE_DEVICE_INFO';
|
||||
receiveVc: 'EXCHANGE_DONE';
|
||||
requestBluetooth: 'BLUETOOTH_DISABLED';
|
||||
sendVcResponse: 'CANCEL' | 'REJECT' | 'STORE_RESPONSE';
|
||||
@@ -79,6 +80,7 @@ export interface Typegen0 {
|
||||
};
|
||||
'eventsCausingDelays': {
|
||||
CLEAR_DELAY: '';
|
||||
CONNECTION_TIMEOUT: 'EXCHANGE_DONE' | 'RECEIVE_DEVICE_INFO';
|
||||
};
|
||||
'matchesStates':
|
||||
| 'bluetoothDenied'
|
||||
@@ -89,6 +91,8 @@ export interface Typegen0 {
|
||||
| 'clearingConnection'
|
||||
| 'disconnected'
|
||||
| 'exchangingDeviceInfo'
|
||||
| 'exchangingDeviceInfo.inProgress'
|
||||
| 'exchangingDeviceInfo.timeout'
|
||||
| 'inactive'
|
||||
| 'preparingToExchangeInfo'
|
||||
| 'reviewing'
|
||||
@@ -104,8 +108,11 @@ export interface Typegen0 {
|
||||
| 'reviewing.rejected'
|
||||
| 'waitingForConnection'
|
||||
| 'waitingForVc'
|
||||
| 'waitingForVc.inProgress'
|
||||
| 'waitingForVc.timeout'
|
||||
| {
|
||||
checkingBluetoothService?: 'checking' | 'enabled' | 'requesting';
|
||||
exchangingDeviceInfo?: 'inProgress' | 'timeout';
|
||||
reviewing?:
|
||||
| 'accepted'
|
||||
| 'accepting'
|
||||
@@ -120,6 +127,7 @@ export interface Typegen0 {
|
||||
| 'requestingReceivedVcs'
|
||||
| 'storingVc';
|
||||
};
|
||||
waitingForVc?: 'inProgress' | 'timeout';
|
||||
};
|
||||
'tags': never;
|
||||
}
|
||||
|
||||
298
machines/revoke.ts
Normal file
298
machines/revoke.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
import { TextInput } from 'react-native';
|
||||
import { assign, ErrorPlatformEvent, StateFrom, send, EventFrom } from 'xstate';
|
||||
import { log } from 'xstate/lib/actions';
|
||||
|
||||
import i18n from '../i18n';
|
||||
import { AppServices } from '../shared/GlobalContext';
|
||||
import { ActivityLogEvents } from './activityLog';
|
||||
import { StoreEvents } from './store';
|
||||
import { createModel } from 'xstate/lib/model';
|
||||
import { request } from '../shared/request';
|
||||
import { VcIdType } from '../types/vc';
|
||||
import { MY_VCS_STORE_KEY } from '../shared/constants';
|
||||
|
||||
const model = createModel(
|
||||
{
|
||||
serviceRefs: {} as AppServices,
|
||||
idType: 'VID' as VcIdType,
|
||||
idError: '',
|
||||
otp: '',
|
||||
otpError: '',
|
||||
transactionId: '',
|
||||
requestId: '',
|
||||
VIDs: [] as string[],
|
||||
},
|
||||
{
|
||||
events: {
|
||||
INPUT_OTP: (otp: string) => ({ otp }),
|
||||
VALIDATE_INPUT: () => ({}),
|
||||
READY: (idInputRef: TextInput) => ({ idInputRef }),
|
||||
DISMISS: () => ({}),
|
||||
SELECT_ID_TYPE: (idType: VcIdType) => ({ idType }),
|
||||
REVOKE_VCS: (vcKeys: string[]) => ({ vcKeys }),
|
||||
STORE_RESPONSE: (response: string[]) => ({ response }),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const revokeVidsMachine =
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QCUwDcD2BrMA1AlhLAHSEA2YAxMgKK4DyA0jQPq4DCAyoqAA4ax8AF3wYAdjxAAPRACYAHAFZiARhUB2WQDZFABgCcO-boDMWgDQgAnohWyALLOK6X9k-J3zZirfYC+fpaomDgERKRiaACGZISUAJIAcgAKAKoAKiz06cmS-IIi4pIyCOq6TuomjvYqWlpe3iaWNgiKik4u5fK69vr2uipmAUHo2HiEJPiRMXEAIvGcALIL3Egg+cKiEmslZRVVDrX1so3Nclr6zi61pvLyKoY9wyDBY2EkUQDGn2C8ImJQXDxWaTCAUah0JisDirPgCTZFHaIEyydTEe6yEyaQzqXrtM4IeQmXRXcrqDz9dQPEzPV6hCbEL4-P5TQHAkgAJzAAEcAK5wf5QehCXiUCDiMAREKSunjcJM36CoEg4hcvkC1nC3gIKaYT5RQpiADaugAunl4Ybioh8dZbHtiHoevpFESlIp9DTAi9RvT5d9FazlZyefzYIKtZQwByORgOcReGQDQAzOMAW2IsvejIDLIBwdVoY1AK1OsiGH1hpN5rWGytSIQJix6LsWNkOLxsgJKLRnVMlXJdxq-m9WYZCrzbJBlHmSxWFoKW2tjdRLcx2K0uJdXbtCGMpN03Qe-X03VktN9co+uYjIviYl4vKECRSGSyOQXCO2oBKKkPaNdRQqXsRRBi0EwXQJep5EdFxURdTczB0C9pWzCdb14e9H2fWdlk4WF1ktJcGwUZQ1E0HQDCMUwLF3NQ+lJVFN3kXFvBQt4GTVMNBVlMUJSlMZM0vbMuOLKBZTLPUDS2atP3rH9bH-R0lGA0CzAgxQCRqS4nQ0F0-10SoRxGVDOKLcNWV46NY3jRMU3TITTPCUSLIBCTdQraTxFk2siMRBTSh3FpBhdWCeiAqkiSA4yfSckMQiDT4ZwWPCCLrYiAq8AlBgeYg6jKLF1DaB5ygCb0xAwCA4EkMdwnIMA5Iy6RbEMPKNGJSo2nuD17Gyh5lDqC59HUTwVBqL0TI4urpliCBiAwEVGv85rWipZx5GGxRiV8GjNN3BQALg8bMS0citHYv1JhmwhiAAIy+HAxAgJbvxWto0T6doTlkFwtBOFRuzaMK1G6TdqWGi6rylGZnt8xdlpKLaTGIT7vp+3Q-tkAH9pUZRdNOux9KxGLauvZklXZUgwQauGv2XH7LhMOwai+zcfvkXrd3sHQwsPal7C8WpFEhtCbyDSmXIwl7l00btud5twLj-Op5BF8cxfzdlpYbAXspYhW9iG3w1f9cnNTvB8n21gKBbROpHEAhn20UTngp+lRiCx7nN1uAxMRNkN1Vc8TL2tlaiTRQZtBMCKBe+gkXZJJ1tB6ECBf0FQA8LBL80+MPf3bGC7lRDHT1TptuzuMLDhRMwzoD-O5Fd2wYOuVF-raXx7HUMq-CAA */
|
||||
model.createMachine(
|
||||
{
|
||||
tsTypes: {} as import('./revoke.typegen').Typegen0,
|
||||
schema: {
|
||||
context: model.initialContext,
|
||||
events: {} as EventFrom<typeof model>,
|
||||
},
|
||||
id: 'RevokeVids',
|
||||
initial: 'acceptingVIDs',
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
REVOKE_VCS: {
|
||||
actions: ['setTransactionId', 'clearOtp'],
|
||||
target: 'acceptingOtpInput',
|
||||
},
|
||||
},
|
||||
},
|
||||
invalid: {
|
||||
states: {
|
||||
otp: {},
|
||||
backend: {},
|
||||
},
|
||||
on: {
|
||||
INPUT_OTP: {
|
||||
actions: 'setOtp',
|
||||
target: 'requestingRevoke',
|
||||
},
|
||||
DISMISS: {
|
||||
target: 'idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
acceptingVIDs: {
|
||||
entry: ['setTransactionId', 'clearOtp'],
|
||||
initial: 'idle',
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
REVOKE_VCS: {
|
||||
actions: 'setVIDs',
|
||||
target: '#RevokeVids.acceptingOtpInput',
|
||||
},
|
||||
},
|
||||
},
|
||||
requestingOtp: {
|
||||
invoke: {
|
||||
src: 'requestOtp',
|
||||
onDone: [
|
||||
{
|
||||
actions: log('accepting OTP'),
|
||||
target: '#RevokeVids.acceptingOtpInput',
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: [log('error OTP'), 'setIdBackendError'],
|
||||
target: '#RevokeVids.invalid.backend',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
on: {
|
||||
DISMISS: {
|
||||
target: 'idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
acceptingOtpInput: {
|
||||
entry: 'clearOtp',
|
||||
on: {
|
||||
INPUT_OTP: {
|
||||
actions: 'setOtp',
|
||||
target: 'requestingRevoke',
|
||||
},
|
||||
DISMISS: {
|
||||
target: 'idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
requestingRevoke: {
|
||||
invoke: {
|
||||
src: 'requestRevoke',
|
||||
onDone: {
|
||||
target: 'revokingVc',
|
||||
},
|
||||
onError: {
|
||||
actions: [log('error on Revoking'), 'setOtpError'],
|
||||
target: 'acceptingOtpInput',
|
||||
},
|
||||
},
|
||||
},
|
||||
revokingVc: {
|
||||
entry: ['revokeVID'],
|
||||
on: {
|
||||
STORE_RESPONSE: {
|
||||
target: 'loggingRevoke',
|
||||
},
|
||||
},
|
||||
},
|
||||
loggingRevoke: {
|
||||
entry: [log('loggingRevoke'), 'logRevoked'],
|
||||
on: {
|
||||
DISMISS: {
|
||||
target: 'acceptingVIDs',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
setOtp: model.assign({
|
||||
otp: (_context, event) => event.otp,
|
||||
}),
|
||||
|
||||
setTransactionId: assign({
|
||||
transactionId: () => String(new Date().valueOf()).substring(3, 13),
|
||||
}),
|
||||
|
||||
setVIDs: model.assign({
|
||||
VIDs: (_context, event) => event.vcKeys,
|
||||
}),
|
||||
|
||||
setIdBackendError: assign({
|
||||
idError: (context, event) => {
|
||||
const message = (event as ErrorPlatformEvent).data.message;
|
||||
const ID_ERRORS_MAP = {
|
||||
'UIN invalid': 'invalidUin',
|
||||
'VID invalid': 'invalidVid',
|
||||
'UIN not available in database': 'missingUin',
|
||||
'VID not available in database': 'missingVid',
|
||||
'Invalid Input Parameter - individualId':
|
||||
context.idType === 'UIN' ? 'invalidUin' : 'invalidVid',
|
||||
};
|
||||
return ID_ERRORS_MAP[message]
|
||||
? i18n.t(`errors.backend.${ID_ERRORS_MAP[message]}`, {
|
||||
ns: 'RevokeVids',
|
||||
})
|
||||
: message;
|
||||
},
|
||||
}),
|
||||
|
||||
setOtpError: assign({
|
||||
otpError: (_context, event) => {
|
||||
const message = (event as ErrorPlatformEvent).data.message;
|
||||
const OTP_ERRORS_MAP = {
|
||||
'OTP is invalid': 'invalidOtp',
|
||||
};
|
||||
return OTP_ERRORS_MAP[message]
|
||||
? i18n.t(`errors.backend.${OTP_ERRORS_MAP[message]}`, {
|
||||
ns: 'RevokeVids',
|
||||
})
|
||||
: message;
|
||||
},
|
||||
}),
|
||||
|
||||
clearOtp: assign({ otp: '' }),
|
||||
|
||||
logRevoked: send(
|
||||
(context) =>
|
||||
ActivityLogEvents.LOG_ACTIVITY(
|
||||
context.VIDs.map((vc) => ({
|
||||
_vcKey: vc,
|
||||
action: 'revoked',
|
||||
timestamp: Date.now(),
|
||||
deviceName: '',
|
||||
vcLabel: vc.split(':')[2],
|
||||
}))
|
||||
),
|
||||
{
|
||||
to: (context) => context.serviceRefs.activityLog,
|
||||
}
|
||||
),
|
||||
|
||||
revokeVID: send(
|
||||
(context) => {
|
||||
return StoreEvents.REMOVE_ITEMS(MY_VCS_STORE_KEY, context.VIDs);
|
||||
},
|
||||
{
|
||||
to: (context) => context.serviceRefs.store,
|
||||
}
|
||||
),
|
||||
},
|
||||
|
||||
services: {
|
||||
requestOtp: async (context) => {
|
||||
const transactionId = String(new Date().valueOf()).substring(3, 13);
|
||||
return request('POST', '/req/otp', {
|
||||
individualId: context.VIDs[0].split(':')[2],
|
||||
individualIdType: 'VID',
|
||||
otpChannel: ['EMAIL', 'PHONE'],
|
||||
transactionID: transactionId,
|
||||
});
|
||||
},
|
||||
|
||||
requestRevoke: async (context) => {
|
||||
try {
|
||||
return await Promise.all(
|
||||
context.VIDs.map((vid: string) => {
|
||||
const vidID = vid.split(':')[2];
|
||||
const transactionId = String(new Date().valueOf()).substring(
|
||||
3,
|
||||
13
|
||||
);
|
||||
return request('PATCH', `/vid/${vidID}`, {
|
||||
transactionID: transactionId,
|
||||
vidStatus: 'REVOKED',
|
||||
individualId: vidID,
|
||||
individualIdType: 'VID',
|
||||
otp: context.otp,
|
||||
});
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
guards: {},
|
||||
}
|
||||
);
|
||||
|
||||
export function createRevokeMachine(serviceRefs: AppServices) {
|
||||
return revokeVidsMachine.withContext({
|
||||
...revokeVidsMachine.context,
|
||||
serviceRefs,
|
||||
});
|
||||
}
|
||||
|
||||
type State = StateFrom<typeof revokeVidsMachine>;
|
||||
|
||||
export const RevokeVidsEvents = model.events;
|
||||
|
||||
export function selectIdType(state: State) {
|
||||
return state.context.idType;
|
||||
}
|
||||
|
||||
export function selectIdError(state: State) {
|
||||
return state.context.idError;
|
||||
}
|
||||
|
||||
export function selectOtpError(state: State) {
|
||||
return state.context.otpError;
|
||||
}
|
||||
|
||||
export function selectIsRevokingVc(state: State) {
|
||||
return state.matches('revokingVc');
|
||||
}
|
||||
|
||||
export function selectIsLoggingRevoke(state: State) {
|
||||
return state.matches('loggingRevoke');
|
||||
}
|
||||
|
||||
export function selectIsAcceptingOtpInput(state: State) {
|
||||
return state.matches('acceptingOtpInput');
|
||||
}
|
||||
71
machines/revoke.typegen.ts
Normal file
71
machines/revoke.typegen.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'internalEvents': {
|
||||
'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]': {
|
||||
type: 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.RevokeVids.requestingRevoke:invocation[0]': {
|
||||
type: 'done.invoke.RevokeVids.requestingRevoke:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]': {
|
||||
type: 'error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'error.platform.RevokeVids.requestingRevoke:invocation[0]': {
|
||||
type: 'error.platform.RevokeVids.requestingRevoke:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
'invokeSrcNameMap': {
|
||||
requestOtp: 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]';
|
||||
requestRevoke: 'done.invoke.RevokeVids.requestingRevoke:invocation[0]';
|
||||
};
|
||||
'missingImplementations': {
|
||||
actions: never;
|
||||
services: never;
|
||||
guards: never;
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
clearOtp:
|
||||
| 'DISMISS'
|
||||
| 'REVOKE_VCS'
|
||||
| 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]'
|
||||
| 'error.platform.RevokeVids.requestingRevoke:invocation[0]'
|
||||
| 'xstate.init';
|
||||
logRevoked: 'STORE_RESPONSE';
|
||||
revokeVID: 'done.invoke.RevokeVids.requestingRevoke:invocation[0]';
|
||||
setIdBackendError: 'error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]';
|
||||
setOtp: 'INPUT_OTP';
|
||||
setOtpError: 'error.platform.RevokeVids.requestingRevoke:invocation[0]';
|
||||
setTransactionId: 'DISMISS' | 'REVOKE_VCS' | 'xstate.init';
|
||||
setVIDs: 'REVOKE_VCS';
|
||||
};
|
||||
'eventsCausingServices': {
|
||||
requestOtp: never;
|
||||
requestRevoke: 'INPUT_OTP';
|
||||
};
|
||||
'eventsCausingGuards': {};
|
||||
'eventsCausingDelays': {};
|
||||
'matchesStates':
|
||||
| 'acceptingOtpInput'
|
||||
| 'acceptingVIDs'
|
||||
| 'acceptingVIDs.idle'
|
||||
| 'acceptingVIDs.requestingOtp'
|
||||
| 'idle'
|
||||
| 'invalid'
|
||||
| 'invalid.backend'
|
||||
| 'invalid.otp'
|
||||
| 'loggingRevoke'
|
||||
| 'requestingRevoke'
|
||||
| 'revokingVc'
|
||||
| { acceptingVIDs?: 'idle' | 'requestingOtp'; invalid?: 'backend' | 'otp' };
|
||||
'tags': never;
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
import RNSafetyNetClient from '@bitwala/react-native-safetynet';
|
||||
import { getDeviceId } from 'react-native-device-info';
|
||||
import { ContextFrom } from 'xstate';
|
||||
import { createModel } from 'xstate/lib/model';
|
||||
|
||||
const ATTESTATION_API_KEY = 'YOUR API KEY';
|
||||
const ATTESTATION_ENDPOINT = '';
|
||||
const NONCE_ENDPOINT = '';
|
||||
|
||||
const model = createModel(
|
||||
{
|
||||
nonce: '',
|
||||
jws: '',
|
||||
error: '',
|
||||
},
|
||||
{
|
||||
events: {
|
||||
NONCE_RECEIVED: (nonce: string) => ({ nonce }),
|
||||
ATTESTATION_RECEIVED: (jws: string) => ({ jws }),
|
||||
ERROR: (error: string) => ({ error }),
|
||||
VERIFIED: () => ({}),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type Context = ContextFrom<typeof model>;
|
||||
|
||||
export const safetynetMachine = model.createMachine(
|
||||
{
|
||||
id: 'safetynet',
|
||||
context: model.initialContext,
|
||||
initial: 'requestingAttestation', // 'requestingNonce',
|
||||
states: {
|
||||
requestingNonce: {
|
||||
invoke: {
|
||||
src: 'requestNonce',
|
||||
},
|
||||
},
|
||||
requestingAttestation: {
|
||||
invoke: {
|
||||
src: 'requestAttestation',
|
||||
onDone: '',
|
||||
},
|
||||
},
|
||||
verifyingAttestation: {
|
||||
invoke: {
|
||||
src: 'verifyAttestation',
|
||||
},
|
||||
},
|
||||
verified: {
|
||||
type: 'final',
|
||||
data: {
|
||||
jws: (context: Context) => context.jws,
|
||||
},
|
||||
},
|
||||
failed: {
|
||||
type: 'final',
|
||||
data: {
|
||||
error: (context: Context) => context.error,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
actions: {},
|
||||
|
||||
services: {
|
||||
requestNonce: () => async (callback) => {
|
||||
const nonceResult = await RNSafetyNetClient.requestNonce({
|
||||
endPointUrl: NONCE_ENDPOINT,
|
||||
additionalData: getDeviceId(),
|
||||
});
|
||||
|
||||
if (!nonceResult.nonce || nonceResult.error) {
|
||||
callback(
|
||||
model.events.ERROR(nonceResult.error || 'Nonce request failed.')
|
||||
);
|
||||
} else {
|
||||
callback(model.events.NONCE_RECEIVED(nonceResult.nonce));
|
||||
}
|
||||
},
|
||||
|
||||
requestAttestation: (context) => async (callback) => {
|
||||
const attestationResult =
|
||||
await RNSafetyNetClient.sendAttestationRequest(
|
||||
context.nonce,
|
||||
ATTESTATION_API_KEY
|
||||
);
|
||||
|
||||
if (!attestationResult.jws || attestationResult.error) {
|
||||
callback(
|
||||
model.events.ERROR(
|
||||
attestationResult.error || 'Attestation request failed.'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
callback(model.events.ATTESTATION_RECEIVED(attestationResult.jws));
|
||||
}
|
||||
},
|
||||
|
||||
verifyAttestation: (context) => async (callback) => {
|
||||
const verification = (await RNSafetyNetClient.verifyAttestationResult({
|
||||
endPointUrl: ATTESTATION_ENDPOINT,
|
||||
attestationJws: context.jws,
|
||||
})) as VerificationResult;
|
||||
|
||||
if (!verification.success) {
|
||||
callback(model.events.ERROR('Verfication failed.'));
|
||||
} else {
|
||||
callback(model.events.VERIFIED());
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
interface VerificationResult {
|
||||
success: boolean;
|
||||
}
|
||||
235
machines/scan.ts
235
machines/scan.ts
@@ -2,15 +2,12 @@ import SmartshareReactNative from '@idpass/smartshare-react-native';
|
||||
import { ConnectionParams } from '@idpass/smartshare-react-native/lib/typescript/IdpassSmartshare';
|
||||
const { IdpassSmartshare, GoogleNearbyMessages } = SmartshareReactNative;
|
||||
|
||||
// import LocationEnabler from 'react-native-location-enabler';
|
||||
const LocationEnabler = {} as any;
|
||||
import SystemSetting from 'react-native-system-setting';
|
||||
import { assign, EventFrom, send, sendParent, StateFrom } from 'xstate';
|
||||
import { createModel } from 'xstate/lib/model';
|
||||
import { EmitterSubscription, Linking, Platform } from 'react-native';
|
||||
import { DeviceInfo } from '../components/DeviceInfoList';
|
||||
import { getDeviceNameSync } from 'react-native-device-info';
|
||||
import { VC } from '../types/vc';
|
||||
import { VC, VerifiablePresentation } from '../types/vc';
|
||||
import { AppServices } from '../shared/GlobalContext';
|
||||
import { ActivityLogEvents } from './activityLog';
|
||||
import {
|
||||
@@ -29,10 +26,11 @@ import {
|
||||
SendVcStatus,
|
||||
} from '../shared/smartshare';
|
||||
import { check, PERMISSIONS, PermissionStatus } from 'react-native-permissions';
|
||||
import { checkLocation, requestLocation } from '../shared/location';
|
||||
import { CameraCapturedPicture } from 'expo-camera';
|
||||
|
||||
const checkingAirplaneMode = '#checkingAirplaneMode';
|
||||
const checkingLocationService = '#checkingLocationService';
|
||||
const findingConnection = '#scan.findingConnection';
|
||||
const findingConnectionId = '#scan.findingConnection';
|
||||
const checkingLocationServiceId = '#checkingLocationService';
|
||||
|
||||
type SharingProtocol = 'OFFLINE' | 'ONLINE';
|
||||
|
||||
@@ -46,12 +44,8 @@ const model = createModel(
|
||||
selectedVc: {} as VC,
|
||||
reason: '',
|
||||
loggers: [] as EmitterSubscription[],
|
||||
locationConfig: {
|
||||
// priority: LocationEnabler.PRIORITIES.BALANCED_POWER_ACCURACY,
|
||||
alwaysShow: false,
|
||||
needBle: true,
|
||||
},
|
||||
vcName: '',
|
||||
verificationImage: {} as CameraCapturedPicture,
|
||||
sharingProtocol: 'OFFLINE' as SharingProtocol,
|
||||
scannedQrParams: {} as ConnectionParams,
|
||||
},
|
||||
@@ -73,13 +67,15 @@ const model = createModel(
|
||||
UPDATE_REASON: (reason: string) => ({ reason }),
|
||||
LOCATION_ENABLED: () => ({}),
|
||||
LOCATION_DISABLED: () => ({}),
|
||||
FLIGHT_ENABLED: () => ({}),
|
||||
FLIGHT_DISABLED: () => ({}),
|
||||
FLIGHT_REQUEST: () => ({}),
|
||||
LOCATION_REQUEST: () => ({}),
|
||||
UPDATE_VC_NAME: (vcName: string) => ({ vcName }),
|
||||
STORE_RESPONSE: (response: unknown) => ({ response }),
|
||||
APP_ACTIVE: () => ({}),
|
||||
VERIFY_AND_SELECT_VC: (vc: VC) => ({ vc }),
|
||||
FACE_VALID: () => ({}),
|
||||
FACE_INVALID: () => ({}),
|
||||
RETRY_VERIFICATION: () => ({}),
|
||||
VP_CREATED: (vp: VerifiablePresentation) => ({ vp }),
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -100,42 +96,12 @@ export const scanMachine = model.createMachine(
|
||||
},
|
||||
on: {
|
||||
SCREEN_BLUR: 'inactive',
|
||||
SCREEN_FOCUS: 'checkingAirplaneMode',
|
||||
SCREEN_FOCUS: 'checkingLocationService',
|
||||
},
|
||||
states: {
|
||||
inactive: {
|
||||
entry: ['removeLoggers'],
|
||||
},
|
||||
checkingAirplaneMode: {
|
||||
id: 'checkingAirplaneMode',
|
||||
on: {
|
||||
APP_ACTIVE: '.checkingStatus',
|
||||
},
|
||||
initial: 'checkingStatus',
|
||||
states: {
|
||||
checkingStatus: {
|
||||
invoke: {
|
||||
src: 'checkAirplaneMode',
|
||||
},
|
||||
on: {
|
||||
FLIGHT_DISABLED: checkingLocationService,
|
||||
FLIGHT_ENABLED: 'enabled',
|
||||
},
|
||||
},
|
||||
requestingToDisable: {
|
||||
entry: ['requestToDisableFlightMode'],
|
||||
on: {
|
||||
FLIGHT_DISABLED: checkingLocationService,
|
||||
},
|
||||
},
|
||||
enabled: {
|
||||
on: {
|
||||
FLIGHT_REQUEST: 'requestingToDisable',
|
||||
FLIGHT_DISABLED: checkingLocationService,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
checkingLocationService: {
|
||||
id: 'checkingLocationService',
|
||||
invoke: {
|
||||
@@ -147,7 +113,6 @@ export const scanMachine = model.createMachine(
|
||||
on: {
|
||||
LOCATION_ENABLED: 'checkingPermission',
|
||||
LOCATION_DISABLED: 'requestingToEnable',
|
||||
FLIGHT_ENABLED: checkingAirplaneMode,
|
||||
},
|
||||
},
|
||||
requestingToEnable: {
|
||||
@@ -205,7 +170,6 @@ export const scanMachine = model.createMachine(
|
||||
},
|
||||
{ target: 'invalid' },
|
||||
],
|
||||
FLIGHT_ENABLED: checkingAirplaneMode,
|
||||
},
|
||||
},
|
||||
preparingToConnect: {
|
||||
@@ -224,6 +188,23 @@ export const scanMachine = model.createMachine(
|
||||
on: {
|
||||
CONNECTED: 'exchangingDeviceInfo',
|
||||
},
|
||||
initial: 'inProgress',
|
||||
states: {
|
||||
inProgress: {},
|
||||
timeout: {
|
||||
on: {
|
||||
CANCEL: {
|
||||
actions: 'disconnect',
|
||||
target: checkingLocationServiceId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
CONNECTION_TIMEOUT: {
|
||||
target: '.timeout',
|
||||
},
|
||||
},
|
||||
},
|
||||
exchangingDeviceInfo: {
|
||||
invoke: {
|
||||
@@ -236,6 +217,23 @@ export const scanMachine = model.createMachine(
|
||||
actions: ['setReceiverInfo'],
|
||||
},
|
||||
},
|
||||
initial: 'inProgress',
|
||||
states: {
|
||||
inProgress: {},
|
||||
timeout: {
|
||||
on: {
|
||||
CANCEL: {
|
||||
actions: 'disconnect',
|
||||
target: checkingLocationServiceId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
CONNECTION_TIMEOUT: {
|
||||
target: '.timeout',
|
||||
},
|
||||
},
|
||||
},
|
||||
reviewing: {
|
||||
on: {
|
||||
@@ -251,16 +249,20 @@ export const scanMachine = model.createMachine(
|
||||
idle: {
|
||||
on: {
|
||||
ACCEPT_REQUEST: 'selectingVc',
|
||||
DISCONNECT: findingConnection,
|
||||
DISCONNECT: findingConnectionId,
|
||||
},
|
||||
},
|
||||
selectingVc: {
|
||||
on: {
|
||||
DISCONNECT: findingConnection,
|
||||
DISCONNECT: findingConnectionId,
|
||||
SELECT_VC: {
|
||||
target: 'sendingVc',
|
||||
actions: ['setSelectedVc'],
|
||||
},
|
||||
VERIFY_AND_SELECT_VC: {
|
||||
target: 'verifyingUserIdentity',
|
||||
actions: ['setSelectedVc'],
|
||||
},
|
||||
CANCEL: 'idle',
|
||||
},
|
||||
},
|
||||
@@ -269,10 +271,27 @@ export const scanMachine = model.createMachine(
|
||||
src: 'sendVc',
|
||||
},
|
||||
on: {
|
||||
DISCONNECT: findingConnection,
|
||||
DISCONNECT: findingConnectionId,
|
||||
VC_ACCEPTED: 'accepted',
|
||||
VC_REJECTED: 'rejected',
|
||||
},
|
||||
initial: 'inProgress',
|
||||
states: {
|
||||
inProgress: {},
|
||||
timeout: {
|
||||
on: {
|
||||
CANCEL: {
|
||||
actions: 'disconnect',
|
||||
target: checkingLocationServiceId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
CONNECTION_TIMEOUT: {
|
||||
target: '.timeout',
|
||||
},
|
||||
},
|
||||
},
|
||||
accepted: {
|
||||
entry: ['logShared'],
|
||||
@@ -283,10 +302,28 @@ export const scanMachine = model.createMachine(
|
||||
rejected: {},
|
||||
cancelled: {},
|
||||
navigatingToHome: {},
|
||||
verifyingUserIdentity: {
|
||||
on: {
|
||||
FACE_VALID: {
|
||||
target: 'sendingVc',
|
||||
},
|
||||
FACE_INVALID: {
|
||||
target: 'invalidUserIdentity',
|
||||
},
|
||||
CANCEL: 'selectingVc',
|
||||
},
|
||||
},
|
||||
invalidUserIdentity: {
|
||||
on: {
|
||||
DISMISS: 'selectingVc',
|
||||
RETRY_VERIFICATION: 'verifyingUserIdentity',
|
||||
},
|
||||
},
|
||||
},
|
||||
exit: ['disconnect', 'clearReason'],
|
||||
},
|
||||
disconnected: {
|
||||
id: 'disconnected',
|
||||
on: {
|
||||
DISMISS: 'findingConnection',
|
||||
},
|
||||
@@ -306,17 +343,7 @@ export const scanMachine = model.createMachine(
|
||||
senderInfo: (_, event) => event.info,
|
||||
}),
|
||||
|
||||
requestToEnableLocation: (context) => {
|
||||
LocationEnabler?.requestResolutionSettings(context.locationConfig);
|
||||
},
|
||||
|
||||
requestToDisableFlightMode: () => {
|
||||
if (Platform.OS === 'android') {
|
||||
SystemSetting.switchAirplane();
|
||||
} else {
|
||||
Linking.openURL('App-prefs:root=AIRPLANE_MODE');
|
||||
}
|
||||
},
|
||||
requestToEnableLocation: () => requestLocation(),
|
||||
|
||||
disconnect: (context) => {
|
||||
try {
|
||||
@@ -409,9 +436,7 @@ export const scanMachine = model.createMachine(
|
||||
{ to: (context) => context.serviceRefs.activityLog }
|
||||
),
|
||||
|
||||
openSettings: () => {
|
||||
Linking.openSettings();
|
||||
},
|
||||
openSettings: () => Linking.openSettings(),
|
||||
},
|
||||
|
||||
services: {
|
||||
@@ -424,22 +449,9 @@ export const scanMachine = model.createMachine(
|
||||
if (Platform.OS === 'android') {
|
||||
response = await check(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
|
||||
} else if (Platform.OS === 'ios') {
|
||||
callback(model.events.LOCATION_ENABLED());
|
||||
return;
|
||||
// response = await check(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE);
|
||||
return callback(model.events.LOCATION_ENABLED());
|
||||
}
|
||||
|
||||
// const response = await PermissionsAndroid.request(
|
||||
// PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
||||
// {
|
||||
// title: 'Location access',
|
||||
// message:
|
||||
// 'Location access is required for the scanning functionality.',
|
||||
// buttonNegative: 'Cancel',
|
||||
// buttonPositive: 'OK',
|
||||
// }
|
||||
// );
|
||||
|
||||
if (response === 'granted') {
|
||||
callback(model.events.LOCATION_ENABLED());
|
||||
} else {
|
||||
@@ -463,26 +475,10 @@ export const scanMachine = model.createMachine(
|
||||
},
|
||||
|
||||
checkLocationStatus: () => (callback) => {
|
||||
// const listener = LocationEnabler.addListener(({ locationEnabled }) => {
|
||||
// if (locationEnabled) {
|
||||
// callback(model.events.LOCATION_ENABLED());
|
||||
// } else {
|
||||
// callback(model.events.LOCATION_DISABLED());
|
||||
// }
|
||||
// });
|
||||
// LocationEnabler.checkSettings(context.locationConfig);
|
||||
// return () => listener.remove();
|
||||
callback(model.events.LOCATION_ENABLED());
|
||||
},
|
||||
|
||||
checkAirplaneMode: () => (callback) => {
|
||||
SystemSetting.isAirplaneEnabled().then((enable) => {
|
||||
if (enable) {
|
||||
callback(model.events.FLIGHT_ENABLED());
|
||||
} else {
|
||||
callback(model.events.FLIGHT_DISABLED());
|
||||
}
|
||||
});
|
||||
checkLocation(
|
||||
() => callback(model.events.LOCATION_ENABLED()),
|
||||
() => callback(model.events.LOCATION_DISABLED())
|
||||
);
|
||||
},
|
||||
|
||||
discoverDevice: (context) => (callback) => {
|
||||
@@ -563,7 +559,6 @@ export const scanMachine = model.createMachine(
|
||||
};
|
||||
|
||||
if (context.sharingProtocol === 'OFFLINE') {
|
||||
console.log('OFFLINE?!');
|
||||
const event: SendVcEvent = {
|
||||
type: 'send-vc',
|
||||
data: { isChunked: false, vc },
|
||||
@@ -604,6 +599,9 @@ export const scanMachine = model.createMachine(
|
||||
|
||||
delays: {
|
||||
CLEAR_DELAY: 250,
|
||||
CONNECTION_TIMEOUT: () => {
|
||||
return (Platform.OS === 'ios' ? 15 : 5) * 1000;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -629,16 +627,28 @@ export function selectVcName(state: State) {
|
||||
return state.context.vcName;
|
||||
}
|
||||
|
||||
export function selectSelectedVc(state: State) {
|
||||
return state.context.selectedVc;
|
||||
}
|
||||
|
||||
export function selectIsScanning(state: State) {
|
||||
return state.matches('findingConnection');
|
||||
}
|
||||
|
||||
export function selectIsConnecting(state: State) {
|
||||
return state.matches('connecting');
|
||||
return state.matches('connecting.inProgress');
|
||||
}
|
||||
|
||||
export function selectIsConnectingTimeout(state: State) {
|
||||
return state.matches('connecting.timeout');
|
||||
}
|
||||
|
||||
export function selectIsExchangingDeviceInfo(state: State) {
|
||||
return state.matches('exchangingDeviceInfo');
|
||||
return state.matches('exchangingDeviceInfo.inProgress');
|
||||
}
|
||||
|
||||
export function selectIsExchangingDeviceInfoTimeout(state: State) {
|
||||
return state.matches('exchangingDeviceInfo.timeout');
|
||||
}
|
||||
|
||||
export function selectIsReviewing(state: State) {
|
||||
@@ -650,7 +660,11 @@ export function selectIsSelectingVc(state: State) {
|
||||
}
|
||||
|
||||
export function selectIsSendingVc(state: State) {
|
||||
return state.matches('reviewing.sendingVc');
|
||||
return state.matches('reviewing.sendingVc.inProgress');
|
||||
}
|
||||
|
||||
export function selectIsSendingVcTimeout(state: State) {
|
||||
return state.matches('reviewing.sendingVc.timeout');
|
||||
}
|
||||
|
||||
export function selectIsAccepted(state: State) {
|
||||
@@ -673,15 +687,22 @@ export function selectIsLocationDisabled(state: State) {
|
||||
return state.matches('checkingLocationService.disabled');
|
||||
}
|
||||
|
||||
export function selectIsAirplaneEnabled(state: State) {
|
||||
return state.matches('checkingAirplaneMode.enabled');
|
||||
export function selectIsDone(state: State) {
|
||||
return state.matches('reviewing.navigatingToHome');
|
||||
}
|
||||
|
||||
export function selectIsVerifyingUserIdentity(state: State) {
|
||||
return state.matches('reviewing.verifyingUserIdentity');
|
||||
}
|
||||
|
||||
export function selectIsInvalidUserIdentity(state: State) {
|
||||
return state.matches('reviewing.invalidUserIdentity');
|
||||
}
|
||||
|
||||
async function sendVc(vc: VC, callback: (status: SendVcStatus) => void) {
|
||||
const rawData = JSON.stringify(vc);
|
||||
const chunks = chunkString(rawData, GNM_MESSAGE_LIMIT);
|
||||
if (chunks.length > 1) {
|
||||
console.log('CHUNKED!', chunks.length);
|
||||
let chunk = 0;
|
||||
const vcChunk = {
|
||||
total: chunks.length,
|
||||
@@ -700,7 +721,6 @@ async function sendVc(vc: VC, callback: (status: SendVcStatus) => void) {
|
||||
SendVcResponseType,
|
||||
async (status) => {
|
||||
if (typeof status === 'number' && chunk < event.data.vcChunk.total) {
|
||||
console.log(SendVcResponseType, chunk, chunks[chunk].length);
|
||||
chunk += 1;
|
||||
await GoogleNearbyMessages.unpublish();
|
||||
await onlineSend({
|
||||
@@ -723,7 +743,6 @@ async function sendVc(vc: VC, callback: (status: SendVcStatus) => void) {
|
||||
);
|
||||
await onlineSend(event);
|
||||
} else {
|
||||
console.log('UNCHUNKED');
|
||||
const event: SendVcEvent = {
|
||||
type: 'send-vc',
|
||||
data: { isChunked: false, vc },
|
||||
|
||||
@@ -10,7 +10,6 @@ export interface Typegen0 {
|
||||
'xstate.stop': { type: 'xstate.stop' };
|
||||
};
|
||||
'invokeSrcNameMap': {
|
||||
checkAirplaneMode: 'done.invoke.scan.checkingAirplaneMode.checkingStatus:invocation[0]';
|
||||
checkLocationPermission: 'done.invoke.scan.checkingLocationService.checkingPermission:invocation[0]';
|
||||
checkLocationStatus: 'done.invoke.checkingLocationService:invocation[0]';
|
||||
discoverDevice: 'done.invoke.scan.connecting:invocation[0]';
|
||||
@@ -61,23 +60,21 @@ export interface Typegen0 {
|
||||
| 'xstate.after(CLEAR_DELAY)#clearingConnection'
|
||||
| 'xstate.init';
|
||||
requestSenderInfo: 'SCAN';
|
||||
requestToDisableFlightMode: 'FLIGHT_REQUEST';
|
||||
requestToEnableLocation: 'LOCATION_DISABLED' | 'LOCATION_REQUEST';
|
||||
setConnectionParams: 'SCAN';
|
||||
setReason: 'UPDATE_REASON';
|
||||
setReceiverInfo: 'EXCHANGE_DONE';
|
||||
setScannedQrParams: 'SCAN';
|
||||
setSelectedVc: 'SELECT_VC';
|
||||
setSelectedVc: 'SELECT_VC' | 'VERIFY_AND_SELECT_VC';
|
||||
setSenderInfo: 'RECEIVE_DEVICE_INFO';
|
||||
};
|
||||
'eventsCausingServices': {
|
||||
checkAirplaneMode: 'APP_ACTIVE' | 'FLIGHT_ENABLED' | 'SCREEN_FOCUS';
|
||||
checkLocationPermission: 'APP_ACTIVE' | 'LOCATION_ENABLED';
|
||||
checkLocationStatus: 'FLIGHT_DISABLED';
|
||||
checkLocationStatus: 'CANCEL' | 'SCREEN_FOCUS';
|
||||
discoverDevice: 'RECEIVE_DEVICE_INFO';
|
||||
exchangeDeviceInfo: 'CONNECTED';
|
||||
monitorConnection: 'SCREEN_BLUR' | 'SCREEN_FOCUS' | 'xstate.init';
|
||||
sendVc: 'SELECT_VC';
|
||||
sendVc: 'FACE_VALID' | 'SELECT_VC';
|
||||
};
|
||||
'eventsCausingGuards': {
|
||||
isQrOffline: 'SCAN';
|
||||
@@ -85,12 +82,13 @@ export interface Typegen0 {
|
||||
};
|
||||
'eventsCausingDelays': {
|
||||
CLEAR_DELAY: 'LOCATION_ENABLED';
|
||||
CONNECTION_TIMEOUT:
|
||||
| 'CONNECTED'
|
||||
| 'FACE_VALID'
|
||||
| 'RECEIVE_DEVICE_INFO'
|
||||
| 'SELECT_VC';
|
||||
};
|
||||
'matchesStates':
|
||||
| 'checkingAirplaneMode'
|
||||
| 'checkingAirplaneMode.checkingStatus'
|
||||
| 'checkingAirplaneMode.enabled'
|
||||
| 'checkingAirplaneMode.requestingToDisable'
|
||||
| 'checkingLocationService'
|
||||
| 'checkingLocationService.checkingPermission'
|
||||
| 'checkingLocationService.checkingStatus'
|
||||
@@ -99,8 +97,12 @@ export interface Typegen0 {
|
||||
| 'checkingLocationService.requestingToEnable'
|
||||
| 'clearingConnection'
|
||||
| 'connecting'
|
||||
| 'connecting.inProgress'
|
||||
| 'connecting.timeout'
|
||||
| 'disconnected'
|
||||
| 'exchangingDeviceInfo'
|
||||
| 'exchangingDeviceInfo.inProgress'
|
||||
| 'exchangingDeviceInfo.timeout'
|
||||
| 'findingConnection'
|
||||
| 'inactive'
|
||||
| 'invalid'
|
||||
@@ -109,29 +111,34 @@ export interface Typegen0 {
|
||||
| 'reviewing.accepted'
|
||||
| 'reviewing.cancelled'
|
||||
| 'reviewing.idle'
|
||||
| 'reviewing.invalidUserIdentity'
|
||||
| 'reviewing.navigatingToHome'
|
||||
| 'reviewing.rejected'
|
||||
| 'reviewing.selectingVc'
|
||||
| 'reviewing.sendingVc'
|
||||
| 'reviewing.sendingVc.inProgress'
|
||||
| 'reviewing.sendingVc.timeout'
|
||||
| 'reviewing.verifyingUserIdentity'
|
||||
| {
|
||||
checkingAirplaneMode?:
|
||||
| 'checkingStatus'
|
||||
| 'enabled'
|
||||
| 'requestingToDisable';
|
||||
checkingLocationService?:
|
||||
| 'checkingPermission'
|
||||
| 'checkingStatus'
|
||||
| 'denied'
|
||||
| 'disabled'
|
||||
| 'requestingToEnable';
|
||||
connecting?: 'inProgress' | 'timeout';
|
||||
exchangingDeviceInfo?: 'inProgress' | 'timeout';
|
||||
reviewing?:
|
||||
| 'accepted'
|
||||
| 'cancelled'
|
||||
| 'idle'
|
||||
| 'invalidUserIdentity'
|
||||
| 'navigatingToHome'
|
||||
| 'rejected'
|
||||
| 'selectingVc'
|
||||
| 'sendingVc';
|
||||
| 'sendingVc'
|
||||
| 'verifyingUserIdentity'
|
||||
| { sendingVc?: 'inProgress' | 'timeout' };
|
||||
};
|
||||
'tags': never;
|
||||
}
|
||||
|
||||
@@ -2,18 +2,6 @@
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'eventsCausingActions': {
|
||||
setContext: 'STORE_RESPONSE';
|
||||
toggleBiometricUnlock: 'TOGGLE_BIOMETRIC_UNLOCK';
|
||||
storeContext:
|
||||
| 'TOGGLE_BIOMETRIC_UNLOCK'
|
||||
| 'UPDATE_NAME'
|
||||
| 'UPDATE_VC_LABEL'
|
||||
| 'STORE_RESPONSE';
|
||||
updateName: 'UPDATE_NAME';
|
||||
updateVcLabel: 'UPDATE_VC_LABEL';
|
||||
requestStoredContext: 'xstate.init';
|
||||
};
|
||||
'internalEvents': {
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
@@ -24,11 +12,23 @@ export interface Typegen0 {
|
||||
guards: never;
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
requestStoredContext: 'xstate.init';
|
||||
setContext: 'STORE_RESPONSE';
|
||||
storeContext:
|
||||
| 'STORE_RESPONSE'
|
||||
| 'TOGGLE_BIOMETRIC_UNLOCK'
|
||||
| 'UPDATE_NAME'
|
||||
| 'UPDATE_VC_LABEL';
|
||||
toggleBiometricUnlock: 'TOGGLE_BIOMETRIC_UNLOCK';
|
||||
updateName: 'UPDATE_NAME';
|
||||
updateVcLabel: 'UPDATE_VC_LABEL';
|
||||
};
|
||||
'eventsCausingServices': {};
|
||||
'eventsCausingGuards': {
|
||||
hasData: 'STORE_RESPONSE';
|
||||
};
|
||||
'eventsCausingDelays': {};
|
||||
'matchesStates': 'init' | 'storingDefaults' | 'idle';
|
||||
'matchesStates': 'idle' | 'init' | 'storingDefaults';
|
||||
'tags': never;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ const model = createModel(
|
||||
SET: (key: string, value: unknown) => ({ key, value }),
|
||||
APPEND: (key: string, value: unknown) => ({ key, value }),
|
||||
PREPEND: (key: string, value: unknown) => ({ key, value }),
|
||||
REMOVE: (key: string) => ({ key }),
|
||||
REMOVE: (key: string, value: string) => ({ key, value }),
|
||||
REMOVE_ITEMS: (key: string, values: string[]) => ({ key, values }),
|
||||
CLEAR: () => ({}),
|
||||
ERROR: (error: Error) => ({ error }),
|
||||
STORE_RESPONSE: (response?: unknown, requester?: string) => ({
|
||||
@@ -116,6 +117,9 @@ export const storeMachine =
|
||||
REMOVE: {
|
||||
actions: 'forwardStoreRequest',
|
||||
},
|
||||
REMOVE_ITEMS: {
|
||||
actions: 'forwardStoreRequest',
|
||||
},
|
||||
CLEAR: {
|
||||
actions: 'forwardStoreRequest',
|
||||
},
|
||||
@@ -200,7 +204,21 @@ export const storeMachine =
|
||||
break;
|
||||
}
|
||||
case 'REMOVE': {
|
||||
await removeItem(event.key);
|
||||
await removeItem(
|
||||
event.key,
|
||||
event.value,
|
||||
context.encryptionKey
|
||||
);
|
||||
response = event.value;
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_ITEMS': {
|
||||
await removeItems(
|
||||
event.key,
|
||||
event.values,
|
||||
context.encryptionKey
|
||||
);
|
||||
response = event.values;
|
||||
break;
|
||||
}
|
||||
case 'CLEAR': {
|
||||
@@ -280,6 +298,7 @@ export async function getItem(
|
||||
const data = await AsyncStorage.getItem(key);
|
||||
if (data != null) {
|
||||
const decrypted = decryptJson(encryptionKey, data);
|
||||
|
||||
return JSON.parse(decrypted);
|
||||
} else {
|
||||
return defaultValue;
|
||||
@@ -310,23 +329,67 @@ export async function prependItem(
|
||||
) {
|
||||
try {
|
||||
const list = await getItem(key, [], encryptionKey);
|
||||
const newList = Array.isArray(value)
|
||||
? [...value, ...list]
|
||||
: [value, ...list];
|
||||
|
||||
await setItem(key, [value, ...list], encryptionKey);
|
||||
await setItem(key, newList, encryptionKey);
|
||||
} catch (e) {
|
||||
console.error('error prependItem:', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeItem(key: string) {
|
||||
export async function removeItem(
|
||||
key: string,
|
||||
value: string,
|
||||
encryptionKey: string
|
||||
) {
|
||||
try {
|
||||
await AsyncStorage.removeItem(key);
|
||||
const data = await AsyncStorage.getItem(key);
|
||||
const decrypted = decryptJson(encryptionKey, data);
|
||||
const list = JSON.parse(decrypted);
|
||||
const vcKeyArray = value.split(':');
|
||||
const finalVcKeyArray = vcKeyArray.pop();
|
||||
const finalVcKey = vcKeyArray.join(':');
|
||||
console.log('finalVcKeyArray', finalVcKeyArray);
|
||||
const newList = list.filter((vc: string) => {
|
||||
return !vc.includes(finalVcKey);
|
||||
});
|
||||
|
||||
await setItem(key, newList, encryptionKey);
|
||||
} catch (e) {
|
||||
console.error('error removeItem:', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeItems(
|
||||
key: string,
|
||||
values: string[],
|
||||
encryptionKey: string
|
||||
) {
|
||||
try {
|
||||
const data = await AsyncStorage.getItem(key);
|
||||
const decrypted = decryptJson(encryptionKey, data);
|
||||
const list = JSON.parse(decrypted);
|
||||
const newList = list.filter(function (vc: string) {
|
||||
return !values.find(function (vcKey: string) {
|
||||
const vcKeyArray = vcKey.split(':');
|
||||
const finalVcKeyArray = vcKeyArray.pop();
|
||||
console.log('finalVcKeyArray', finalVcKeyArray);
|
||||
const finalVcKey = vcKeyArray.join(':');
|
||||
return vc.includes(finalVcKey);
|
||||
});
|
||||
});
|
||||
|
||||
await setItem(key, newList, encryptionKey);
|
||||
} catch (e) {
|
||||
console.error('error removeItems:', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function clear() {
|
||||
try {
|
||||
await AsyncStorage.clear();
|
||||
|
||||
@@ -3,27 +3,27 @@
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'internalEvents': {
|
||||
'error.platform.store.resettingStorage:invocation[0]': {
|
||||
type: 'error.platform.store.resettingStorage:invocation[0]';
|
||||
'done.invoke._store': {
|
||||
type: 'done.invoke._store';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.store.resettingStorage:invocation[0]': {
|
||||
type: 'done.invoke.store.resettingStorage:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
'done.invoke._store': {
|
||||
type: 'done.invoke._store';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'error.platform._store': { type: 'error.platform._store'; data: unknown };
|
||||
'error.platform.store.resettingStorage:invocation[0]': {
|
||||
type: 'error.platform.store.resettingStorage:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
'invokeSrcNameMap': {
|
||||
getEncryptionKey: 'done.invoke.store.gettingEncryptionKey:invocation[0]';
|
||||
generateEncryptionKey: 'done.invoke.store.generatingEncryptionKey:invocation[0]';
|
||||
clear: 'done.invoke.store.resettingStorage:invocation[0]';
|
||||
generateEncryptionKey: 'done.invoke.store.generatingEncryptionKey:invocation[0]';
|
||||
getEncryptionKey: 'done.invoke.store.gettingEncryptionKey:invocation[0]';
|
||||
store: 'done.invoke._store';
|
||||
};
|
||||
'missingImplementations': {
|
||||
@@ -33,30 +33,31 @@ export interface Typegen0 {
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
setEncryptionKey: 'KEY_RECEIVED';
|
||||
forwardStoreRequest:
|
||||
| 'GET'
|
||||
| 'SET'
|
||||
| 'APPEND'
|
||||
| 'CLEAR'
|
||||
| 'GET'
|
||||
| 'PREPEND'
|
||||
| 'REMOVE'
|
||||
| 'CLEAR';
|
||||
| 'REMOVE_ITEMS'
|
||||
| 'SET';
|
||||
notifyParent:
|
||||
| 'KEY_RECEIVED'
|
||||
| 'done.invoke.store.resettingStorage:invocation[0]';
|
||||
setEncryptionKey: 'KEY_RECEIVED';
|
||||
};
|
||||
'eventsCausingServices': {
|
||||
getEncryptionKey: 'xstate.init';
|
||||
generateEncryptionKey: 'ERROR';
|
||||
clear: 'KEY_RECEIVED';
|
||||
generateEncryptionKey: 'ERROR';
|
||||
getEncryptionKey: 'xstate.init';
|
||||
store: 'KEY_RECEIVED' | 'done.invoke.store.resettingStorage:invocation[0]';
|
||||
};
|
||||
'eventsCausingGuards': {};
|
||||
'eventsCausingDelays': {};
|
||||
'matchesStates':
|
||||
| 'gettingEncryptionKey'
|
||||
| 'generatingEncryptionKey'
|
||||
| 'resettingStorage'
|
||||
| 'ready';
|
||||
| 'gettingEncryptionKey'
|
||||
| 'ready'
|
||||
| 'resettingStorage';
|
||||
'tags': never;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createModel } from 'xstate/lib/model';
|
||||
import { StoreEvents } from './store';
|
||||
import { VC } from '../types/vc';
|
||||
import { AppServices } from '../shared/GlobalContext';
|
||||
import { respond } from 'xstate/lib/actions';
|
||||
import { log, respond } from 'xstate/lib/actions';
|
||||
import { VcItemEvents } from './vcItem';
|
||||
import {
|
||||
MY_VCS_STORE_KEY,
|
||||
@@ -29,6 +29,7 @@ const model = createModel(
|
||||
VC_RECEIVED: (vcKey: string) => ({ vcKey }),
|
||||
VC_DOWNLOADED: (vc: VC) => ({ vc }),
|
||||
REFRESH_MY_VCS: () => ({}),
|
||||
REFRESH_MY_VCS_TWO: (vc: VC) => ({ vc }),
|
||||
REFRESH_RECEIVED_VCS: () => ({}),
|
||||
GET_RECEIVED_VCS: () => ({}),
|
||||
},
|
||||
@@ -82,6 +83,7 @@ export const vcMachine =
|
||||
idle: {
|
||||
on: {
|
||||
REFRESH_MY_VCS: {
|
||||
actions: [log('REFRESH_MY_VCS:myVcs---')],
|
||||
target: 'refreshing',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,18 +2,6 @@
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'eventsCausingActions': {
|
||||
setMyVcs: 'STORE_RESPONSE';
|
||||
setReceivedVcs: 'STORE_RESPONSE';
|
||||
getReceivedVcsResponse: 'GET_RECEIVED_VCS';
|
||||
getVcItemResponse: 'GET_VC_ITEM';
|
||||
prependToMyVcs: 'VC_ADDED';
|
||||
setDownloadedVc: 'VC_DOWNLOADED';
|
||||
moveExistingVcToTop: 'VC_RECEIVED';
|
||||
prependToReceivedVcs: 'VC_RECEIVED';
|
||||
loadMyVcs: 'REFRESH_MY_VCS';
|
||||
loadReceivedVcs: 'STORE_RESPONSE' | 'REFRESH_RECEIVED_VCS';
|
||||
};
|
||||
'internalEvents': {
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
@@ -24,6 +12,18 @@ export interface Typegen0 {
|
||||
guards: never;
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
getReceivedVcsResponse: 'GET_RECEIVED_VCS';
|
||||
getVcItemResponse: 'GET_VC_ITEM';
|
||||
loadMyVcs: 'REFRESH_MY_VCS' | 'xstate.init';
|
||||
loadReceivedVcs: 'REFRESH_RECEIVED_VCS' | 'STORE_RESPONSE';
|
||||
moveExistingVcToTop: 'VC_RECEIVED';
|
||||
prependToMyVcs: 'VC_ADDED';
|
||||
prependToReceivedVcs: 'VC_RECEIVED';
|
||||
setDownloadedVc: 'VC_DOWNLOADED';
|
||||
setMyVcs: 'STORE_RESPONSE';
|
||||
setReceivedVcs: 'STORE_RESPONSE';
|
||||
};
|
||||
'eventsCausingServices': {};
|
||||
'eventsCausingGuards': {
|
||||
hasExistingReceivedVc: 'VC_RECEIVED';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { assign, ErrorPlatformEvent, EventFrom, send, StateFrom } from 'xstate';
|
||||
import { createModel } from 'xstate/lib/model';
|
||||
import { VC_ITEM_STORE_KEY } from '../shared/constants';
|
||||
import { MY_VCS_STORE_KEY, VC_ITEM_STORE_KEY } from '../shared/constants';
|
||||
import { AppServices } from '../shared/GlobalContext';
|
||||
import { CredentialDownloadResponse, request } from '../shared/request';
|
||||
import {
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
} from '../types/vc';
|
||||
import { StoreEvents } from './store';
|
||||
import { ActivityLogEvents } from './activityLog';
|
||||
import { verifyCredential } from '../shared/verifyCredential';
|
||||
import { verifyCredential } from '../shared/vcjs/verifyCredential';
|
||||
import { log } from 'xstate/lib/actions';
|
||||
|
||||
const model = createModel(
|
||||
{
|
||||
@@ -30,6 +31,7 @@ const model = createModel(
|
||||
otpError: '',
|
||||
idError: '',
|
||||
transactionId: '',
|
||||
revoked: false,
|
||||
},
|
||||
{
|
||||
events: {
|
||||
@@ -44,9 +46,9 @@ const model = createModel(
|
||||
GET_VC_RESPONSE: (vc: VC) => ({ vc }),
|
||||
VERIFY: () => ({}),
|
||||
LOCK_VC: () => ({}),
|
||||
UNLOCK_VC: () => ({}),
|
||||
INPUT_OTP: (otp: string) => ({ otp }),
|
||||
REFRESH: () => ({}),
|
||||
REVOKE_VC: () => ({}),
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -157,8 +159,8 @@ export const vcItemMachine =
|
||||
LOCK_VC: {
|
||||
target: 'requestingOtp',
|
||||
},
|
||||
UNLOCK_VC: {
|
||||
target: 'requestingOtp',
|
||||
REVOKE_VC: {
|
||||
target: 'acceptingRevokeInput',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -228,28 +230,54 @@ export const vcItemMachine =
|
||||
},
|
||||
},
|
||||
requestingOtp: {
|
||||
entry: 'setTransactionId',
|
||||
invoke: {
|
||||
src: 'requestOtp',
|
||||
onDone: [
|
||||
{
|
||||
actions: [log('accepting OTP')],
|
||||
target: 'acceptingOtpInput',
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: [log('error OTP')],
|
||||
target: '#vc-item.invalid.backend',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
acceptingOtpInput: {
|
||||
entry: 'clearOtp',
|
||||
entry: ['clearOtp', 'setTransactionId'],
|
||||
on: {
|
||||
INPUT_OTP: {
|
||||
actions: 'setOtp',
|
||||
target: 'requestingLock',
|
||||
INPUT_OTP: [
|
||||
{
|
||||
actions: [
|
||||
log('setting OTP lock'),
|
||||
'setTransactionId',
|
||||
'setOtp',
|
||||
],
|
||||
target: 'requestingLock',
|
||||
},
|
||||
],
|
||||
DISMISS: {
|
||||
actions: ['clearOtp', 'clearTransactionId'],
|
||||
target: 'idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
acceptingRevokeInput: {
|
||||
entry: [log('acceptingRevokeInput'), 'clearOtp', 'setTransactionId'],
|
||||
on: {
|
||||
INPUT_OTP: [
|
||||
{
|
||||
actions: [
|
||||
log('setting OTP revoke'),
|
||||
'setTransactionId',
|
||||
'setOtp',
|
||||
],
|
||||
target: 'requestingRevoke',
|
||||
},
|
||||
],
|
||||
DISMISS: {
|
||||
actions: ['clearOtp', 'clearTransactionId'],
|
||||
target: 'idle',
|
||||
@@ -274,13 +302,46 @@ export const vcItemMachine =
|
||||
},
|
||||
},
|
||||
lockingVc: {
|
||||
entry: 'storeLock',
|
||||
entry: ['storeLock'],
|
||||
on: {
|
||||
STORE_RESPONSE: {
|
||||
target: 'idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
requestingRevoke: {
|
||||
invoke: {
|
||||
src: 'requestRevoke',
|
||||
onDone: [
|
||||
{
|
||||
actions: [log('doneRevoking'), 'setRevoke'],
|
||||
target: 'revokingVc',
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: [log('OTP error'), 'setOtpError'],
|
||||
target: 'acceptingOtpInput',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
revokingVc: {
|
||||
entry: ['revokeVID'],
|
||||
on: {
|
||||
STORE_RESPONSE: {
|
||||
target: 'loggingRevoke',
|
||||
},
|
||||
},
|
||||
},
|
||||
loggingRevoke: {
|
||||
entry: [log('loggingRevoke'), 'logRevoked'],
|
||||
on: {
|
||||
DISMISS: {
|
||||
target: 'idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -356,6 +417,32 @@ export const vcItemMachine =
|
||||
}
|
||||
),
|
||||
|
||||
logRevoked: send(
|
||||
(context) =>
|
||||
ActivityLogEvents.LOG_ACTIVITY({
|
||||
_vcKey: VC_ITEM_STORE_KEY(context),
|
||||
action: 'revoked',
|
||||
timestamp: Date.now(),
|
||||
deviceName: '',
|
||||
vcLabel: context.tag || context.id,
|
||||
}),
|
||||
{
|
||||
to: (context) => context.serviceRefs.activityLog,
|
||||
}
|
||||
),
|
||||
|
||||
revokeVID: send(
|
||||
(context) => {
|
||||
return StoreEvents.REMOVE(
|
||||
MY_VCS_STORE_KEY,
|
||||
VC_ITEM_STORE_KEY(context)
|
||||
);
|
||||
},
|
||||
{
|
||||
to: (context) => context.serviceRefs.store,
|
||||
}
|
||||
),
|
||||
|
||||
markVcValid: assign((context) => {
|
||||
return {
|
||||
...context,
|
||||
@@ -385,6 +472,10 @@ export const vcItemMachine =
|
||||
locked: (context) => !context.locked,
|
||||
}),
|
||||
|
||||
setRevoke: assign({
|
||||
revoked: () => true,
|
||||
}),
|
||||
|
||||
storeLock: send(
|
||||
(context) => {
|
||||
const { serviceRefs, ...data } = context;
|
||||
@@ -497,6 +588,20 @@ export const vcItemMachine =
|
||||
}
|
||||
return response.response;
|
||||
},
|
||||
|
||||
requestRevoke: async (context) => {
|
||||
try {
|
||||
return request('PATCH', `/vid/${context.id}`, {
|
||||
transactionID: context.transactionId,
|
||||
vidStatus: 'REVOKED',
|
||||
individualId: context.id,
|
||||
individualIdType: 'VID',
|
||||
otp: context.otp,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
guards: {
|
||||
@@ -579,10 +684,22 @@ export function selectIsLockingVc(state: State) {
|
||||
return state.matches('lockingVc');
|
||||
}
|
||||
|
||||
export function selectIsRevokingVc(state: State) {
|
||||
return state.matches('revokingVc');
|
||||
}
|
||||
|
||||
export function selectIsLoggingRevoke(state: State) {
|
||||
return state.matches('loggingRevoke');
|
||||
}
|
||||
|
||||
export function selectIsAcceptingOtpInput(state: State) {
|
||||
return state.matches('acceptingOtpInput');
|
||||
}
|
||||
|
||||
export function selectIsAcceptingRevokeInput(state: State) {
|
||||
return state.matches('acceptingRevokeInput');
|
||||
}
|
||||
|
||||
export function selectIsRequestingOtp(state: State) {
|
||||
return state.matches('requestingOtp');
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ export interface Typegen0 {
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.vc-item.requestingRevoke:invocation[0]': {
|
||||
type: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.vc-item.verifyingCredential:invocation[0]': {
|
||||
type: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
|
||||
data: unknown;
|
||||
@@ -41,6 +46,14 @@ export interface Typegen0 {
|
||||
type: 'error.platform.vc-item.requestingLock:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'error.platform.vc-item.requestingOtp:invocation[0]': {
|
||||
type: 'error.platform.vc-item.requestingOtp:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'error.platform.vc-item.requestingRevoke:invocation[0]': {
|
||||
type: 'error.platform.vc-item.requestingRevoke:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'error.platform.vc-item.verifyingCredential:invocation[0]': {
|
||||
type: 'error.platform.vc-item.verifyingCredential:invocation[0]';
|
||||
data: unknown;
|
||||
@@ -52,6 +65,7 @@ export interface Typegen0 {
|
||||
downloadCredential: 'done.invoke.downloadCredential';
|
||||
requestLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
|
||||
requestOtp: 'done.invoke.vc-item.requestingOtp:invocation[0]';
|
||||
requestRevoke: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
|
||||
verifyCredential: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
|
||||
};
|
||||
'missingImplementations': {
|
||||
@@ -64,10 +78,12 @@ export interface Typegen0 {
|
||||
clearOtp:
|
||||
| ''
|
||||
| 'DISMISS'
|
||||
| 'REVOKE_VC'
|
||||
| 'STORE_RESPONSE'
|
||||
| 'done.invoke.vc-item.requestingOtp:invocation[0]'
|
||||
| 'done.invoke.vc-item.verifyingCredential:invocation[0]'
|
||||
| 'error.platform.vc-item.requestingLock:invocation[0]'
|
||||
| 'error.platform.vc-item.requestingRevoke:invocation[0]'
|
||||
| 'error.platform.vc-item.verifyingCredential:invocation[0]';
|
||||
clearTransactionId:
|
||||
| ''
|
||||
@@ -77,18 +93,28 @@ export interface Typegen0 {
|
||||
| 'error.platform.vc-item.verifyingCredential:invocation[0]';
|
||||
logDownloaded: 'CREDENTIAL_DOWNLOADED';
|
||||
logError: 'error.platform.vc-item.verifyingCredential:invocation[0]';
|
||||
logRevoked: 'STORE_RESPONSE';
|
||||
markVcValid: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
|
||||
requestStoredContext: 'GET_VC_RESPONSE' | 'REFRESH';
|
||||
requestVcContext: 'xstate.init';
|
||||
revokeVID: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
|
||||
setCredential:
|
||||
| 'CREDENTIAL_DOWNLOADED'
|
||||
| 'GET_VC_RESPONSE'
|
||||
| 'STORE_RESPONSE';
|
||||
setLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
|
||||
setOtp: 'INPUT_OTP';
|
||||
setOtpError: 'error.platform.vc-item.requestingLock:invocation[0]';
|
||||
setOtpError:
|
||||
| 'error.platform.vc-item.requestingLock:invocation[0]'
|
||||
| 'error.platform.vc-item.requestingRevoke:invocation[0]';
|
||||
setRevoke: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
|
||||
setTag: 'SAVE_TAG';
|
||||
setTransactionId: 'LOCK_VC' | 'UNLOCK_VC';
|
||||
setTransactionId:
|
||||
| 'INPUT_OTP'
|
||||
| 'REVOKE_VC'
|
||||
| 'done.invoke.vc-item.requestingOtp:invocation[0]'
|
||||
| 'error.platform.vc-item.requestingLock:invocation[0]'
|
||||
| 'error.platform.vc-item.requestingRevoke:invocation[0]';
|
||||
storeContext:
|
||||
| 'CREDENTIAL_DOWNLOADED'
|
||||
| 'done.invoke.vc-item.verifyingCredential:invocation[0]';
|
||||
@@ -103,7 +129,8 @@ export interface Typegen0 {
|
||||
checkStatus: 'STORE_RESPONSE';
|
||||
downloadCredential: 'DOWNLOAD_READY';
|
||||
requestLock: 'INPUT_OTP';
|
||||
requestOtp: 'LOCK_VC' | 'UNLOCK_VC';
|
||||
requestOtp: 'LOCK_VC';
|
||||
requestRevoke: 'INPUT_OTP';
|
||||
verifyCredential: '' | 'VERIFY';
|
||||
};
|
||||
'eventsCausingGuards': {
|
||||
@@ -113,6 +140,7 @@ export interface Typegen0 {
|
||||
'eventsCausingDelays': {};
|
||||
'matchesStates':
|
||||
| 'acceptingOtpInput'
|
||||
| 'acceptingRevokeInput'
|
||||
| 'checkingServerData'
|
||||
| 'checkingServerData.checkingStatus'
|
||||
| 'checkingServerData.downloadingCredential'
|
||||
@@ -125,8 +153,11 @@ export interface Typegen0 {
|
||||
| 'invalid.backend'
|
||||
| 'invalid.otp'
|
||||
| 'lockingVc'
|
||||
| 'loggingRevoke'
|
||||
| 'requestingLock'
|
||||
| 'requestingOtp'
|
||||
| 'requestingRevoke'
|
||||
| 'revokingVc'
|
||||
| 'storingTag'
|
||||
| 'verifyingCredential'
|
||||
| {
|
||||
|
||||
21
package-lock.json
generated
21
package-lock.json
generated
@@ -39,6 +39,7 @@
|
||||
"expo-status-bar": "~1.2.0",
|
||||
"expo-updates": "~0.11.6",
|
||||
"i18next": "^21.6.16",
|
||||
"mosip-mobileid-sdk": "^1.0.10",
|
||||
"react": "17.0.1",
|
||||
"react-i18next": "^11.16.6",
|
||||
"react-native": "0.64.3",
|
||||
@@ -17687,6 +17688,18 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mosip-mobileid-sdk": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/mosip-mobileid-sdk/-/mosip-mobileid-sdk-1.0.10.tgz",
|
||||
"integrity": "sha512-q4yhPUtI3iX1cMr/Di0zVeantaC3ONpaJdb7TWAmMKHIh6JG/yRkkmFlTeKrxOk3RQJ+AaVxXnaypXR2vtIlvw==",
|
||||
"peerDependencies": {
|
||||
"expo": "*",
|
||||
"expo-camera": "*",
|
||||
"expo-modules-core": "*",
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/move-concurrently": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||
@@ -41217,6 +41230,12 @@
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
},
|
||||
"mosip-mobileid-sdk": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/mosip-mobileid-sdk/-/mosip-mobileid-sdk-1.0.10.tgz",
|
||||
"integrity": "sha512-q4yhPUtI3iX1cMr/Di0zVeantaC3ONpaJdb7TWAmMKHIh6JG/yRkkmFlTeKrxOk3RQJ+AaVxXnaypXR2vtIlvw==",
|
||||
"requires": {}
|
||||
},
|
||||
"move-concurrently": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||
@@ -49050,4 +49069,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"expo-status-bar": "~1.2.0",
|
||||
"expo-updates": "~0.11.6",
|
||||
"i18next": "^21.6.16",
|
||||
"mosip-mobileid-sdk": "^1.0.10",
|
||||
"react": "17.0.1",
|
||||
"react-i18next": "^11.16.6",
|
||||
"react-native": "0.64.3",
|
||||
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
} from '@react-navigation/bottom-tabs';
|
||||
import { HomeScreen } from '../screens/Home/HomeScreen';
|
||||
import { ProfileScreen } from '../screens/Profile/ProfileScreen';
|
||||
import { ScanScreen } from '../screens/Scan/ScanScreen';
|
||||
import { RootStackParamList } from './index';
|
||||
import { RequestLayout } from '../screens/Request/RequestLayout';
|
||||
import { ScanLayout } from '../screens/Scan/ScanLayout';
|
||||
|
||||
export const mainRoutes: TabScreen[] = [
|
||||
{
|
||||
@@ -17,7 +17,7 @@ export const mainRoutes: TabScreen[] = [
|
||||
},
|
||||
{
|
||||
name: 'Scan',
|
||||
component: ScanScreen,
|
||||
component: ScanLayout,
|
||||
icon: 'qr-code-scanner',
|
||||
options: {
|
||||
headerShown: false,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"unlock": "Unlock with fingerprint"
|
||||
"unlock": "Unlock with biometrics"
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
selectIsUnenrolled,
|
||||
selectIsUnvailable,
|
||||
} from '../machines/biometrics';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
export function useBiometricScreen(props: RootRouteProps) {
|
||||
const { appService } = useContext(GlobalContext);
|
||||
@@ -21,11 +22,11 @@ export function useBiometricScreen(props: RootRouteProps) {
|
||||
const [initAuthBio, updateInitAuthBio] = useState(true);
|
||||
const [bioState, bioSend, bioService] = useMachine(biometricsMachine);
|
||||
|
||||
const isAuthorized: boolean = useSelector(authService, selectAuthorized);
|
||||
const isAvailable: boolean = useSelector(bioService, selectIsAvailable);
|
||||
const isUnavailable: boolean = useSelector(bioService, selectIsUnvailable);
|
||||
const isSuccessBio: boolean = useSelector(bioService, selectIsSuccess);
|
||||
const isUnenrolled: boolean = useSelector(bioService, selectIsUnenrolled);
|
||||
const isAuthorized = useSelector(authService, selectAuthorized);
|
||||
const isAvailable = useSelector(bioService, selectIsAvailable);
|
||||
const isUnavailable = useSelector(bioService, selectIsUnvailable);
|
||||
const isSuccessBio = useSelector(bioService, selectIsSuccess);
|
||||
const isUnenrolled = useSelector(bioService, selectIsUnenrolled);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('bioState', bioState);
|
||||
@@ -59,16 +60,20 @@ export function useBiometricScreen(props: RootRouteProps) {
|
||||
}, [isAuthorized, isAvailable, isUnenrolled, isUnavailable, isSuccessBio]);
|
||||
|
||||
const checkBiometricsChange = () => {
|
||||
RNFingerprintChange.hasFingerPrintChanged().then(
|
||||
async (biometricsHasChanged: any) => {
|
||||
//if new biometrics are added, re-enable Biometrics Authentication
|
||||
if (biometricsHasChanged) {
|
||||
setReEnabling(true);
|
||||
} else {
|
||||
bioSend({ type: 'AUTHENTICATE' });
|
||||
if (Platform.OS === 'android') {
|
||||
RNFingerprintChange.hasFingerPrintChanged().then(
|
||||
async (biometricsHasChanged: any) => {
|
||||
//if new biometrics are added, re-enable Biometrics Authentication
|
||||
if (biometricsHasChanged) {
|
||||
setReEnabling(true);
|
||||
} else {
|
||||
bioSend({ type: 'AUTHENTICATE' });
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
} else {
|
||||
// TODO: solution for iOS
|
||||
}
|
||||
};
|
||||
|
||||
const useBiometrics = () => {
|
||||
|
||||
@@ -38,7 +38,7 @@ export const HistoryTab: React.FC<HomeScreenTabProps> = (props) => {
|
||||
}>
|
||||
{controller.activities.map((activity) => (
|
||||
<TextItem
|
||||
key={activity.timestamp}
|
||||
key={`${activity.timestamp}-${activity._vcKey}`}
|
||||
label={createLabel(activity, i18n.language)}
|
||||
text={`${activity.vcLabel} ${t(activity.action)}`}
|
||||
/>
|
||||
|
||||
@@ -53,6 +53,9 @@ export const HomeScreen: React.FC<HomeRouteProps> = (props) => {
|
||||
isVisible={controller.isViewingVc}
|
||||
onDismiss={controller.DISMISS_MODAL}
|
||||
vcItemActor={controller.selectedVc}
|
||||
onRevokeDelete={() => {
|
||||
controller.REVOKE();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
selectTabsLoaded,
|
||||
selectViewingVc,
|
||||
} from './HomeScreenMachine';
|
||||
import { VcEvents } from '../../machines/vc';
|
||||
|
||||
export function useHomeScreen(props: HomeRouteProps) {
|
||||
const { appService } = useContext(GlobalContext);
|
||||
@@ -23,6 +24,7 @@ export function useHomeScreen(props: HomeRouteProps) {
|
||||
);
|
||||
const service = useInterpret(machine.current);
|
||||
const settingsService = appService.children.get('settings');
|
||||
const vcService = appService.children.get('vc');
|
||||
|
||||
useEffect(() => {
|
||||
if (props.route.params?.activeTab != null) {
|
||||
@@ -43,6 +45,10 @@ export function useHomeScreen(props: HomeRouteProps) {
|
||||
|
||||
SELECT_TAB,
|
||||
DISMISS_MODAL: () => service.send(HomeScreenEvents.DISMISS_MODAL()),
|
||||
REVOKE: () => {
|
||||
vcService.send(VcEvents.REFRESH_MY_VCS());
|
||||
service.send(HomeScreenEvents.DISMISS_MODAL());
|
||||
},
|
||||
};
|
||||
|
||||
function SELECT_TAB(index: number) {
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'eventsCausingActions': {
|
||||
setSelectedVc: 'VIEW_VC';
|
||||
spawnTabActors: 'xstate.init';
|
||||
resetSelectedVc: 'DISMISS_MODAL';
|
||||
};
|
||||
'internalEvents': {
|
||||
'xstate.after(100)#HomeScreen.tabs.init': {
|
||||
type: 'xstate.after(100)#HomeScreen.tabs.init';
|
||||
@@ -20,21 +15,26 @@ export interface Typegen0 {
|
||||
guards: never;
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
resetSelectedVc: 'DISMISS_MODAL' | 'xstate.init';
|
||||
setSelectedVc: 'VIEW_VC';
|
||||
spawnTabActors: 'xstate.init';
|
||||
};
|
||||
'eventsCausingServices': {};
|
||||
'eventsCausingGuards': {};
|
||||
'eventsCausingDelays': {};
|
||||
'matchesStates':
|
||||
| 'tabs'
|
||||
| 'tabs.init'
|
||||
| 'tabs.myVcs'
|
||||
| 'tabs.receivedVcs'
|
||||
| 'tabs.history'
|
||||
| 'modals'
|
||||
| 'modals.none'
|
||||
| 'modals.viewingVc'
|
||||
| 'tabs'
|
||||
| 'tabs.history'
|
||||
| 'tabs.init'
|
||||
| 'tabs.myVcs'
|
||||
| 'tabs.receivedVcs'
|
||||
| {
|
||||
tabs?: 'init' | 'myVcs' | 'receivedVcs' | 'history';
|
||||
modals?: 'none' | 'viewingVc';
|
||||
tabs?: 'history' | 'init' | 'myVcs' | 'receivedVcs';
|
||||
};
|
||||
'tags': never;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export const AddVcModal: React.FC<AddVcModalProps> = (props) => {
|
||||
<MessageOverlay
|
||||
isVisible={controller.isRequestingCredential}
|
||||
title={t('requestingCredential')}
|
||||
hasProgress
|
||||
progress
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -299,6 +299,9 @@ export const AddVcModalMachine =
|
||||
},
|
||||
|
||||
requestCredential: async (context) => {
|
||||
// force wait to fix issue with hanging overlay
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
const response = await request('POST', '/credentialshare/request', {
|
||||
individualId: context.id,
|
||||
individualIdType: context.idType,
|
||||
@@ -312,7 +315,11 @@ export const AddVcModalMachine =
|
||||
guards: {
|
||||
isEmptyId: ({ id }) => !id || !id.length,
|
||||
|
||||
isWrongIdFormat: ({ id }) => !/^\d{10,16}$/.test(id),
|
||||
isWrongIdFormat: ({ idType, id }) => {
|
||||
const validIdType =
|
||||
idType === 'UIN' ? id.length === 10 : id.length === 16;
|
||||
return !(/^\d{10,16}$/.test(id) && validIdType);
|
||||
},
|
||||
|
||||
isIdInvalid: (_context, event: unknown) =>
|
||||
['IDA-MLC-009', 'RES-SER-29', 'IDA-MLC-018'].includes(
|
||||
|
||||
@@ -3,8 +3,15 @@
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'internalEvents': {
|
||||
'xstate.after(100)#AddVcModal.acceptingIdInput.focusing': {
|
||||
type: 'xstate.after(100)#AddVcModal.acceptingIdInput.focusing';
|
||||
'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]': {
|
||||
type: 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.AddVcModal.requestingCredential:invocation[0]': {
|
||||
type: 'done.invoke.AddVcModal.requestingCredential:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'error.platform.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]': {
|
||||
type: 'error.platform.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]';
|
||||
@@ -14,21 +21,14 @@ export interface Typegen0 {
|
||||
type: 'error.platform.AddVcModal.requestingCredential:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'done.invoke.AddVcModal.requestingCredential:invocation[0]': {
|
||||
type: 'done.invoke.AddVcModal.requestingCredential:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
'xstate.after(100)#AddVcModal.acceptingIdInput.focusing': {
|
||||
type: 'xstate.after(100)#AddVcModal.acceptingIdInput.focusing';
|
||||
};
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]': {
|
||||
type: 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
};
|
||||
'invokeSrcNameMap': {
|
||||
requestOtp: 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]';
|
||||
requestCredential: 'done.invoke.AddVcModal.requestingCredential:invocation[0]';
|
||||
requestOtp: 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]';
|
||||
};
|
||||
'missingImplementations': {
|
||||
actions: never;
|
||||
@@ -37,69 +37,69 @@ export interface Typegen0 {
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
forwardToParent: 'DISMISS';
|
||||
setIdInputRef: 'READY';
|
||||
setId: 'INPUT_ID';
|
||||
setIdType: 'SELECT_ID_TYPE';
|
||||
clearId: 'SELECT_ID_TYPE';
|
||||
clearIdError: 'INPUT_ID';
|
||||
setIdBackendError:
|
||||
| 'error.platform.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]'
|
||||
| 'error.platform.AddVcModal.requestingCredential:invocation[0]';
|
||||
setOtp: 'INPUT_OTP';
|
||||
resetIdInputRef: 'DISMISS';
|
||||
setRequestId: 'done.invoke.AddVcModal.requestingCredential:invocation[0]';
|
||||
setOtpError: 'error.platform.AddVcModal.requestingCredential:invocation[0]';
|
||||
setTransactionId:
|
||||
| 'xstate.init'
|
||||
| 'DISMISS'
|
||||
| 'error.platform.AddVcModal.requestingCredential:invocation[0]';
|
||||
clearOtp:
|
||||
| 'xstate.init'
|
||||
| 'DISMISS'
|
||||
| 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]'
|
||||
| 'error.platform.AddVcModal.requestingCredential:invocation[0]'
|
||||
| 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]';
|
||||
| 'xstate.init';
|
||||
focusInput:
|
||||
| 'xstate.after(100)#AddVcModal.acceptingIdInput.focusing'
|
||||
| 'INPUT_ID'
|
||||
| 'SELECT_ID_TYPE'
|
||||
| 'VALIDATE_INPUT'
|
||||
| 'error.platform.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]'
|
||||
| 'error.platform.AddVcModal.requestingCredential:invocation[0]'
|
||||
| 'xstate.after(100)#AddVcModal.acceptingIdInput.focusing';
|
||||
forwardToParent: 'DISMISS';
|
||||
resetIdInputRef: 'DISMISS';
|
||||
setId: 'INPUT_ID';
|
||||
setIdBackendError:
|
||||
| 'error.platform.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]'
|
||||
| 'error.platform.AddVcModal.requestingCredential:invocation[0]';
|
||||
setIdErrorEmpty: 'VALIDATE_INPUT';
|
||||
setIdErrorWrongFormat: 'VALIDATE_INPUT';
|
||||
setIdInputRef: 'READY';
|
||||
setIdType: 'SELECT_ID_TYPE';
|
||||
setOtp: 'INPUT_OTP';
|
||||
setOtpError: 'error.platform.AddVcModal.requestingCredential:invocation[0]';
|
||||
setRequestId: 'done.invoke.AddVcModal.requestingCredential:invocation[0]';
|
||||
setTransactionId:
|
||||
| 'DISMISS'
|
||||
| 'error.platform.AddVcModal.requestingCredential:invocation[0]'
|
||||
| 'xstate.init';
|
||||
};
|
||||
'eventsCausingServices': {
|
||||
requestOtp: 'VALIDATE_INPUT';
|
||||
requestCredential: 'INPUT_OTP';
|
||||
requestOtp: 'VALIDATE_INPUT';
|
||||
};
|
||||
'eventsCausingGuards': {
|
||||
isEmptyId: 'VALIDATE_INPUT';
|
||||
isWrongIdFormat: 'VALIDATE_INPUT';
|
||||
isIdInvalid: 'error.platform.AddVcModal.requestingCredential:invocation[0]';
|
||||
isWrongIdFormat: 'VALIDATE_INPUT';
|
||||
};
|
||||
'eventsCausingDelays': {};
|
||||
'matchesStates':
|
||||
| 'acceptingIdInput'
|
||||
| 'acceptingIdInput.rendering'
|
||||
| 'acceptingIdInput.focusing'
|
||||
| 'acceptingIdInput.idle'
|
||||
| 'acceptingIdInput.invalid'
|
||||
| 'acceptingIdInput.invalid.backend'
|
||||
| 'acceptingIdInput.invalid.empty'
|
||||
| 'acceptingIdInput.invalid.format'
|
||||
| 'acceptingIdInput.invalid.backend'
|
||||
| 'acceptingIdInput.rendering'
|
||||
| 'acceptingIdInput.requestingOtp'
|
||||
| 'acceptingOtpInput'
|
||||
| 'requestingCredential'
|
||||
| 'done'
|
||||
| 'requestingCredential'
|
||||
| {
|
||||
acceptingIdInput?:
|
||||
| 'rendering'
|
||||
| 'focusing'
|
||||
| 'idle'
|
||||
| 'invalid'
|
||||
| 'rendering'
|
||||
| 'requestingOtp'
|
||||
| { invalid?: 'empty' | 'format' | 'backend' };
|
||||
| { invalid?: 'backend' | 'empty' | 'format' };
|
||||
};
|
||||
'tags': never;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Modal } from '../../../components/ui/Modal';
|
||||
import { Theme } from '../../../components/ui/styleUtils';
|
||||
import { IdInputModalProps, useIdInputModal } from './IdInputModalController';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { KeyboardAvoidingView, Platform } from 'react-native';
|
||||
import { KeyboardAvoidingView, Platform, TextInput } from 'react-native';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import { individualId } from '../../../shared/constants';
|
||||
import { GET_INDIVIDUAL_ID } from '../../../shared/constants';
|
||||
@@ -26,6 +26,9 @@ export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
|
||||
|
||||
const inputLabel = t('enterId', { idType: controller.idType });
|
||||
|
||||
const setIdInputRef = (node: TextInput) =>
|
||||
!controller.idInputRef && controller.READY(node);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onDismiss={dismissInput}
|
||||
@@ -77,9 +80,7 @@ export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
|
||||
errorStyle={{ color: Theme.Colors.errorMessage }}
|
||||
errorMessage={controller.idError}
|
||||
onChangeText={controller.INPUT_ID}
|
||||
ref={(node) =>
|
||||
!controller.idInputRef && controller.READY(node)
|
||||
}
|
||||
ref={setIdInputRef}
|
||||
/>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
58
screens/Home/MyVcs/OtpVerification.tsx
Normal file
58
screens/Home/MyVcs/OtpVerification.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dimensions, StyleSheet, View } from 'react-native';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { PinInput } from '../../../components/PinInput';
|
||||
import { Column, Text } from '../../../components/ui';
|
||||
import { ModalProps } from '../../../components/ui/Modal';
|
||||
import { Colors } from '../../../components/ui/styleUtils';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modal: {
|
||||
width: Dimensions.get('screen').width,
|
||||
height: Dimensions.get('screen').height,
|
||||
},
|
||||
viewContainer: {
|
||||
backgroundColor: Colors.White,
|
||||
width: Dimensions.get('screen').width,
|
||||
height: Dimensions.get('screen').height,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
zIndex: 9,
|
||||
padding: 32,
|
||||
},
|
||||
close: {
|
||||
position: 'absolute',
|
||||
top: 32,
|
||||
right: 0,
|
||||
color: Colors.Orange,
|
||||
},
|
||||
});
|
||||
|
||||
export const OtpVerification: React.FC<OtpVerificationModalProps> = (props) => {
|
||||
const { t } = useTranslation('OtpVerificationModal');
|
||||
|
||||
return (
|
||||
<View style={styles.viewContainer}>
|
||||
<Column fill padding="32" backgroundColor={Colors.White}>
|
||||
<View style={styles.close}>
|
||||
<Icon name="close" onPress={() => props.onDismiss()} />
|
||||
</View>
|
||||
<Icon name="lock" color={Colors.Orange} size={60} />
|
||||
<Column fill align="space-between">
|
||||
<Text align="center">{t('enterOtp')}</Text>
|
||||
<Text align="center" color={Colors.Red} margin="16 0 0 0">
|
||||
{props.error}
|
||||
</Text>
|
||||
<PinInput length={6} onDone={props.onInputDone} />
|
||||
</Column>
|
||||
<Column fill></Column>
|
||||
</Column>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
interface OtpVerificationModalProps extends ModalProps {
|
||||
onInputDone: (otp: string) => void;
|
||||
error?: string;
|
||||
}
|
||||
@@ -41,9 +41,9 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
|
||||
onRefresh={controller.REFRESH}
|
||||
/>
|
||||
}>
|
||||
{controller.vcKeys.map((vcKey) => (
|
||||
{controller.vcKeys.map((vcKey, index) => (
|
||||
<VcItem
|
||||
key={vcKey}
|
||||
key={`${vcKey}-${index}`}
|
||||
vcKey={vcKey}
|
||||
margin="0 2 8 2"
|
||||
onPress={controller.VIEW_VC}
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'eventsCausingActions': {
|
||||
completeOnboarding: 'ADD_VC' | 'ONBOARDING_DONE';
|
||||
sendVcAdded: 'STORE_RESPONSE';
|
||||
getOnboardingStatus: 'xstate.init';
|
||||
viewVcFromParent: 'VIEW_VC';
|
||||
storeVcItem: 'done.invoke.AddVcModal';
|
||||
};
|
||||
'internalEvents': {
|
||||
'done.invoke.AddVcModal': {
|
||||
type: 'done.invoke.AddVcModal';
|
||||
@@ -24,20 +17,27 @@ export interface Typegen0 {
|
||||
guards: never;
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
completeOnboarding: 'ADD_VC' | 'ONBOARDING_DONE';
|
||||
getOnboardingStatus: 'xstate.init';
|
||||
sendVcAdded: 'STORE_RESPONSE';
|
||||
storeVcItem: 'done.invoke.AddVcModal';
|
||||
viewVcFromParent: 'VIEW_VC';
|
||||
};
|
||||
'eventsCausingServices': {};
|
||||
'eventsCausingGuards': {
|
||||
isOnboardingDone: 'STORE_RESPONSE';
|
||||
};
|
||||
'eventsCausingDelays': {};
|
||||
'matchesStates':
|
||||
| 'checkingOnboardingStatus'
|
||||
| 'onboarding'
|
||||
| 'idle'
|
||||
| 'viewingVc'
|
||||
| 'addingVc'
|
||||
| 'addingVc.waitingForvcKey'
|
||||
| 'addingVc.storing'
|
||||
| 'addingVc.addVcSuccessful'
|
||||
| { addingVc?: 'waitingForvcKey' | 'storing' | 'addVcSuccessful' };
|
||||
| 'addingVc.storing'
|
||||
| 'addingVc.waitingForvcKey'
|
||||
| 'checkingOnboardingStatus'
|
||||
| 'idle'
|
||||
| 'onboarding'
|
||||
| 'viewingVc'
|
||||
| { addingVc?: 'addVcSuccessful' | 'storing' | 'waitingForvcKey' };
|
||||
'tags': never;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'eventsCausingActions': {
|
||||
viewVcFromParent: 'VIEW_VC';
|
||||
};
|
||||
'internalEvents': {
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
@@ -15,6 +12,9 @@ export interface Typegen0 {
|
||||
guards: never;
|
||||
delays: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
viewVcFromParent: 'VIEW_VC';
|
||||
};
|
||||
'eventsCausingServices': {};
|
||||
'eventsCausingGuards': {};
|
||||
'eventsCausingDelays': {};
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
{
|
||||
"cancel": "Cancel",
|
||||
"lock": "Lock",
|
||||
"unlock": "Unlock",
|
||||
"rename": "Rename",
|
||||
"delete": "Delete",
|
||||
"revoke": "Revoke",
|
||||
"revoking": "Your wallet contains a credential with VID {{vid}}. Revoking this will automatically remove the same from the wallet. Are you sure you want to proceed?",
|
||||
"requestingOtp": "Requesting OTP...",
|
||||
"editTag": "Edit Tag"
|
||||
"editTag": "Rename",
|
||||
"redirecting": "Redirecting...",
|
||||
"success": {
|
||||
"unlocked": "{{vcLabel}} successfully unlocked",
|
||||
"locked": "{{vcLabel}} successfully locked",
|
||||
"revoked": "VID {{vid}} has been revoked. Any credential containing the same will be removed automatically from the wallet"
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,43 @@
|
||||
import React from 'react';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { DropdownIcon } from '../../components/DropdownIcon';
|
||||
import { TextEditOverlay } from '../../components/TextEditOverlay';
|
||||
import { Column } from '../../components/ui';
|
||||
import { Modal } from '../../components/ui/Modal';
|
||||
import { Theme } from '../../components/ui/styleUtils';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
import { ToastItem } from '../../components/ui/ToastItem';
|
||||
import { Passcode } from '../../components/Passcode';
|
||||
import { OtpVerificationModal } from './MyVcs/OtpVerificationModal';
|
||||
import { RevokeConfirmModal } from '../../components/RevokeConfirm';
|
||||
import { OIDcAuthenticationModal } from '../../components/OIDcAuth';
|
||||
import { useViewVcModal, ViewVcModalProps } from './ViewVcModalController';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { VcDetails } from '../../components/VcDetails';
|
||||
|
||||
import { OtpVerification } from './MyVcs/OtpVerification';
|
||||
|
||||
export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
|
||||
const { t } = useTranslation('ViewVcModal');
|
||||
const controller = useViewVcModal(props);
|
||||
|
||||
const DATA = [
|
||||
{
|
||||
idType: 'UIN',
|
||||
label: controller.vc.locked ? 'Unlock' : 'Lock',
|
||||
icon: 'lock-outline',
|
||||
onPress: () => controller.lockVc(),
|
||||
},
|
||||
{
|
||||
idType: 'VID',
|
||||
label: t('revoke'),
|
||||
icon: 'close-circle-outline',
|
||||
onPress: () => controller.CONFIRM_REVOKE_VC(),
|
||||
},
|
||||
{
|
||||
label: t('editTag'),
|
||||
icon: 'pencil',
|
||||
onPress: () => controller.EDIT_TAG(),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isVisible={props.isVisible}
|
||||
@@ -23,10 +45,10 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
|
||||
headerTitle={controller.vc.tag || controller.vc.id}
|
||||
headerElevation={2}
|
||||
headerRight={
|
||||
<Icon
|
||||
name="edit"
|
||||
onPress={controller.EDIT_TAG}
|
||||
color={Theme.Colors.Icon}
|
||||
<DropdownIcon
|
||||
icon="dots-vertical"
|
||||
idType={controller.vc.idType}
|
||||
items={DATA}
|
||||
/>
|
||||
}>
|
||||
<Column scroll>
|
||||
@@ -34,38 +56,50 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
|
||||
<VcDetails vc={controller.vc} />
|
||||
</Column>
|
||||
</Column>
|
||||
{controller.isEditingTag && (
|
||||
<TextEditOverlay
|
||||
isVisible={controller.isEditingTag}
|
||||
label={t('editTag')}
|
||||
value={controller.vc.tag}
|
||||
onDismiss={controller.DISMISS}
|
||||
onSave={controller.SAVE_TAG}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TextEditOverlay
|
||||
isVisible={controller.isEditingTag}
|
||||
label={t('editTag')}
|
||||
value={controller.vc.tag}
|
||||
onDismiss={controller.DISMISS}
|
||||
onSave={controller.SAVE_TAG}
|
||||
/>
|
||||
{controller.isAcceptingRevokeInput && (
|
||||
<OIDcAuthenticationModal
|
||||
isVisible={controller.isAcceptingRevokeInput}
|
||||
onDismiss={controller.DISMISS}
|
||||
onVerify={() => {
|
||||
controller.revokeVc('111111');
|
||||
}}
|
||||
error={controller.otpError}
|
||||
/>
|
||||
)}
|
||||
|
||||
<OtpVerificationModal
|
||||
isVisible={controller.isAcceptingOtpInput}
|
||||
onDismiss={controller.DISMISS}
|
||||
onInputDone={controller.inputOtp}
|
||||
error={controller.otpError}
|
||||
/>
|
||||
{controller.isAcceptingOtpInput && (
|
||||
<OtpVerification
|
||||
isVisible={controller.isAcceptingOtpInput}
|
||||
onDismiss={controller.DISMISS}
|
||||
onInputDone={controller.inputOtp}
|
||||
error={controller.otpError}
|
||||
/>
|
||||
)}
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isRequestingOtp}
|
||||
title={t('requestingOtp')}
|
||||
hasProgress
|
||||
progress
|
||||
/>
|
||||
|
||||
{controller.reAuthenticating !== '' &&
|
||||
controller.reAuthenticating == 'passcode' && (
|
||||
<Passcode
|
||||
onSuccess={() => controller.onSuccess()}
|
||||
onError={(value) => controller.onError(value)}
|
||||
storedPasscode={controller.storedPasscode}
|
||||
onDismiss={() => controller.setReAuthenticating('')}
|
||||
error={controller.error}
|
||||
/>
|
||||
)}
|
||||
{controller.isRevoking && (
|
||||
<RevokeConfirmModal
|
||||
id={controller.vc.id}
|
||||
onCancel={() => controller.setRevoking(false)}
|
||||
onRevoke={controller.REVOKE_VC}
|
||||
/>
|
||||
)}
|
||||
|
||||
{controller.toastVisible && <ToastItem message={controller.message} />}
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
import { useMachine, useSelector } from '@xstate/react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { ActorRefFrom } from 'xstate';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
import { ModalProps } from '../../components/ui/Modal';
|
||||
import { GlobalContext } from '../../shared/GlobalContext';
|
||||
import {
|
||||
selectOtpError,
|
||||
selectIsAcceptingOtpInput,
|
||||
selectIsAcceptingRevokeInput,
|
||||
selectIsEditingTag,
|
||||
selectIsLockingVc,
|
||||
selectIsRequestingOtp,
|
||||
selectIsRevokingVc,
|
||||
selectIsLoggingRevoke,
|
||||
selectVc,
|
||||
VcItemEvents,
|
||||
vcItemMachine,
|
||||
} from '../../machines/vcItem';
|
||||
import { selectPasscode } from '../../machines/auth';
|
||||
import {
|
||||
biometricsMachine,
|
||||
selectIsAvailable,
|
||||
selectIsSuccess,
|
||||
} from '../../machines/biometrics';
|
||||
import { selectBiometricUnlockEnabled } from '../../machines/settings';
|
||||
import { biometricsMachine, selectIsSuccess } from '../../machines/biometrics';
|
||||
import { selectVcLabel } from '../../machines/settings';
|
||||
|
||||
export function useViewVcModal({ vcItemActor, isVisible }: ViewVcModalProps) {
|
||||
export function useViewVcModal({
|
||||
vcItemActor,
|
||||
isVisible,
|
||||
onRevokeDelete,
|
||||
}: ViewVcModalProps) {
|
||||
const { t } = useTranslation('ViewVcModal');
|
||||
const [toastVisible, setToastVisible] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const [reAuthenticating, setReAuthenticating] = useState('');
|
||||
const [isRevoking, setRevoking] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const authService = appService.children.get('auth');
|
||||
const settingsService = appService.children.get('settings');
|
||||
const [, bioSend, bioService] = useMachine(biometricsMachine);
|
||||
const isBiometricUnlockEnabled = useSelector(
|
||||
settingsService,
|
||||
selectBiometricUnlockEnabled
|
||||
);
|
||||
const isAvailable = useSelector(bioService, selectIsAvailable);
|
||||
const isSuccessBio = useSelector(bioService, selectIsSuccess);
|
||||
const isLockingVc = useSelector(vcItemActor, selectIsLockingVc);
|
||||
const vc = useSelector(vcItemActor, selectVc);
|
||||
|
||||
const isSuccessBio = useSelector(bioService, selectIsSuccess);
|
||||
const vcLabel = useSelector(settingsService, selectVcLabel);
|
||||
const isLockingVc = useSelector(vcItemActor, selectIsLockingVc);
|
||||
const isRevokingVc = useSelector(vcItemActor, selectIsRevokingVc);
|
||||
const isLoggingRevoke = useSelector(vcItemActor, selectIsLoggingRevoke);
|
||||
const vc = useSelector(vcItemActor, selectVc);
|
||||
const otError = useSelector(vcItemActor, selectOtpError);
|
||||
const onSuccess = () => {
|
||||
bioSend({ type: 'SET_IS_AVAILABLE', data: true });
|
||||
setError('');
|
||||
setReAuthenticating('');
|
||||
if (vc.locked) {
|
||||
vcItemActor.send(VcItemEvents.UNLOCK_VC());
|
||||
} else {
|
||||
vcItemActor.send(VcItemEvents.LOCK_VC());
|
||||
}
|
||||
vcItemActor.send(VcItemEvents.LOCK_VC());
|
||||
};
|
||||
|
||||
const onError = (value: string) => {
|
||||
@@ -65,21 +65,48 @@ export function useViewVcModal({ vcItemActor, isVisible }: ViewVcModalProps) {
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const netInfoFetch = (otp: string) => {
|
||||
NetInfo.fetch().then((state) => {
|
||||
if (state.isConnected) {
|
||||
vcItemActor.send(VcItemEvents.INPUT_OTP(otp));
|
||||
} else {
|
||||
vcItemActor.send(VcItemEvents.DISMISS());
|
||||
showToast('Request network failed');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isLockingVc) {
|
||||
showToast(
|
||||
vc.locked ? 'ID successfully locked' : 'ID successfully unlocked'
|
||||
vc.locked
|
||||
? t('success.locked', { vcLabel: vcLabel.singular })
|
||||
: t('success.unlocked', { vcLabel: vcLabel.singular })
|
||||
);
|
||||
}
|
||||
if (isRevokingVc) {
|
||||
showToast(t('success.revoked', { vid: vc.id }));
|
||||
}
|
||||
if (isLoggingRevoke) {
|
||||
vcItemActor.send(VcItemEvents.DISMISS());
|
||||
onRevokeDelete();
|
||||
}
|
||||
if (isSuccessBio && reAuthenticating != '') {
|
||||
onSuccess();
|
||||
}
|
||||
}, [reAuthenticating, isLockingVc, isSuccessBio, otError]);
|
||||
}, [
|
||||
reAuthenticating,
|
||||
isLockingVc,
|
||||
isSuccessBio,
|
||||
otError,
|
||||
isRevokingVc,
|
||||
isLoggingRevoke,
|
||||
vc,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
vcItemActor.send(VcItemEvents.REFRESH());
|
||||
}, [isVisible]);
|
||||
|
||||
return {
|
||||
error,
|
||||
message,
|
||||
@@ -87,48 +114,48 @@ export function useViewVcModal({ vcItemActor, isVisible }: ViewVcModalProps) {
|
||||
vc,
|
||||
otpError: useSelector(vcItemActor, selectOtpError),
|
||||
reAuthenticating,
|
||||
isRevoking,
|
||||
|
||||
isEditingTag: useSelector(vcItemActor, selectIsEditingTag),
|
||||
isLockingVc,
|
||||
isAcceptingOtpInput: useSelector(vcItemActor, selectIsAcceptingOtpInput),
|
||||
isAcceptingRevokeInput: useSelector(
|
||||
vcItemActor,
|
||||
selectIsAcceptingRevokeInput
|
||||
),
|
||||
isRequestingOtp: useSelector(vcItemActor, selectIsRequestingOtp),
|
||||
storedPasscode: useSelector(authService, selectPasscode),
|
||||
|
||||
CONFIRM_REVOKE_VC: () => {
|
||||
setRevoking(true);
|
||||
},
|
||||
REVOKE_VC: () => {
|
||||
vcItemActor.send(VcItemEvents.REVOKE_VC());
|
||||
setRevoking(false);
|
||||
},
|
||||
setReAuthenticating,
|
||||
setRevoking,
|
||||
onError,
|
||||
lockVc: () => {
|
||||
NetInfo.fetch().then((state) => {
|
||||
if (state.isConnected) {
|
||||
if (isAvailable && isBiometricUnlockEnabled) {
|
||||
setReAuthenticating('biometrics');
|
||||
bioSend({ type: 'AUTHENTICATE' });
|
||||
} else {
|
||||
setReAuthenticating('passcode');
|
||||
}
|
||||
} else {
|
||||
showToast('Request network failed');
|
||||
}
|
||||
});
|
||||
vcItemActor.send(VcItemEvents.LOCK_VC());
|
||||
},
|
||||
inputOtp: (otp: string) => {
|
||||
NetInfo.fetch().then((state) => {
|
||||
if (state.isConnected) {
|
||||
vcItemActor.send(VcItemEvents.INPUT_OTP(otp));
|
||||
} else {
|
||||
vcItemActor.send(VcItemEvents.DISMISS());
|
||||
showToast('Request network failed');
|
||||
}
|
||||
});
|
||||
netInfoFetch(otp);
|
||||
},
|
||||
revokeVc: (otp: string) => {
|
||||
netInfoFetch(otp);
|
||||
},
|
||||
onSuccess,
|
||||
EDIT_TAG: () => vcItemActor.send(VcItemEvents.EDIT_TAG()),
|
||||
SAVE_TAG: (tag: string) => vcItemActor.send(VcItemEvents.SAVE_TAG(tag)),
|
||||
DISMISS: () => vcItemActor.send(VcItemEvents.DISMISS()),
|
||||
LOCK_VC: () => vcItemActor.send(VcItemEvents.LOCK_VC()),
|
||||
UNLOCK_VC: () => vcItemActor.send(VcItemEvents.UNLOCK_VC()),
|
||||
INPUT_OTP: (otp: string) => vcItemActor.send(VcItemEvents.INPUT_OTP(otp)),
|
||||
};
|
||||
}
|
||||
|
||||
export interface ViewVcModalProps extends ModalProps {
|
||||
vcItemActor: ActorRefFrom<typeof vcItemMachine>;
|
||||
onDismiss: () => void;
|
||||
onRevokeDelete: () => void;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dimensions, Image, StyleSheet, View } from 'react-native';
|
||||
import {
|
||||
Dimensions,
|
||||
Image,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { Divider, Icon, ListItem, Overlay } from 'react-native-elements';
|
||||
import Markdown from 'react-native-simple-markdown';
|
||||
import { Button, Text, Row } from '../../components/ui';
|
||||
@@ -64,25 +70,27 @@ export const Credits: React.FC<CreditsProps> = (props) => {
|
||||
overlayStyle={{ padding: 24 }}
|
||||
isVisible={isViewing}
|
||||
onBackdropPress={() => setIsViewing(false)}>
|
||||
<View style={styles.view}>
|
||||
<Row align="center" crossAlign="center" margin="0 0 10 0">
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button
|
||||
type="clear"
|
||||
icon={<Icon name="chevron-left" color={Theme.Colors.Icon} />}
|
||||
title=""
|
||||
onPress={() => setIsViewing(false)}
|
||||
/>
|
||||
<SafeAreaView>
|
||||
<View style={styles.view}>
|
||||
<Row align="center" crossAlign="center" margin="0 0 10 0">
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button
|
||||
type="clear"
|
||||
icon={<Icon name="chevron-left" color={Theme.Colors.Icon} />}
|
||||
title=""
|
||||
onPress={() => setIsViewing(false)}
|
||||
/>
|
||||
</View>
|
||||
<Text size="small">{t('header')}</Text>
|
||||
</Row>
|
||||
<Divider />
|
||||
<View style={styles.markdownView}>
|
||||
<Markdown rules={rules} styles={markdownStyles}>
|
||||
{creditsContent}
|
||||
</Markdown>
|
||||
</View>
|
||||
<Text size="small">{t('header')}</Text>
|
||||
</Row>
|
||||
<Divider />
|
||||
<View style={styles.markdownView}>
|
||||
<Markdown rules={rules} styles={markdownStyles}>
|
||||
{creditsContent}
|
||||
</Markdown>
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</Overlay>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
@@ -5,5 +5,11 @@
|
||||
"bioUnlock": "Biometric unlock",
|
||||
"authFactorUnlock": "Unlock auth factor",
|
||||
"credits": "Credits and legal notices",
|
||||
"logout": "Log-out"
|
||||
"logout": "Log-out",
|
||||
"revokeLabel": "Revoke VID",
|
||||
"revokeHeader": "REVOKE VID",
|
||||
"revokingVids": "You are about to revoke ({{count}}) VIDs.",
|
||||
"revokingVidsAfter": "This means you will no longer be able to use or view any of the IDs linked to those VID(s). \nAre you sure you want to proceed?",
|
||||
"empty": "Empty",
|
||||
"revokeSuccessful": "VID successfully revoked"
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { MainRouteProps } from '../../routes/main';
|
||||
import { EditableListItem } from '../../components/EditableListItem';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
import { Credits } from './Credits';
|
||||
import { Revoke } from './Revoke';
|
||||
import { useProfileScreen } from './ProfileScreenController';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LanguageSelector } from '../../components/LanguageSelector';
|
||||
@@ -58,6 +59,7 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
|
||||
onEdit={controller.UPDATE_VC_LABEL}
|
||||
/>
|
||||
<LanguageSetting />
|
||||
<Revoke label={t('revokeLabel')} />
|
||||
<ListItem bottomDivider disabled={!controller.canUseBiometrics}>
|
||||
<ListItem.Content>
|
||||
<ListItem.Title>
|
||||
|
||||
189
screens/Profile/Revoke.tsx
Normal file
189
screens/Profile/Revoke.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
RefreshControl,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { Divider, Icon, ListItem, Overlay } from 'react-native-elements';
|
||||
import { Button, Column, Centered, Row, Text } from '../../components/ui';
|
||||
import { VidItem } from '../../components/VidItem';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { ToastItem } from '../../components/ui/ToastItem';
|
||||
import { OIDcAuthenticationOverlay } from '../../components/OIDcAuthModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRevoke } from './RevokeController';
|
||||
|
||||
export const Revoke: React.FC<RevokeScreenProps> = (props) => {
|
||||
const controller = useRevoke();
|
||||
const { t } = useTranslation('ProfileScreen');
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttonContainer: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 'auto',
|
||||
},
|
||||
view: {
|
||||
flex: 1,
|
||||
width: Dimensions.get('screen').width,
|
||||
},
|
||||
revokeView: { padding: 20 },
|
||||
flexRow: { flexDirection: 'row', margin: 0, padding: 0 },
|
||||
rowStyle: { flexDirection: 'column', justifyContent: 'space-between' },
|
||||
viewContainer: {
|
||||
backgroundColor: 'rgba(0,0,0,.6)',
|
||||
width: Dimensions.get('screen').width,
|
||||
height: Dimensions.get('screen').height,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
zIndex: 999,
|
||||
},
|
||||
boxContainer: {
|
||||
backgroundColor: Colors.White,
|
||||
padding: 24,
|
||||
elevation: 6,
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ListItem bottomDivider onPress={() => controller.setAuthenticating(true)}>
|
||||
<ListItem.Content>
|
||||
<ListItem.Title>
|
||||
<Text>{props.label}</Text>
|
||||
</ListItem.Title>
|
||||
</ListItem.Content>
|
||||
<Overlay
|
||||
overlayStyle={{ padding: 0 }}
|
||||
isVisible={controller.isViewing}
|
||||
onBackdropPress={() => controller.setIsViewing(false)}>
|
||||
<SafeAreaView>
|
||||
{controller.toastVisible && (
|
||||
<ToastItem message={controller.message} />
|
||||
)}
|
||||
<View style={styles.view}>
|
||||
<Row align="center" crossAlign="center" margin="0 0 10 0">
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button
|
||||
type="clear"
|
||||
icon={<Icon name="chevron-left" color={Colors.Orange} />}
|
||||
title=""
|
||||
onPress={() => controller.setIsViewing(false)}
|
||||
/>
|
||||
</View>
|
||||
<Text size="small">{t('revokeHeader')}</Text>
|
||||
</Row>
|
||||
<Divider />
|
||||
<Row style={styles.rowStyle} fill>
|
||||
<View style={styles.revokeView}>
|
||||
{controller.vidKeys.length > 0 && (
|
||||
<Column
|
||||
scroll
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={controller.isRefreshingVcs}
|
||||
onRefresh={controller.REFRESH}
|
||||
/>
|
||||
}>
|
||||
{controller.vidKeys.map((vcKey, index) => (
|
||||
<VidItem
|
||||
key={`${vcKey}-${index}`}
|
||||
vcKey={vcKey}
|
||||
margin="0 2 8 2"
|
||||
onPress={controller.selectVcItem(index, vcKey)}
|
||||
selectable
|
||||
selected={controller.selectedVidKeys.includes(vcKey)}
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
)}
|
||||
{controller.vidKeys.length === 0 && (
|
||||
<React.Fragment>
|
||||
<Centered fill>
|
||||
<Text weight="semibold" margin="0 0 8 0">
|
||||
{t('empty')}
|
||||
</Text>
|
||||
</Centered>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</View>
|
||||
<Column margin="0 20">
|
||||
<Button
|
||||
disabled={controller.selectedVidKeys.length === 0}
|
||||
title={t('revokeHeader')}
|
||||
onPress={controller.CONFIRM_REVOKE_VC}
|
||||
/>
|
||||
</Column>
|
||||
</Row>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
{controller.isRevoking && (
|
||||
<View style={styles.viewContainer}>
|
||||
<Centered fill>
|
||||
<Column
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
style={styles.boxContainer}>
|
||||
<Text weight="semibold" margin="0 0 12 0">
|
||||
{t('revokeLabel')}
|
||||
</Text>
|
||||
<Text margin="0 0 12 0">
|
||||
{t('revokingVids', {
|
||||
count: controller.selectedVidKeys.length,
|
||||
})}
|
||||
</Text>
|
||||
{controller.selectedVidKeys.map((vcKey, index) => (
|
||||
<View style={styles.flexRow} key={index}>
|
||||
<Text margin="0 8" weight="bold">
|
||||
{'\u2022'}
|
||||
</Text>
|
||||
<Text margin="0 0 0 0" weight="bold">
|
||||
{vcKey.split(':')[2]}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
<Text margin="12 0">{t('revokingVidsAfter')}</Text>
|
||||
<Row>
|
||||
<Button
|
||||
fill
|
||||
type="clear"
|
||||
title={t('cancel')}
|
||||
onPress={() => controller.setRevoking(false)}
|
||||
/>
|
||||
<Button
|
||||
fill
|
||||
title={t('revokeLabel')}
|
||||
onPress={controller.REVOKE_VC}
|
||||
/>
|
||||
</Row>
|
||||
</Column>
|
||||
</Centered>
|
||||
</View>
|
||||
)}
|
||||
</Overlay>
|
||||
|
||||
<OIDcAuthenticationOverlay
|
||||
isVisible={controller.isAuthenticating}
|
||||
onDismiss={() => controller.setAuthenticating(false)}
|
||||
onVerify={() => {
|
||||
controller.setAuthenticating(false);
|
||||
controller.setIsViewing(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
<OIDcAuthenticationOverlay
|
||||
isVisible={controller.isAcceptingOtpInput}
|
||||
onDismiss={controller.DISMISS}
|
||||
onVerify={() => {
|
||||
controller.setIsViewing(true);
|
||||
controller.revokeVc('111111');
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
interface RevokeScreenProps {
|
||||
label: string;
|
||||
}
|
||||
128
screens/Profile/RevokeController.tsx
Normal file
128
screens/Profile/RevokeController.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { useSelector } from '@xstate/react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
import { GlobalContext } from '../../shared/GlobalContext';
|
||||
import {
|
||||
selectIsRefreshingMyVcs,
|
||||
selectMyVcs,
|
||||
VcEvents,
|
||||
} from '../../machines/vc';
|
||||
import { vcItemMachine } from '../../machines/vcItem';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
RevokeVidsEvents,
|
||||
selectIsAcceptingOtpInput,
|
||||
selectIsRevokingVc,
|
||||
selectIsLoggingRevoke,
|
||||
} from '../../machines/revoke';
|
||||
|
||||
import { ActorRefFrom } from 'xstate';
|
||||
|
||||
export function useRevoke() {
|
||||
const { t } = useTranslation('ProfileScreen');
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const vcService = appService.children.get('vc');
|
||||
const revokeService = appService.children.get('RevokeVids');
|
||||
const vcKeys = useSelector(vcService, selectMyVcs);
|
||||
const isRevokingVc = useSelector(revokeService, selectIsRevokingVc);
|
||||
const isLoggingRevoke = useSelector(revokeService, selectIsLoggingRevoke);
|
||||
const isAcceptingOtpInput = useSelector(
|
||||
revokeService,
|
||||
selectIsAcceptingOtpInput
|
||||
);
|
||||
|
||||
const [isRevoking, setRevoking] = useState(false);
|
||||
const [isAuthenticating, setAuthenticating] = useState(false);
|
||||
const [isViewing, setIsViewing] = useState(false);
|
||||
const [toastVisible, setToastVisible] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(null);
|
||||
const [selectedVidKeys, setSelectedVidKeys] = useState<string[]>([]);
|
||||
|
||||
const vidKeys = vcKeys.filter((vc) => {
|
||||
const vcKey = vc.split(':');
|
||||
return vcKey[1] === 'VID';
|
||||
});
|
||||
|
||||
const selectVcItem = (index: number, vcKey: string) => {
|
||||
return () => {
|
||||
setSelectedIndex(index);
|
||||
if (selectedVidKeys.includes(vcKey)) {
|
||||
setSelectedVidKeys(selectedVidKeys.filter((item) => item !== vcKey));
|
||||
} else {
|
||||
setSelectedVidKeys((prevArray) => [...prevArray, vcKey]);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const showToast = (message: string) => {
|
||||
setToastVisible(true);
|
||||
setMessage(message);
|
||||
setTimeout(() => {
|
||||
setToastVisible(false);
|
||||
setMessage('');
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isRevokingVc) {
|
||||
setSelectedVidKeys([]);
|
||||
showToast(t('revokeSuccessful'));
|
||||
}
|
||||
if (isLoggingRevoke) {
|
||||
revokeService.send(RevokeVidsEvents.DISMISS());
|
||||
vcService.send(VcEvents.REFRESH_MY_VCS());
|
||||
}
|
||||
}, [isRevokingVc, isLoggingRevoke]);
|
||||
|
||||
return {
|
||||
error: '',
|
||||
isAcceptingOtpInput,
|
||||
isAuthenticating,
|
||||
isRefreshingVcs: useSelector(vcService, selectIsRefreshingMyVcs),
|
||||
isRevoking,
|
||||
isViewing,
|
||||
message,
|
||||
selectedIndex,
|
||||
selectedVidKeys,
|
||||
toastVisible,
|
||||
vidKeys: vidKeys.filter(
|
||||
(vcKey, index, vid) => vid.indexOf(vcKey) === index
|
||||
),
|
||||
|
||||
CONFIRM_REVOKE_VC: () => {
|
||||
setRevoking(true);
|
||||
},
|
||||
DISMISS: () => {
|
||||
revokeService.send(RevokeVidsEvents.DISMISS());
|
||||
},
|
||||
INPUT_OTP: (otp: string) =>
|
||||
revokeService.send(RevokeVidsEvents.INPUT_OTP(otp)),
|
||||
REFRESH: () => vcService.send(VcEvents.REFRESH_MY_VCS()),
|
||||
REVOKE_VC: () => {
|
||||
revokeService.send(RevokeVidsEvents.REVOKE_VCS(selectedVidKeys));
|
||||
setRevoking(false);
|
||||
//since nested modals/overlays don't work in ios, we need to toggle revoke screen
|
||||
setIsViewing(false);
|
||||
},
|
||||
revokeVc: (otp: string) => {
|
||||
NetInfo.fetch().then((state) => {
|
||||
if (state.isConnected) {
|
||||
revokeService.send(RevokeVidsEvents.INPUT_OTP(otp));
|
||||
} else {
|
||||
revokeService.send(RevokeVidsEvents.DISMISS());
|
||||
showToast('Request network failed');
|
||||
}
|
||||
});
|
||||
},
|
||||
setAuthenticating,
|
||||
selectVcItem,
|
||||
setIsViewing,
|
||||
setRevoking,
|
||||
};
|
||||
}
|
||||
|
||||
export interface RevokeProps {
|
||||
service: ActorRefFrom<typeof vcItemMachine>;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { RequestScreen } from './RequestScreen';
|
||||
import { useRequestLayout } from './RequestLayoutController';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
import { Message } from '../../components/Message';
|
||||
import { ReceiveVcScreen } from './ReceiveVcScreen';
|
||||
import { LanguageSelector } from '../../components/LanguageSelector';
|
||||
import { Theme } from '../../components/ui/styleUtils';
|
||||
@@ -50,32 +50,35 @@ export const RequestLayout: React.FC = () => {
|
||||
/>
|
||||
</RequestStack.Navigator>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isAccepted}
|
||||
title={t('status.accepted.title')}
|
||||
message={t('status.accepted.message', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
sender: controller.senderInfo.deviceName,
|
||||
})}
|
||||
onBackdropPress={controller.DISMISS}
|
||||
/>
|
||||
{controller.isAccepted && (
|
||||
<Message
|
||||
title={t('status.accepted.title')}
|
||||
message={t('status.accepted.message', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
sender: controller.senderInfo.deviceName,
|
||||
})}
|
||||
onBackdropPress={controller.DISMISS}
|
||||
/>
|
||||
)}
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isRejected}
|
||||
title={t('status.rejected.title')}
|
||||
message={t('status.rejected.message', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
sender: controller.senderInfo.deviceName,
|
||||
})}
|
||||
onBackdropPress={controller.DISMISS}
|
||||
/>
|
||||
{controller.isRejected && (
|
||||
<Message
|
||||
title={t('status.rejected.title')}
|
||||
message={t('status.rejected.message', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
sender: controller.senderInfo.deviceName,
|
||||
})}
|
||||
onBackdropPress={controller.DISMISS}
|
||||
/>
|
||||
)}
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isDisconnected}
|
||||
title={t('status.disconnected.title')}
|
||||
message={t('status.disconnected.message')}
|
||||
onBackdropPress={controller.DISMISS}
|
||||
/>
|
||||
{controller.isDisconnected && (
|
||||
<Message
|
||||
title={t('status.disconnected.title')}
|
||||
message={t('status.disconnected.message')}
|
||||
onBackdropPress={controller.DISMISS}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,8 +17,14 @@
|
||||
"message": "The connection was interrupted. Please try again."
|
||||
},
|
||||
"waitingConnection": "Waiting for connection...",
|
||||
"exchangingDeviceInfo": "Exchanging device info...",
|
||||
"connected": "Connected to device. Waiting for {{vcLabel}}..."
|
||||
"exchangingDeviceInfo": {
|
||||
"message": "Exchanging device info...",
|
||||
"timeoutHint": "It's taking too long to exchange device info..."
|
||||
},
|
||||
"connected": {
|
||||
"message": "Connected to device. Waiting for {{vcLabel}}...",
|
||||
"timeoutHint": "No VC data received yet. Is sending device still connected?"
|
||||
}
|
||||
},
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
|
||||
@@ -3,7 +3,7 @@ import QRCode from 'react-native-qrcode-svg';
|
||||
import { Centered, Button, Row, Column, Text } from '../../components/ui';
|
||||
import { Theme } from '../../components/ui/styleUtils';
|
||||
import { useRequestScreen } from './RequestScreenController';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TFunction, useTranslation } from 'react-i18next';
|
||||
import { Switch } from 'react-native-elements';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
@@ -16,24 +16,66 @@ export const RequestScreen: React.FC = () => {
|
||||
fill
|
||||
padding="24"
|
||||
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
|
||||
<Column>
|
||||
{controller.isBluetoothDenied ? (
|
||||
<React.Fragment>
|
||||
<Text color={Theme.Colors.errorMessage} align="center">
|
||||
{t('bluetoothDenied', { vcLabel: controller.vcLabel.singular })}
|
||||
</Text>
|
||||
<Button
|
||||
margin={[32, 0, 0, 0]}
|
||||
title={t('gotoSettings')}
|
||||
onPress={controller.GOTO_SETTINGS}
|
||||
/>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Text align="center">
|
||||
{t('showQrCode', { vcLabel: controller.vcLabel.singular })}
|
||||
{controller.isBluetoothDenied && (
|
||||
<BluetoothPrompt t={t} controller={controller} />
|
||||
)}
|
||||
|
||||
{!controller.isCheckingBluetoothService &&
|
||||
!controller.isBluetoothDenied ? (
|
||||
<Column align="flex-end" fill>
|
||||
{controller.isWaitingForConnection && (
|
||||
<SharingCode t={t} controller={controller} />
|
||||
)}
|
||||
<StatusMessage t={t} controller={controller} />
|
||||
</Column>
|
||||
) : null}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
const BluetoothPrompt: React.FC<RequestScreenProps> = ({ t, controller }) => {
|
||||
return (
|
||||
<Centered fill>
|
||||
<Text color={Theme.Colors.errorMessage} align="center">
|
||||
{t('bluetoothDenied', { vcLabel: controller.vcLabel.singular })}
|
||||
</Text>
|
||||
<Button
|
||||
margin={[32, 0, 0, 0]}
|
||||
title={t('gotoSettings')}
|
||||
onPress={controller.GOTO_SETTINGS}
|
||||
/>
|
||||
</Centered>
|
||||
);
|
||||
};
|
||||
|
||||
const StatusMessage: React.FC<RequestScreenProps> = ({ t, controller }) => {
|
||||
return (
|
||||
controller.statusMessage !== '' && (
|
||||
<Column elevation={1} padding="16 24">
|
||||
<Text>{controller.statusMessage}</Text>
|
||||
{controller.statusHint !== '' && (
|
||||
<Text size="small" color={Theme.Colors.textLabel}>
|
||||
{controller.statusHint}
|
||||
</Text>
|
||||
)}
|
||||
{controller.isStatusCancellable && (
|
||||
<Button
|
||||
margin={[8, 0, 0, 0]}
|
||||
title={t('cancel', { ns: 'common' })}
|
||||
onPress={controller.CANCEL}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const SharingCode: React.FC<RequestScreenProps> = ({ t, controller }) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Text align="center">
|
||||
{t('showQrCode', { vcLabel: controller.vcLabel.singular })}
|
||||
</Text>
|
||||
|
||||
<Centered fill>
|
||||
{controller.connectionParams !== '' ? (
|
||||
@@ -54,12 +96,11 @@ export const RequestScreen: React.FC = () => {
|
||||
/>
|
||||
<Text margin={[0, 0, 0, 16]}>Online</Text>
|
||||
</Row>
|
||||
|
||||
{controller.statusMessage !== '' && (
|
||||
<Column elevation={1} padding="16 24">
|
||||
<Text>{controller.statusMessage}</Text>
|
||||
</Column>
|
||||
)}
|
||||
</Column>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
interface RequestScreenProps {
|
||||
t: TFunction;
|
||||
controller: ReturnType<typeof useRequestScreen>;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import {
|
||||
selectIsExchangingDeviceInfo,
|
||||
selectIsWaitingForVc,
|
||||
selectSharingProtocol,
|
||||
selectIsExchangingDeviceInfoTimeout,
|
||||
selectIsWaitingForVcTimeout,
|
||||
selectIsCheckingBluetoothService,
|
||||
} from '../../machines/request';
|
||||
import { selectVcLabel } from '../../machines/settings';
|
||||
import { GlobalContext } from '../../shared/GlobalContext';
|
||||
@@ -38,15 +41,37 @@ export function useRequestScreen() {
|
||||
requestService,
|
||||
selectIsExchangingDeviceInfo
|
||||
);
|
||||
const isExchangingDeviceInfoTimeout = useSelector(
|
||||
requestService,
|
||||
selectIsExchangingDeviceInfoTimeout
|
||||
);
|
||||
const isWaitingForVc = useSelector(requestService, selectIsWaitingForVc);
|
||||
const isWaitingForVcTimeout = useSelector(
|
||||
requestService,
|
||||
selectIsWaitingForVcTimeout
|
||||
);
|
||||
|
||||
let statusMessage = '';
|
||||
let statusHint = '';
|
||||
let isStatusCancellable = false;
|
||||
if (isWaitingForConnection) {
|
||||
statusMessage = t('status.waitingConnection');
|
||||
} else if (isExchangingDeviceInfo) {
|
||||
statusMessage = t('status.exchangingDeviceInfo');
|
||||
statusMessage = t('status.exchangingDeviceInfo.message');
|
||||
} else if (isExchangingDeviceInfoTimeout) {
|
||||
statusMessage = t('status.exchangingDeviceInfo.message');
|
||||
statusHint = t('status.exchangingDeviceInfo.timeoutHint');
|
||||
isStatusCancellable = true;
|
||||
} else if (isWaitingForVc) {
|
||||
statusMessage = t('status.connected', { vcLabel: vcLabel.singular });
|
||||
statusMessage = t('status.connected.message', {
|
||||
vcLabel: vcLabel.singular,
|
||||
});
|
||||
} else if (isWaitingForVcTimeout) {
|
||||
statusMessage = t('status.connected.message', {
|
||||
vcLabel: vcLabel.singular,
|
||||
});
|
||||
statusHint = t('status.connected.timeoutHint');
|
||||
isStatusCancellable = true;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -60,17 +85,24 @@ export function useRequestScreen() {
|
||||
return {
|
||||
vcLabel,
|
||||
statusMessage,
|
||||
statusHint,
|
||||
sharingProtocol: useSelector(requestService, selectSharingProtocol),
|
||||
|
||||
isWaitingForConnection,
|
||||
isExchangingDeviceInfo,
|
||||
|
||||
isStatusCancellable,
|
||||
isWaitingForVc,
|
||||
isBluetoothDenied,
|
||||
isCheckingBluetoothService: useSelector(
|
||||
requestService,
|
||||
selectIsCheckingBluetoothService
|
||||
),
|
||||
connectionParams: useSelector(requestService, selectConnectionParams),
|
||||
senderInfo: useSelector(requestService, selectSenderInfo),
|
||||
isReviewing: useSelector(requestService, selectIsReviewing),
|
||||
|
||||
CANCEL: () => requestService.send(RequestEvents.CANCEL()),
|
||||
DISMISS: () => requestService.send(RequestEvents.DISMISS()),
|
||||
ACCEPT: () => requestService.send(RequestEvents.ACCEPT()),
|
||||
REJECT: () => requestService.send(RequestEvents.REJECT()),
|
||||
|
||||
61
screens/Scan/ScanLayout.tsx
Normal file
61
screens/Scan/ScanLayout.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { Icon } from 'react-native-elements';
|
||||
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { SendVcScreen } from './SendVcScreen';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
import { useScanLayout } from './ScanLayoutController';
|
||||
import { LanguageSelector } from '../../components/LanguageSelector';
|
||||
import { ScanScreen } from './ScanScreen';
|
||||
|
||||
const ScanStack = createNativeStackNavigator();
|
||||
|
||||
export const ScanLayout: React.FC = () => {
|
||||
const { t } = useTranslation('ScanScreen');
|
||||
const controller = useScanLayout();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ScanStack.Navigator
|
||||
initialRouteName="ScanScreen"
|
||||
screenOptions={{
|
||||
headerTitleAlign: 'center',
|
||||
headerRight: () => (
|
||||
<LanguageSelector
|
||||
triggerComponent={<Icon name="language" color={Colors.Orange} />}
|
||||
/>
|
||||
),
|
||||
}}>
|
||||
{!controller.isDone && (
|
||||
<ScanStack.Screen
|
||||
name="SendVcScreen"
|
||||
component={SendVcScreen}
|
||||
options={{
|
||||
title: t('sharingVc', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ScanStack.Screen
|
||||
name="ScanScreen"
|
||||
component={ScanScreen}
|
||||
options={{
|
||||
title: t('scan').toUpperCase(),
|
||||
}}
|
||||
/>
|
||||
</ScanStack.Navigator>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.statusOverlay != null}
|
||||
message={controller.statusOverlay?.message}
|
||||
hint={controller.statusOverlay?.hint}
|
||||
onCancel={controller.statusOverlay?.onCancel}
|
||||
progress={!controller.isInvalid}
|
||||
onBackdropPress={controller.DISMISS_INVALID}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
136
screens/Scan/ScanLayoutController.ts
Normal file
136
screens/Scan/ScanLayoutController.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { NavigationProp, useNavigation } from '@react-navigation/native';
|
||||
import { useSelector } from '@xstate/react';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MessageOverlayProps } from '../../components/MessageOverlay';
|
||||
import {
|
||||
ScanEvents,
|
||||
selectIsInvalid,
|
||||
selectIsLocationDisabled,
|
||||
selectIsLocationDenied,
|
||||
selectIsConnecting,
|
||||
selectIsExchangingDeviceInfo,
|
||||
selectIsConnectingTimeout,
|
||||
selectIsExchangingDeviceInfoTimeout,
|
||||
selectIsDone,
|
||||
selectIsReviewing,
|
||||
selectIsScanning,
|
||||
} from '../../machines/scan';
|
||||
import { selectVcLabel } from '../../machines/settings';
|
||||
import { MainBottomTabParamList } from '../../routes/main';
|
||||
import { GlobalContext } from '../../shared/GlobalContext';
|
||||
|
||||
type ScanStackParamList = {
|
||||
ScanScreen: undefined;
|
||||
SendVcScreen: undefined;
|
||||
};
|
||||
|
||||
type ScanLayoutNavigation = NavigationProp<
|
||||
ScanStackParamList & MainBottomTabParamList
|
||||
>;
|
||||
|
||||
export function useScanLayout() {
|
||||
const { t } = useTranslation('ScanScreen');
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const scanService = appService.children.get('scan');
|
||||
const settingsService = appService.children.get('settings');
|
||||
const navigation = useNavigation<ScanLayoutNavigation>();
|
||||
|
||||
const isLocationDisabled = useSelector(scanService, selectIsLocationDisabled);
|
||||
const isLocationDenied = useSelector(scanService, selectIsLocationDenied);
|
||||
|
||||
const locationError = { message: '', button: '' };
|
||||
|
||||
if (isLocationDisabled) {
|
||||
locationError.message = t('errors.locationDisabled.message');
|
||||
locationError.button = t('errors.locationDisabled.button');
|
||||
} else if (isLocationDenied) {
|
||||
locationError.message = t('errors.locationDenied.message');
|
||||
locationError.button = t('errors.locationDenied.button');
|
||||
}
|
||||
|
||||
const isInvalid = useSelector(scanService, selectIsInvalid);
|
||||
const isConnecting = useSelector(scanService, selectIsConnecting);
|
||||
const isConnectingTimeout = useSelector(
|
||||
scanService,
|
||||
selectIsConnectingTimeout
|
||||
);
|
||||
const isExchangingDeviceInfo = useSelector(
|
||||
scanService,
|
||||
selectIsExchangingDeviceInfo
|
||||
);
|
||||
const isExchangingDeviceInfoTimeout = useSelector(
|
||||
scanService,
|
||||
selectIsExchangingDeviceInfoTimeout
|
||||
);
|
||||
|
||||
const onCancel = () => scanService.send(ScanEvents.CANCEL());
|
||||
let statusOverlay: Pick<
|
||||
MessageOverlayProps,
|
||||
'message' | 'hint' | 'onCancel'
|
||||
> = null;
|
||||
if (isConnecting) {
|
||||
statusOverlay = {
|
||||
message: t('status.connecting'),
|
||||
};
|
||||
} else if (isConnectingTimeout) {
|
||||
statusOverlay = {
|
||||
message: t('status.connecting'),
|
||||
hint: t('status.connectingTimeout'),
|
||||
onCancel,
|
||||
};
|
||||
} else if (isExchangingDeviceInfo) {
|
||||
statusOverlay = {
|
||||
message: t('status.exchangingDeviceInfo'),
|
||||
};
|
||||
} else if (isExchangingDeviceInfoTimeout) {
|
||||
statusOverlay = {
|
||||
message: t('status.exchangingDeviceInfo'),
|
||||
hint: t('status.exchangingDeviceInfoTimeout'),
|
||||
onCancel,
|
||||
};
|
||||
} else if (isInvalid) {
|
||||
statusOverlay = {
|
||||
message: t('status.invalid'),
|
||||
};
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const subscriptions = [
|
||||
navigation.addListener('focus', () =>
|
||||
scanService.send(ScanEvents.SCREEN_FOCUS())
|
||||
),
|
||||
navigation.addListener('blur', () =>
|
||||
scanService.send(ScanEvents.SCREEN_BLUR())
|
||||
),
|
||||
];
|
||||
|
||||
return () => {
|
||||
subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isDone = useSelector(scanService, selectIsDone);
|
||||
const isReviewing = useSelector(scanService, selectIsReviewing);
|
||||
const isScanning = useSelector(scanService, selectIsScanning);
|
||||
useEffect(() => {
|
||||
if (isDone) {
|
||||
navigation.navigate('Home', { activeTab: 0 });
|
||||
} else if (isReviewing) {
|
||||
navigation.navigate('SendVcScreen');
|
||||
} else if (isScanning) {
|
||||
navigation.navigate('ScanScreen');
|
||||
}
|
||||
}, [isDone, isReviewing]);
|
||||
|
||||
return {
|
||||
vcLabel: useSelector(settingsService, selectVcLabel),
|
||||
|
||||
isInvalid,
|
||||
isDone,
|
||||
statusOverlay,
|
||||
|
||||
DISMISS_INVALID: () =>
|
||||
isInvalid ? scanService.send(ScanEvents.DISMISS()) : null,
|
||||
};
|
||||
}
|
||||
@@ -3,10 +3,6 @@
|
||||
"noShareableVcs": "No shareable {{vcLabel}} are available.",
|
||||
"sharingVc": "Sharing {{vcLabel}}",
|
||||
"errors": {
|
||||
"flightMode": {
|
||||
"message": "Flight mode must be disabled for the scanning functionality",
|
||||
"button": "Disable flight mode"
|
||||
},
|
||||
"locationDisabled": {
|
||||
"message": "Location services must be enabled for the scanning functionality",
|
||||
"button": "Enable location services"
|
||||
@@ -18,7 +14,9 @@
|
||||
},
|
||||
"status": {
|
||||
"connecting": "Connecting...",
|
||||
"connectingTimeout": "It's taking a while to establish connection. Is the other device open for connections?",
|
||||
"exchangingDeviceInfo": "Exchanging device info...",
|
||||
"exchangingDeviceInfoTimeout": "It's taking a while to exchange device info. You may have to reconnect.",
|
||||
"invalid": "Invalid QR Code"
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,52 @@
|
||||
import React from 'react';
|
||||
import { QrScanner } from '../../components/QrScanner';
|
||||
import { Button, Column, Text } from '../../components/ui';
|
||||
import { Theme } from '../../components/ui/styleUtils';
|
||||
import { MainRouteProps } from '../../routes/main';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
import { useScanScreen } from './ScanScreenController';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SendVcModal } from './SendVcModal';
|
||||
import { QrScanner } from '../../components/QrScanner';
|
||||
import { Button, Centered, Column, Text } from '../../components/ui';
|
||||
import { Theme } from '../../components/ui/styleUtils';
|
||||
import { useScanScreen } from './ScanScreenController';
|
||||
|
||||
export const ScanScreen: React.FC<MainRouteProps> = (props) => {
|
||||
export const ScanScreen: React.FC = () => {
|
||||
const { t } = useTranslation('ScanScreen');
|
||||
const controller = useScanScreen(props);
|
||||
const controller = useScanScreen();
|
||||
|
||||
return (
|
||||
<Column
|
||||
fill
|
||||
padding="98 24 24 24"
|
||||
padding="24 0"
|
||||
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
|
||||
<Text align="center">{t('header')}</Text>
|
||||
<Centered
|
||||
fill
|
||||
align="space-evenly"
|
||||
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
|
||||
<Text align="center">{t('header')}</Text>
|
||||
|
||||
{controller.isLocationDisabled ||
|
||||
controller.isLocationDenied ||
|
||||
controller.isFlightMode ? (
|
||||
<Column fill align="space-between">
|
||||
<Text align="center" margin="16 0" color={Theme.Colors.errorMessage}>
|
||||
{controller.locationError.message}
|
||||
</Text>
|
||||
<Button
|
||||
title={controller.locationError.button}
|
||||
onPress={controller.ON_REQUEST}
|
||||
/>
|
||||
</Column>
|
||||
) : null}
|
||||
|
||||
{!controller.isEmpty ? (
|
||||
controller.isScanning && (
|
||||
<Column fill padding="16 0" crossAlign="center">
|
||||
<QrScanner onQrFound={controller.SCAN} />
|
||||
{controller.isLocationDisabled || controller.isLocationDenied ? (
|
||||
<Column align="space-between">
|
||||
<Text
|
||||
align="center"
|
||||
margin="16 0"
|
||||
color={Theme.Colors.errorMessage}>
|
||||
{controller.locationError.message}
|
||||
</Text>
|
||||
<Button
|
||||
title={controller.locationError.button}
|
||||
onPress={controller.LOCATION_REQUEST}
|
||||
/>
|
||||
</Column>
|
||||
)
|
||||
) : (
|
||||
<Text align="center" margin="16 0" color={Theme.Colors.errorMessage}>
|
||||
{t('noShareableVcs', { vcLabel: controller.vcLabel.plural })}
|
||||
</Text>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.statusMessage !== ''}
|
||||
message={controller.statusMessage}
|
||||
hasProgress={!controller.isInvalid}
|
||||
onBackdropPress={controller.DISMISS_INVALID}
|
||||
/>
|
||||
|
||||
<SendVcModal
|
||||
isVisible={controller.isReviewing}
|
||||
onDismiss={controller.DISMISS}
|
||||
headerElevation={2}
|
||||
headerTitle={t('sharingVc', { vcLabel: controller.vcLabel.singular })}
|
||||
/>
|
||||
{!controller.isEmpty ? (
|
||||
controller.isScanning && (
|
||||
<Column crossAlign="center">
|
||||
<QrScanner onQrFound={controller.SCAN} />
|
||||
</Column>
|
||||
)
|
||||
) : (
|
||||
<Text align="center" color={Theme.Colors.errorMessage}>
|
||||
{t('noShareableVcs', { vcLabel: controller.vcLabel.plural })}
|
||||
</Text>
|
||||
)}
|
||||
</Centered>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
import { useSelector } from '@xstate/react';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
ScanEvents,
|
||||
selectIsInvalid,
|
||||
selectIsAirplaneEnabled,
|
||||
selectIsLocationDisabled,
|
||||
selectIsLocationDenied,
|
||||
selectIsReviewing,
|
||||
selectIsScanning,
|
||||
selectIsConnecting,
|
||||
selectIsExchangingDeviceInfo,
|
||||
} from '../../machines/scan';
|
||||
import { selectVcLabel } from '../../machines/settings';
|
||||
import { selectShareableVcs } from '../../machines/vc';
|
||||
import { MainRouteProps } from '../../routes/main';
|
||||
import { GlobalContext } from '../../shared/GlobalContext';
|
||||
|
||||
export function useScanScreen({ navigation }: MainRouteProps) {
|
||||
export function useScanScreen() {
|
||||
const { t } = useTranslation('ScanScreen');
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const scanService = appService.children.get('scan');
|
||||
@@ -28,13 +23,10 @@ export function useScanScreen({ navigation }: MainRouteProps) {
|
||||
|
||||
const isLocationDisabled = useSelector(scanService, selectIsLocationDisabled);
|
||||
const isLocationDenied = useSelector(scanService, selectIsLocationDenied);
|
||||
const isFlightMode = useSelector(scanService, selectIsAirplaneEnabled);
|
||||
|
||||
const locationError = { message: '', button: '' };
|
||||
if (isFlightMode) {
|
||||
locationError.message = t('errors.flightMode.message');
|
||||
locationError.button = t('errors.flightMode.button');
|
||||
} else if (isLocationDisabled) {
|
||||
|
||||
if (isLocationDisabled) {
|
||||
locationError.message = t('errors.locationDisabled.message');
|
||||
locationError.button = t('errors.locationDisabled.button');
|
||||
} else if (isLocationDenied) {
|
||||
@@ -42,64 +34,16 @@ export function useScanScreen({ navigation }: MainRouteProps) {
|
||||
locationError.button = t('errors.locationDenied.button');
|
||||
}
|
||||
|
||||
const isInvalid = useSelector(scanService, selectIsInvalid);
|
||||
const isConnecting = useSelector(scanService, selectIsConnecting);
|
||||
const isExchangingDeviceInfo = useSelector(
|
||||
scanService,
|
||||
selectIsExchangingDeviceInfo
|
||||
);
|
||||
|
||||
let statusMessage = '';
|
||||
if (isConnecting) {
|
||||
statusMessage = t('status.connecting');
|
||||
} else if (isExchangingDeviceInfo) {
|
||||
statusMessage = t('status.exchangingDeviceInfo');
|
||||
} else if (isInvalid) {
|
||||
statusMessage = t('status.invalid');
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const subscriptions = [
|
||||
navigation.addListener('focus', () =>
|
||||
scanService.send(ScanEvents.SCREEN_FOCUS())
|
||||
),
|
||||
navigation.addListener('blur', () =>
|
||||
scanService.send(ScanEvents.SCREEN_BLUR())
|
||||
),
|
||||
];
|
||||
|
||||
const navSubscription = scanService.subscribe((state) => {
|
||||
if (state.matches('reviewing.navigatingToHome')) {
|
||||
navigation.navigate('Home', { activeTab: 0 });
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
navSubscription.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
locationError,
|
||||
vcLabel: useSelector(settingsService, selectVcLabel),
|
||||
|
||||
isInvalid,
|
||||
isEmpty: !shareableVcs.length,
|
||||
isLocationDisabled,
|
||||
isLocationDenied,
|
||||
isScanning: useSelector(scanService, selectIsScanning),
|
||||
isReviewing: useSelector(scanService, selectIsReviewing),
|
||||
isFlightMode,
|
||||
statusMessage,
|
||||
|
||||
DISMISS: () => scanService.send(ScanEvents.DISMISS()),
|
||||
ON_REQUEST: () =>
|
||||
isFlightMode
|
||||
? scanService.send(ScanEvents.FLIGHT_REQUEST())
|
||||
: scanService.send(ScanEvents.LOCATION_REQUEST()),
|
||||
LOCATION_REQUEST: () => scanService.send(ScanEvents.LOCATION_REQUEST()),
|
||||
SCAN: (qrCode: string) => scanService.send(ScanEvents.SCAN(qrCode)),
|
||||
DISMISS_INVALID: () =>
|
||||
isInvalid ? scanService.send(ScanEvents.DISMISS()) : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"header": "Share {{vcLabel}}",
|
||||
"chooseVc": "Choose the {{vcLabel}} you'd like to share with",
|
||||
"cancel": "Cancel",
|
||||
"share": "Share"
|
||||
"share": "Share",
|
||||
"verifyAndShare": "Verify Identity & Share"
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dimensions, StyleSheet } from 'react-native';
|
||||
import { Overlay } from 'react-native-elements/dist/overlay/Overlay';
|
||||
import { Button, Column, Row, Text } from '../../components/ui';
|
||||
import { Button, Column, Text } from '../../components/ui';
|
||||
import { Theme } from '../../components/ui/styleUtils';
|
||||
import { VcItem } from '../../components/VcItem';
|
||||
import {
|
||||
@@ -47,21 +47,25 @@ export const SelectVcOverlay: React.FC<SelectVcOverlayProps> = (props) => {
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
<Row margin="16 0 0 0">
|
||||
<Button
|
||||
fill
|
||||
type="clear"
|
||||
title={t('cancel')}
|
||||
onPress={() => props.onCancel()}
|
||||
margin="0 8 0 0"
|
||||
/>
|
||||
<Button
|
||||
fill
|
||||
title={t('share')}
|
||||
disabled={controller.selectedIndex == null}
|
||||
onPress={controller.onSelect}
|
||||
/>
|
||||
</Row>
|
||||
<Button
|
||||
title={t('share')}
|
||||
disabled={controller.selectedIndex == null}
|
||||
onPress={controller.onSelect}
|
||||
margin="8 0 0 0"
|
||||
/>
|
||||
<Button
|
||||
type="outline"
|
||||
title={t('verifyAndShare')}
|
||||
disabled={controller.selectedIndex == null}
|
||||
onPress={controller.onVerifyAndSelect}
|
||||
margin="8 0 0 0"
|
||||
/>
|
||||
<Button
|
||||
type="clear"
|
||||
title={t('common:cancel')}
|
||||
onPress={props.onCancel}
|
||||
margin="8 0 0 0"
|
||||
/>
|
||||
</Column>
|
||||
</Overlay>
|
||||
);
|
||||
|
||||
@@ -23,6 +23,11 @@ export function useSelectVcOverlay(props: SelectVcOverlayProps) {
|
||||
const { serviceRefs, ...vc } = selectedVcRef.getSnapshot().context;
|
||||
props.onSelect(vc);
|
||||
},
|
||||
|
||||
onVerifyAndSelect: () => {
|
||||
const { serviceRefs, ...vc } = selectedVcRef.getSnapshot().context;
|
||||
props.onVerifyAndSelect(vc);
|
||||
},
|
||||
};
|
||||
|
||||
function selectVcItem(index: number) {
|
||||
@@ -38,5 +43,6 @@ export interface SelectVcOverlayProps {
|
||||
receiverName: string;
|
||||
vcKeys: string[];
|
||||
onSelect: (vc: VC) => void;
|
||||
onVerifyAndSelect: (vc: VC) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"reasonForSharing": "Reason for sharing (optional)",
|
||||
"acceptRequest": "Accept request and choose {{vcLabel}}",
|
||||
"reject": "Reject",
|
||||
"statusSharing": {
|
||||
"title": "Sharing..."
|
||||
},
|
||||
"statusAccepted": {
|
||||
"title": "Success!",
|
||||
"message": "Your {{vcLabel}} has been successfully shared with {{receiver}}"
|
||||
},
|
||||
"statusRejected": {
|
||||
"title": "Notice",
|
||||
"message": "Your {{vcLabel}} was rejected by {{receiver}}"
|
||||
}
|
||||
}
|
||||
26
screens/Scan/SendVcScreen.strings.json
Normal file
26
screens/Scan/SendVcScreen.strings.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"reasonForSharing": "Reason for sharing (optional)",
|
||||
"acceptRequest": "Accept request and choose {{vcLabel}}",
|
||||
"reject": "Reject",
|
||||
"status": {
|
||||
"sharing": {
|
||||
"title": "Sharing...",
|
||||
"timeoutHint": "It's taking a while to share VC. There could be a problem with the connection."
|
||||
},
|
||||
"accepted": {
|
||||
"title": "Success!",
|
||||
"message": "Your {{vcLabel}} has been successfully shared with {{receiver}}"
|
||||
},
|
||||
"rejected": {
|
||||
"title": "Notice",
|
||||
"message": "Your {{vcLabel}} was rejected by {{receiver}}"
|
||||
},
|
||||
"verifyingIdentity": "Verifying identity..."
|
||||
},
|
||||
"errors": {
|
||||
"invalidIdentity": {
|
||||
"title": "Unable to verify identity",
|
||||
"message": "An error occured and we couldn't scan your portrait. Try again, make sure your face is visible, devoid of any accessories."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Input } from 'react-native-elements';
|
||||
import { DeviceInfoList } from '../../components/DeviceInfoList';
|
||||
import { Button, Column } from '../../components/ui';
|
||||
import { Button, Column, Row } from '../../components/ui';
|
||||
import { Theme } from '../../components/ui/styleUtils';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
import { Modal, ModalProps } from '../../components/ui/Modal';
|
||||
import { useSendVcModal } from './SendVcModalController';
|
||||
import { useSendVcScreen } from './SendVcScreenController';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { VcItem } from '../../components/VcItem';
|
||||
import { useSelectVcOverlay } from './SelectVcOverlayController';
|
||||
import { SingleVcItem } from '../../components/SingleVcItem';
|
||||
import { VerifyIdentityOverlay } from './VerifyIdentityOverlay';
|
||||
|
||||
export const SendVcModal: React.FC<SendVcModalProps> = (props) => {
|
||||
const { t } = useTranslation('SendVcModal');
|
||||
const controller = useSendVcModal();
|
||||
export const SendVcScreen: React.FC = () => {
|
||||
const { t } = useTranslation('SendVcScreen');
|
||||
const controller = useSendVcScreen();
|
||||
|
||||
const onShare = () => {
|
||||
controller.ACCEPT_REQUEST();
|
||||
@@ -33,7 +33,7 @@ export const SendVcModal: React.FC<SendVcModalProps> = (props) => {
|
||||
const reasonLabel = t('Reason For Sharing');
|
||||
|
||||
return (
|
||||
<Modal {...props}>
|
||||
<React.Fragment>
|
||||
<Column fill backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
|
||||
<Column padding="16 0" scroll>
|
||||
<DeviceInfoList of="receiver" deviceInfo={controller.receiverInfo} />
|
||||
@@ -90,33 +90,80 @@ export const SendVcModal: React.FC<SendVcModalProps> = (props) => {
|
||||
</Column>
|
||||
</Column>
|
||||
|
||||
<SelectVcOverlay
|
||||
isVisible={controller.isSelectingVc}
|
||||
receiverName={controller.receiverInfo.deviceName}
|
||||
onSelect={controller.SELECT_VC}
|
||||
onVerifyAndSelect={controller.VERIFY_AND_SELECT_VC}
|
||||
onCancel={controller.CANCEL}
|
||||
vcKeys={controller.vcKeys}
|
||||
/>
|
||||
|
||||
<VerifyIdentityOverlay
|
||||
isVisible={controller.isVerifyingUserIdentity}
|
||||
onCancel={controller.CANCEL}
|
||||
onFaceValid={controller.FACE_VALID}
|
||||
onFaceInvalid={controller.FACE_INVALID}
|
||||
/>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isInvalidUserIdentity}
|
||||
title={t('errors.invalidIdentity.title')}
|
||||
message={t('errors.invalidIdentity.message')}
|
||||
onBackdropPress={controller.DISMISS}>
|
||||
<Row>
|
||||
<Button
|
||||
fill
|
||||
type="clear"
|
||||
title={t('common:cancel')}
|
||||
onPress={controller.DISMISS}
|
||||
margin={[0, 8, 0, 0]}
|
||||
/>
|
||||
<Button
|
||||
fill
|
||||
title={t('common:tryAgain')}
|
||||
onPress={controller.RETRY_VERIFICATION}
|
||||
/>
|
||||
</Row>
|
||||
</MessageOverlay>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isSendingVc}
|
||||
title={t('Sharing..')}
|
||||
hasProgress
|
||||
title={t('status.sharing.title')}
|
||||
hint={
|
||||
controller.isSendingVcTimeout ? t('status.sharing.timeoutHint') : null
|
||||
}
|
||||
onCancel={controller.isSendingVcTimeout ? controller.CANCEL : null}
|
||||
progress
|
||||
/>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.status != null}
|
||||
title={controller.status?.title}
|
||||
hint={controller.status?.hint}
|
||||
onCancel={controller.status?.onCancel}
|
||||
progress
|
||||
/>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isAccepted}
|
||||
title={t(controller.vcLabel.singular, 'Sent succesfully')}
|
||||
message={t('statusAccepted.message', {
|
||||
title={t('status.accepted.title')}
|
||||
message={t('status.accepted.message', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
receiver: controller.receiverInfo.deviceName,
|
||||
})}
|
||||
onShow={props.onDismiss}
|
||||
onShow={controller.DISMISS}
|
||||
/>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isRejected}
|
||||
title={t('statusRejected.title')}
|
||||
message={t('statusRejected.message', {
|
||||
title={t('status.rejected.title')}
|
||||
message={t('status.rejected.message', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
receiver: controller.receiverInfo.deviceName,
|
||||
})}
|
||||
onBackdropPress={props.onDismiss}
|
||||
onBackdropPress={controller.DISMISS}
|
||||
/>
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
type SendVcModalProps = ModalProps;
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useSelector } from '@xstate/react';
|
||||
import { useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MessageOverlayProps } from '../../components/MessageOverlay';
|
||||
import {
|
||||
ScanEvents,
|
||||
selectIsAccepted,
|
||||
@@ -9,19 +11,41 @@ import {
|
||||
selectIsSelectingVc,
|
||||
selectIsSendingVc,
|
||||
selectVcName,
|
||||
selectIsSendingVcTimeout,
|
||||
selectIsVerifyingUserIdentity,
|
||||
selectIsInvalidUserIdentity,
|
||||
} from '../../machines/scan';
|
||||
import { selectVcLabel } from '../../machines/settings';
|
||||
import { selectShareableVcs } from '../../machines/vc';
|
||||
import { GlobalContext } from '../../shared/GlobalContext';
|
||||
import { VC } from '../../types/vc';
|
||||
|
||||
export function useSendVcModal() {
|
||||
export function useSendVcScreen() {
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const scanService = appService.children.get('scan');
|
||||
const settingsService = appService.children.get('settings');
|
||||
const vcService = appService.children.get('vc');
|
||||
|
||||
const { t } = useTranslation('SendVcScreen');
|
||||
const isSendingVc = useSelector(scanService, selectIsSendingVc);
|
||||
const isSendingVcTimeout = useSelector(scanService, selectIsSendingVcTimeout);
|
||||
const CANCEL = () => scanService.send(ScanEvents.CANCEL());
|
||||
|
||||
let status: Pick<MessageOverlayProps, 'title' | 'hint' | 'onCancel'> = null;
|
||||
if (isSendingVc) {
|
||||
status = {
|
||||
title: t('status.sharing.title'),
|
||||
};
|
||||
} else if (isSendingVcTimeout) {
|
||||
status = {
|
||||
title: t('status.sharing.title'),
|
||||
hint: t('status.sharing.timeoutHint'),
|
||||
onCancel: CANCEL,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
receiverInfo: useSelector(scanService, selectReceiverInfo),
|
||||
reason: useSelector(scanService, selectReason),
|
||||
vcName: useSelector(scanService, selectVcName),
|
||||
@@ -29,17 +53,31 @@ export function useSendVcModal() {
|
||||
vcKeys: useSelector(vcService, selectShareableVcs),
|
||||
|
||||
isSelectingVc: useSelector(scanService, selectIsSelectingVc),
|
||||
isSendingVc: useSelector(scanService, selectIsSendingVc),
|
||||
isSendingVc,
|
||||
isSendingVcTimeout,
|
||||
isAccepted: useSelector(scanService, selectIsAccepted),
|
||||
isRejected: useSelector(scanService, selectIsRejected),
|
||||
isVerifyingUserIdentity: useSelector(
|
||||
scanService,
|
||||
selectIsVerifyingUserIdentity
|
||||
),
|
||||
isInvalidUserIdentity: useSelector(
|
||||
scanService,
|
||||
selectIsInvalidUserIdentity
|
||||
),
|
||||
|
||||
ACCEPT_REQUEST: () => scanService.send(ScanEvents.ACCEPT_REQUEST()),
|
||||
CANCEL: () => scanService.send(ScanEvents.CANCEL()),
|
||||
CANCEL,
|
||||
SELECT_VC: (vc: VC) => scanService.send(ScanEvents.SELECT_VC(vc)),
|
||||
VERIFY_AND_SELECT_VC: (vc: VC) =>
|
||||
scanService.send(ScanEvents.VERIFY_AND_SELECT_VC(vc)),
|
||||
DISMISS: () => scanService.send(ScanEvents.DISMISS()),
|
||||
UPDATE_REASON: (reason: string) =>
|
||||
scanService.send(ScanEvents.UPDATE_REASON(reason)),
|
||||
UPDATE_VC_NAME: (vcName: string) =>
|
||||
scanService.send(ScanEvents.UPDATE_VC_NAME(vcName)),
|
||||
FACE_VALID: () => scanService.send(ScanEvents.FACE_VALID()),
|
||||
FACE_INVALID: () => scanService.send(ScanEvents.FACE_INVALID()),
|
||||
RETRY_VERIFICATION: () => scanService.send(ScanEvents.RETRY_VERIFICATION()),
|
||||
};
|
||||
}
|
||||
39
screens/Scan/VerifyIdentityOverlay.tsx
Normal file
39
screens/Scan/VerifyIdentityOverlay.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import FaceAuth from 'mosip-mobileid-sdk';
|
||||
import React from 'react';
|
||||
import { Dimensions, StyleSheet } from 'react-native';
|
||||
import { Icon, Overlay } from 'react-native-elements';
|
||||
import { Column, Row } from '../../components/ui';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import {
|
||||
useVerifyIdentityOverlay,
|
||||
VerifyIdentityOverlayProps,
|
||||
} from './VerifyIdentityOverlayController';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
content: {
|
||||
width: Dimensions.get('screen').width,
|
||||
height: Dimensions.get('screen').height,
|
||||
backgroundColor: Colors.White,
|
||||
},
|
||||
});
|
||||
|
||||
export const VerifyIdentityOverlay: React.FC<VerifyIdentityOverlayProps> = (
|
||||
props
|
||||
) => {
|
||||
const controller = useVerifyIdentityOverlay();
|
||||
|
||||
return (
|
||||
<Overlay isVisible={props.isVisible}>
|
||||
<Row align="flex-end" padding="16">
|
||||
<Icon name="close" color={Colors.Orange} onPress={props.onCancel} />
|
||||
</Row>
|
||||
<Column fill style={styles.content} align="center">
|
||||
<FaceAuth
|
||||
data={controller.selectedVc?.credential?.biometrics.face}
|
||||
onValidationSuccess={props.onFaceValid}
|
||||
// onValidationFailed={props.onFaceInvalid}
|
||||
/>
|
||||
</Column>
|
||||
</Overlay>
|
||||
);
|
||||
};
|
||||
21
screens/Scan/VerifyIdentityOverlayController.ts
Normal file
21
screens/Scan/VerifyIdentityOverlayController.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useContext } from 'react';
|
||||
import { scanMachine, selectSelectedVc } from '../../machines/scan';
|
||||
import { GlobalContext } from '../../shared/GlobalContext';
|
||||
|
||||
import { useSelector } from '@xstate/react';
|
||||
|
||||
export function useVerifyIdentityOverlay() {
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const scanService = appService.children.get(scanMachine.id);
|
||||
|
||||
return {
|
||||
selectedVc: useSelector(scanService, selectSelectedVc),
|
||||
};
|
||||
}
|
||||
|
||||
export interface VerifyIdentityOverlayProps {
|
||||
isVisible: boolean;
|
||||
onCancel: () => void;
|
||||
onFaceValid: () => void;
|
||||
onFaceInvalid: () => void;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { scanMachine } from '../machines/scan';
|
||||
import { settingsMachine } from '../machines/settings';
|
||||
import { storeMachine } from '../machines/store';
|
||||
import { vcMachine } from '../machines/vc';
|
||||
import { revokeVidsMachine } from '../machines/revoke';
|
||||
|
||||
export const GlobalContext = createContext({} as GlobalServices);
|
||||
|
||||
@@ -23,4 +24,5 @@ export interface AppServices {
|
||||
activityLog: ActorRefFrom<typeof activityLogMachine>;
|
||||
request: ActorRefFrom<typeof requestMachine>;
|
||||
scan: ActorRefFrom<typeof scanMachine>;
|
||||
revoke: ActorRefFrom<typeof revokeVidsMachine>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"editLabel": "Edit {{label}}"
|
||||
"editLabel": "Edit {{label}}",
|
||||
"tryAgain": "Try again"
|
||||
}
|
||||
7
shared/location.ios.ts
Normal file
7
shared/location.ios.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function checkLocation(onEnabled: () => void) {
|
||||
onEnabled(); // iOS does not need location enabled
|
||||
}
|
||||
|
||||
export function requestLocation() {
|
||||
// pass
|
||||
}
|
||||
19
shared/location.ts
Normal file
19
shared/location.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import LocationEnabler from 'react-native-location-enabler';
|
||||
|
||||
const LOCATION_CONFIG = {
|
||||
priority: LocationEnabler.PRIORITIES.BALANCED_POWER_ACCURACY,
|
||||
alwaysShow: false,
|
||||
needBle: true,
|
||||
};
|
||||
|
||||
export function checkLocation(onEnabled: () => void, onDisabled: () => void) {
|
||||
const subscription = LocationEnabler.addListener(({ locationEnabled }) => {
|
||||
locationEnabled ? onEnabled() : onDisabled();
|
||||
});
|
||||
LocationEnabler.checkSettings(LOCATION_CONFIG);
|
||||
return subscription;
|
||||
}
|
||||
|
||||
export function requestLocation() {
|
||||
return LocationEnabler.requestResolutionSettings(LOCATION_CONFIG);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export class BackendResponseError extends Error {
|
||||
}
|
||||
|
||||
export async function request(
|
||||
method: 'GET' | 'POST',
|
||||
method: 'GET' | 'POST' | 'PATCH',
|
||||
path: `/${string}`,
|
||||
body?: Record<string, unknown>
|
||||
) {
|
||||
|
||||
29
shared/vcjs/createVerifiablePresentation.ts
Normal file
29
shared/vcjs/createVerifiablePresentation.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import vcjs from '@digitalcredentials/vc';
|
||||
import jsonld from '@digitalcredentials/jsonld';
|
||||
// import { RSAKeyPair } from '@digitalcredentials/jsonld-signatures';
|
||||
import { RsaSignature2018 } from '../../lib/jsonld-signatures/suites/rsa2018/RsaSignature2018';
|
||||
import { VerifiableCredential, VerifiablePresentation } from '../../types/vc';
|
||||
|
||||
export function createVerifiablePresentation(
|
||||
vc: VerifiableCredential,
|
||||
challenge: string
|
||||
): Promise<VerifiablePresentation> {
|
||||
const presentation = vcjs.createPresentation({
|
||||
verifiableCredential: [vc],
|
||||
});
|
||||
|
||||
// TODO: private key to sign VP
|
||||
// const key = new RSAKeyPair({ ... })
|
||||
const suite = new RsaSignature2018({
|
||||
verificationMethod: vc.proof.verificationMethod,
|
||||
date: vc.proof.created,
|
||||
// TODO: key
|
||||
});
|
||||
|
||||
return vcjs.signPresentation({
|
||||
presentation,
|
||||
suite,
|
||||
challenge,
|
||||
documentLoader: jsonld.documentLoaders.xhr(),
|
||||
});
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import vcjs from '@digitalcredentials/vc';
|
||||
import jsonld from '@digitalcredentials/jsonld';
|
||||
import { RsaSignature2018 } from '../lib/jsonld-signatures/suites/rsa2018/RsaSignature2018';
|
||||
import { Ed25519Signature2018 } from '../lib/jsonld-signatures/suites/ed255192018/Ed25519Signature2018';
|
||||
import { AssertionProofPurpose } from '../lib/jsonld-signatures/purposes/AssertionProofPurpose';
|
||||
import { PublicKeyProofPurpose } from '../lib/jsonld-signatures/purposes/PublicKeyProofPurpose';
|
||||
import { VerifiableCredential } from '../types/vc';
|
||||
import { RsaSignature2018 } from '../../lib/jsonld-signatures/suites/rsa2018/RsaSignature2018';
|
||||
import { Ed25519Signature2018 } from '../../lib/jsonld-signatures/suites/ed255192018/Ed25519Signature2018';
|
||||
import { AssertionProofPurpose } from '../../lib/jsonld-signatures/purposes/AssertionProofPurpose';
|
||||
import { PublicKeyProofPurpose } from '../../lib/jsonld-signatures/purposes/PublicKeyProofPurpose';
|
||||
import { VerifiableCredential } from '../../types/vc';
|
||||
|
||||
// FIXME: Ed25519Signature2018 not fully supported yet.
|
||||
const ProofType = {
|
||||
23
shared/vcjs/verifyPresentation.ts
Normal file
23
shared/vcjs/verifyPresentation.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import vcjs from '@digitalcredentials/vc';
|
||||
import jsonld from '@digitalcredentials/jsonld';
|
||||
import { RsaSignature2018 } from '../../lib/jsonld-signatures/suites/rsa2018/RsaSignature2018';
|
||||
import { VerifiablePresentation } from '../../types/vc';
|
||||
|
||||
export async function verifyPresentation(
|
||||
presentation: VerifiablePresentation,
|
||||
challenge: string
|
||||
): Promise<boolean> {
|
||||
const suite = new RsaSignature2018({
|
||||
verificationMethod: presentation.proof.verificationMethod,
|
||||
date: presentation.proof.created,
|
||||
});
|
||||
|
||||
const result = await vcjs.verify({
|
||||
presentation,
|
||||
challenge,
|
||||
suite,
|
||||
documentLoader: jsonld.documentLoaders.xhr(),
|
||||
});
|
||||
|
||||
return result.verified;
|
||||
}
|
||||
38
types/vc.ts
38
types/vc.ts
@@ -31,25 +31,27 @@ export interface DecodedCredential {
|
||||
|
||||
export interface CredentialSubject {
|
||||
UIN: string;
|
||||
addressLine1: string;
|
||||
addressLine2: string;
|
||||
addressLine3: string;
|
||||
addressLine1: LocalizedField[] | string;
|
||||
addressLine2: LocalizedField[] | string;
|
||||
addressLine3: LocalizedField[] | string;
|
||||
biometrics: string; // Encrypted Base64Encoded Biometrics
|
||||
city: string;
|
||||
city: LocalizedField[] | string;
|
||||
dateOfBirth: string;
|
||||
email: string;
|
||||
fullName: string;
|
||||
gender: string;
|
||||
gender: LocalizedField[] | string;
|
||||
id: string;
|
||||
phone: string;
|
||||
postalCode: string;
|
||||
province: string;
|
||||
region: string;
|
||||
province: LocalizedField[] | string;
|
||||
region: LocalizedField[] | string;
|
||||
vcVer: 'VC-V1' | string;
|
||||
}
|
||||
|
||||
type VCContext = (string | Record<string, unknown>)[];
|
||||
|
||||
export interface VerifiableCredential {
|
||||
'@context': (string | Record<string, unknown>)[];
|
||||
'@context': VCContext;
|
||||
'credentialSubject': CredentialSubject;
|
||||
'id': string;
|
||||
'issuanceDate': string;
|
||||
@@ -64,6 +66,21 @@ export interface VerifiableCredential {
|
||||
'type': VerifiableCredentialType[];
|
||||
}
|
||||
|
||||
export interface VerifiablePresentation {
|
||||
'@context': VCContext;
|
||||
'verifiableCredential': VerifiableCredential[];
|
||||
'type': 'VerifiablePresentation';
|
||||
'proof': {
|
||||
created: string;
|
||||
jws: string;
|
||||
proofPurpose: 'authentication' | string;
|
||||
type: 'RsaSignature2018' | string;
|
||||
verificationMethod: string;
|
||||
challenge: string;
|
||||
domain: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type VerifiableCredentialType =
|
||||
| 'VerifiableCredential'
|
||||
| 'MOSIPVerfiableCredential'
|
||||
@@ -73,3 +90,8 @@ export interface VCLabel {
|
||||
singular: string;
|
||||
plural: string;
|
||||
}
|
||||
|
||||
export interface LocalizedField {
|
||||
language: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user