refactor: remove old VID code

This commit is contained in:
Paolo Miguel de Leon
2022-05-12 10:26:20 +08:00
parent 604a879413
commit 2b339b51c3
7 changed files with 203 additions and 376 deletions

View File

@@ -1,3 +1,4 @@
import { formatDistanceToNow } from 'date-fns';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Image } from 'react-native';
@@ -5,6 +6,7 @@ import { Icon, ListItem } from 'react-native-elements';
import { VC, CredentialSubject } from '../types/vc';
import { Column, Row, Text } from './ui';
import { Colors } from './ui/styleUtils';
import { TextItem } from './ui/TextItem';
const VerifiedIcon: React.FC = () => {
return (
@@ -42,7 +44,7 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
{props.vc?.id}
</Text>
</Column>
<Column fill elevation={1} padding="12 16">
<Column fill elevation={1} padding="12 16" margin="">
<Text size="smaller" color={Colors.Grey}>
{t('status')}
</Text>
@@ -74,64 +76,49 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
</ListItem.Content>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Subtitle>{t('fullName')}</ListItem.Subtitle>
<ListItem.Title>
{props.vc?.verifiableCredential.credentialSubject.fullName}
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Subtitle>{t('gender')}</ListItem.Subtitle>
<ListItem.Title>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.gender
)}
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Subtitle>{t('dateOfBirth')}</ListItem.Subtitle>
<ListItem.Title>
{props.vc?.verifiableCredential.credentialSubject.dateOfBirth}
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Subtitle>{t('phoneNumber')}</ListItem.Subtitle>
<ListItem.Title>
{props.vc?.verifiableCredential.credentialSubject.phone}
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Subtitle>{t('email')}</ListItem.Subtitle>
<ListItem.Title>
{props.vc?.verifiableCredential.credentialSubject.email}
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Subtitle>{t('address')}</ListItem.Subtitle>
<ListItem.Title>
{getFullAddress(props.vc?.verifiableCredential.credentialSubject)}
</ListItem.Title>
</ListItem.Content>
</ListItem>
{Boolean(props.vc?.reason) && (
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Subtitle>{t('reasonForSharing')}</ListItem.Subtitle>
<ListItem.Title>{props.vc?.reason}</ListItem.Title>
</ListItem.Content>
</ListItem>
<TextItem
divider
label="Full name"
text={props.vc?.verifiableCredential.credentialSubject.fullName}
/>
<TextItem
divider
label="Gender"
text={props.vc?.verifiableCredential.credentialSubject.gender}
/>
<TextItem
divider
label="Date of birth"
text={props.vc?.verifiableCredential.credentialSubject.dateOfBirth}
/>
<TextItem
divider
label="Phone number"
text={props.vc?.verifiableCredential.credentialSubject.phone}
/>
<TextItem
divider
label="Email"
text={props.vc?.verifiableCredential.credentialSubject.email}
/>
<TextItem
divider
label="Address"
text={getFullAddress(props.vc?.verifiableCredential.credentialSubject)}
/>
{props.vc?.reason?.length > 0 && (
<Text margin="24 24 16 24" weight="semibold">
Reason(s) for sharing
</Text>
)}
{props.vc?.reason?.map((reason, index) => (
<TextItem
key={index}
divider
label={formatDistanceToNow(reason.timestamp, { addSuffix: true })}
text={reason.message}
/>
))}
</Column>
);
};
@@ -158,13 +145,13 @@ function getFullAddress(credential: CredentialSubject) {
'province',
'region',
];
return fields
.map((field) => getLocalizedField(credential[field]))
.concat(credential.postalCode)
.filter(Boolean)
.join(', ');
}
function getLocalizedField(rawField: string | LocalizedField) {
try {
const locales: LocalizedField[] =

View File

@@ -1,134 +0,0 @@
import { formatDistanceToNow } from 'date-fns';
import React from 'react';
import { Image } from 'react-native';
import { ListItem } from 'react-native-elements';
import { VID, VIDCredential } from '../types/vid';
import { Column, Row, Text } from './ui';
import { Colors } from './ui/styleUtils';
import { TextItem } from './ui/TextItem';
export const VidDetails: React.FC<VidDetailsProps> = (props) => {
return (
<Column>
<Row padding="16 24">
<Column fill elevation={1} padding="12 16" margin="0 16 0 0">
<Text size="smaller" color={Colors.Grey}>
Generated
</Text>
<Text weight="bold" size="smaller">
{new Date(props.vid?.generatedOn).toLocaleDateString()}
</Text>
</Column>
<Column fill elevation={1} padding="12 16" margin="0 16 0 0">
<Text size="smaller" color={Colors.Grey}>
UIN
</Text>
<Text weight="bold" size="smaller">
{props.vid?.uin}
</Text>
</Column>
<Column fill elevation={1} padding="12 16" margin="">
<Text size="smaller" color={Colors.Grey}>
Status
</Text>
<Text weight="bold" size="smaller">
Valid
</Text>
</Column>
</Row>
{props.vid?.credential.biometrics && (
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Subtitle>
<Text>Photo</Text>
</ListItem.Subtitle>
<ListItem.Content>
<Image
source={{ uri: props.vid?.credential.biometrics.face }}
style={{
width: 110,
height: 110,
resizeMode: 'cover',
marginTop: 8,
}}
/>
</ListItem.Content>
</ListItem.Content>
</ListItem>
)}
<TextItem
divider
label="Full name"
text={props.vid?.credential.fullName}
/>
<TextItem divider label="Gender" text={props.vid?.credential.gender} />
<TextItem
divider
label="Date of birth"
text={props.vid?.credential.dateOfBirth}
/>
<TextItem
divider
label="Phone number"
text={props.vid?.credential.phone}
/>
<TextItem divider label="Email" text={props.vid?.credential.email} />
<TextItem
divider
label="Address"
text={getFullAddress(props.vid?.credential)}
/>
{props.vid?.reason?.length > 0 && (
<Text margin="24 24 16 24" weight="semibold">
Reason(s) for sharing
</Text>
)}
{props.vid?.reason?.map((reason, index) => (
<TextItem
key={index}
divider
label={formatDistanceToNow(reason.timestamp, { addSuffix: true })}
text={reason.message}
/>
))}
</Column>
);
};
interface VidDetailsProps {
vid: VID;
}
interface LocalizedField {
language: string;
value: string;
}
function getFullAddress(credential: VIDCredential) {
if (!credential) {
return '';
}
const fields = [
'addressLine1',
'addressLine2',
'addressLine3',
'city',
'province',
];
return (
fields.map((field) => getLocalizedField(credential[field])).join(', ') +
', ' +
credential.postalCode
);
}
function getLocalizedField(rawField: string) {
try {
const locales: LocalizedField[] = JSON.parse(rawField);
// TODO: language switching
return locales.find((locale) => locale.language === 'eng').value;
} catch (e) {
return '';
}
}

View File

@@ -37,145 +37,173 @@ const model = createModel(
export const VcEvents = model.events;
export const vcMachine = model.createMachine(
{
tsTypes: {} as import('./vc.typegen').Typegen0,
schema: {
context: model.initialContext,
events: {} as EventFrom<typeof model>,
},
id: 'vc',
initial: 'init',
states: {
init: {
initial: 'myVcs',
states: {
myVcs: {
entry: ['loadMyVcs'],
on: {
STORE_RESPONSE: {
target: 'receivedVcs',
actions: ['setMyVcs'],
},
},
},
receivedVcs: {
entry: ['loadReceivedVcs'],
on: {
STORE_RESPONSE: {
target: '#ready',
actions: ['setReceivedVcs'],
},
},
},
},
export const vcMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QDcDGA6AlgO0wF3QFsBPANVVgGIBlAFQHkAlAUQH0XqAFegOWucSgADgHtY+TCOyCQAD0QBmABwAmdAEYALCoCsANgAMOgJx6dAdnNmANCGKIlBvemWbLKvcc06FVgL5+tmhYuAQATmCoYJjIkORUdExsHNx8AkggouJ4ktIZ8gjKalq65qrmmup6SpqatvYI6k6a6HoKmko6OkpK5sb9AUEYEQCGEMREZBRYEAA2YJQsAGIcABKsALIAmqykAMLUMlkSUjIF6uom6JV6ujrqxo7qZfWITRUamgpOKubdWuYVIMQMFRuNJvF0BEAGYRWAACxwUBoDBY7GYXF4-COYhOeVA50u5nQxh8fyKCm6uleCFMxKayjaBgUCjaFmBoLAYwmESiMTi00wcwWyzW6L2zAAkqRmAARXYHHHZXJnN7qXwacwGAyVJymJTqGnqlnoToqFS+XzqA1ODnDLng3nRWIQSEwuGI7DIxJolJY9LCXE5U75NV6dToJy+YwGXrGFRuI0WiNKUnqvS3LwGcwKO1Qh3ESgAcWYtHFUpl8v2hwyx2D+LkbxUplangzPVumhjdTsiEMBg0XWb6rp1vMebBhZLZf2rEltGYGyVeNVjQuEeZvi1zO1Nl7CH7g50w4Uo56E4LlFnAEFZbK5cv66vjAoI1ahyz9OSaYfLsfjCOejmGOF7cleeysLK9AAOo8AAMvQt4PrWQYqqGjTHgokbxhaKgPF46gqEoP7akeug1H0x7aKB4zgeW0rIYGyohgSfa6C4NRVO0mh6DxlQ0n8OitE0mhOD4OpuLmwLYCIEBwDIwQ4PgEIUI+aGsYU1ToBY0baM2Si3LxP5WJGNTNgYeFUgMgQghgSnhJEzoCvAKHMQ2BTtPSRg6o4omVBUNIAcUqgKCoTg9DqJh5vZaksY2hQWho3kdDqOrPD2DSGFhsbaAZDyAjxNETCQkJCvMsXuYgjzOOafxakorJuBaRoWUJ3wWRUHQ+AZehFSpsD5rCcCelAFWriOKbtg1XUmC8+79MYpras2Zj9H0CZ9SVqmuSu6GmGotUWLGjWAgoNKpi0DIhfoFhmFJQz5ty+Z8i6pXCmNe2dBoZj3BZlw1KyRqvhGGYWGtxhlAZSh9U6-KutM7rDUiH0aSOag+I8zQ-N4SYGd9VIPIRzZETDjlw-EKPxaYEZVF0TSWQDehGv9pqkgYENVJcvhFZTBQvvSP30-9XxM-ulgDldvjVA15qaHavOIM2i1NDoPmpf5GWIMe9JDjotSGFm6gBAEQA */
model.createMachine(
{
tsTypes: {} as import('./vc.typegen').Typegen0,
schema: {
context: model.initialContext,
events: {} as EventFrom<typeof model>,
},
ready: {
id: 'ready',
entry: [sendParent('READY')],
on: {
GET_RECEIVED_VCS: {
actions: ['getReceivedVcsResponse'],
},
GET_VC_ITEM: {
actions: ['getVcItemResponse'],
},
VC_ADDED: {
actions: ['prependToMyVcs'],
},
VC_DOWNLOADED: {
actions: ['setDownloadedVc'],
},
VC_RECEIVED: {
actions: ['prependToReceivedVcs'],
},
},
type: 'parallel',
states: {
myVcs: {
initial: 'idle',
states: {
idle: {
on: {
REFRESH_MY_VCS: 'refreshing',
id: 'vc',
initial: 'init',
states: {
init: {
initial: 'myVcs',
states: {
myVcs: {
entry: 'loadMyVcs',
on: {
STORE_RESPONSE: {
actions: 'setMyVcs',
target: 'receivedVcs',
},
},
refreshing: {
entry: ['loadMyVcs'],
on: {
STORE_RESPONSE: {
target: 'idle',
actions: ['setMyVcs'],
},
receivedVcs: {
entry: 'loadReceivedVcs',
on: {
STORE_RESPONSE: {
actions: 'setReceivedVcs',
target: '#vc.ready',
},
},
},
},
},
ready: {
entry: sendParent('READY'),
type: 'parallel',
states: {
myVcs: {
initial: 'idle',
states: {
idle: {
on: {
REFRESH_MY_VCS: {
target: 'refreshing',
},
},
},
refreshing: {
entry: 'loadMyVcs',
on: {
STORE_RESPONSE: {
actions: 'setMyVcs',
target: 'idle',
},
},
},
},
},
receivedVcs: {
initial: 'idle',
states: {
idle: {
on: {
REFRESH_RECEIVED_VCS: {
target: 'refreshing',
},
},
},
refreshing: {
entry: 'loadReceivedVcs',
on: {
STORE_RESPONSE: {
actions: 'setReceivedVcs',
target: 'idle',
},
},
},
},
},
},
receivedVcs: {
initial: 'idle',
states: {
idle: {
on: {
REFRESH_RECEIVED_VCS: 'refreshing',
},
},
refreshing: {
entry: ['loadReceivedVcs'],
on: {
STORE_RESPONSE: {
target: 'idle',
actions: ['setReceivedVcs'],
},
},
},
on: {
GET_RECEIVED_VCS: {
actions: 'getReceivedVcsResponse',
},
GET_VC_ITEM: {
actions: 'getVcItemResponse',
},
VC_ADDED: {
actions: 'prependToMyVcs',
},
VC_DOWNLOADED: {
actions: 'setDownloadedVc',
},
VC_RECEIVED: [
{
actions: 'moveExistingVcToTop',
cond: 'hasExistingReceivedVc',
},
{
actions: 'prependToReceivedVcs',
},
],
},
},
},
},
},
{
actions: {
getReceivedVcsResponse: respond((context) => ({
type: 'VC_RESPONSE',
response: context.receivedVcs,
})),
{
actions: {
getReceivedVcsResponse: respond((context) => ({
type: 'VC_RESPONSE',
response: context.receivedVcs,
})),
getVcItemResponse: respond((context, event) => {
const vc = context.vcs[event.vcKey];
return VcItemEvents.GET_VC_RESPONSE(vc);
}),
getVcItemResponse: respond((context, event) => {
const vc = context.vcs[event.vcKey];
return VcItemEvents.GET_VC_RESPONSE(vc);
}),
loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), {
to: (context) => context.serviceRefs.store,
}),
loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), {
to: (context) => context.serviceRefs.store,
}),
loadReceivedVcs: send(StoreEvents.GET(RECEIVED_VCS_STORE_KEY), {
to: (context) => context.serviceRefs.store,
}),
loadReceivedVcs: send(StoreEvents.GET(RECEIVED_VCS_STORE_KEY), {
to: (context) => context.serviceRefs.store,
}),
setMyVcs: model.assign({
myVcs: (_context, event) => (event.response || []) as string[],
}),
setMyVcs: model.assign({
myVcs: (_context, event) => (event.response || []) as string[],
}),
setReceivedVcs: model.assign({
receivedVcs: (_context, event) => (event.response || []) as string[],
}),
setReceivedVcs: model.assign({
receivedVcs: (_context, event) => (event.response || []) as string[],
}),
setDownloadedVc: (context, event) => {
context.vcs[VC_ITEM_STORE_KEY(event.vc)] = event.vc;
setDownloadedVc: (context, event) => {
context.vcs[VC_ITEM_STORE_KEY(event.vc)] = event.vc;
},
prependToMyVcs: model.assign({
myVcs: (context, event) => [event.vcKey, ...context.myVcs],
}),
prependToReceivedVcs: model.assign({
receivedVcs: (context, event) => [
event.vcKey,
...context.receivedVcs,
],
}),
moveExistingVcToTop: model.assign({
receivedVcs: (context, event) => {
return [
event.vcKey,
...context.receivedVcs.filter((value) => value === event.vcKey),
];
},
}),
},
prependToMyVcs: model.assign({
myVcs: (context, event) => [event.vcKey, ...context.myVcs],
}),
prependToReceivedVcs: model.assign({
receivedVcs: (context, event) => [event.vcKey, ...context.receivedVcs],
}),
},
}
);
guards: {
hasExistingReceivedVc: (context, event) =>
context.receivedVcs.includes(event.vcKey),
},
}
);
export function createVcMachine(serviceRefs: AppServices) {
return vcMachine.withContext({

View File

@@ -9,6 +9,7 @@ export interface Typegen0 {
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';
@@ -24,7 +25,9 @@ export interface Typegen0 {
delays: never;
};
'eventsCausingServices': {};
'eventsCausingGuards': {};
'eventsCausingGuards': {
hasExistingReceivedVc: 'VC_RECEIVED';
};
'eventsCausingDelays': {};
'matchesStates':
| 'init'

View File

@@ -51,6 +51,7 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
title={t('requestingOtp')}
hasProgress
/>
{controller.reAuthenticating !== '' &&
controller.reAuthenticating == 'passcode' && (
<Passcode

View File

@@ -9,7 +9,12 @@ export interface VC {
isVerified: boolean;
lastVerifiedOn: number;
locked: boolean;
reason?: string[];
reason?: VCSharingReason[];
}
export interface VCSharingReason {
timestamp: number;
message: string;
}
export type VcIdType = 'UIN' | 'VID';

View File

@@ -1,63 +0,0 @@
export interface VID {
tag: string;
uin: string;
credential: VIDCredential;
verifiableCredential: VIDVerifiableCredential;
generatedOn: Date;
requestId: string;
reason?: VIDSharingReason[];
}
export interface VIDSharingReason {
timestamp: number;
message: string;
}
export interface VIDCredential {
id: string;
uin: string;
fullName: string;
gender: string;
biometrics: {
// Encrypted Base64Encoded Biometrics
face: string;
finger: {
left_thumb: string;
right_thumb: string;
};
};
dateOfBirth: string;
phone: string;
email: string;
region: string;
addressLine1: string;
addressLine2: string;
addressLine3: string;
city: string;
province: string;
postalCode: string;
}
export interface VIDVerifiableCredential {
id: string;
transactionId: string;
type: {
namespace: string;
name: string;
};
timestamp: string;
dataShareUri: string;
data: {
credential: string;
proof: {
signature: string;
};
credentialType: string;
protectionKey: string;
};
}
export interface VIDLabel {
singular: string;
plural: string;
}