mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-10 05:58:01 -05:00
feat(#INJI-42): [Kiruthika/Bhargavi] show error overlay if saving fails while downloading VC
Issue Link: https://mosip.atlassian.net/browse/INJI-42
This commit is contained in:
@@ -17,7 +17,11 @@ import {
|
||||
selectContext,
|
||||
selectTag,
|
||||
selectEmptyWalletBindingId,
|
||||
selectStoreError,
|
||||
selectIsSavingFailedInIdle,
|
||||
} from '../machines/vcItem';
|
||||
import { VcItemEvents } from '../machines/vcItem';
|
||||
import { MessageOverlay } from '../components/MessageOverlay';
|
||||
import { Column, Row, Text } from './ui';
|
||||
import { Theme } from './ui/styleUtils';
|
||||
import { RotatingIcon } from './RotatingIcon';
|
||||
@@ -26,6 +30,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { VcItemTags } from './VcItemTags';
|
||||
import VerifiedIcon from './VerifiedIcon';
|
||||
import { getLocalizedField } from '../i18n';
|
||||
import { selectVcLabel } from '../machines/settings';
|
||||
|
||||
const getDetails = (arg1, arg2, verifiableCredential) => {
|
||||
if (arg1 === 'Status') {
|
||||
@@ -115,6 +120,7 @@ const WalletUnverified: React.FC = () => {
|
||||
export const VcItem: React.FC<VcItemProps> = (props) => {
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const { t } = useTranslation('VcDetails');
|
||||
const { t: commonTranslate } = useTranslation('common');
|
||||
const machine = useRef(
|
||||
createVcItemMachine(
|
||||
appService.getSnapshot().context.serviceRefs,
|
||||
@@ -126,6 +132,18 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
|
||||
const context = useSelector(service, selectContext);
|
||||
const verifiableCredential = useSelector(service, selectVerifiableCredential);
|
||||
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
|
||||
const settingsService = appService.children.get('settings');
|
||||
const storeError = useSelector(service, selectStoreError);
|
||||
const isSavingFailedInIdle = useSelector(service, selectIsSavingFailedInIdle);
|
||||
const vcLabel = useSelector(settingsService, selectVcLabel);
|
||||
const DISMISS = () => service.send(VcItemEvents.DISMISS());
|
||||
|
||||
let storeErrorTranslationPath = 'errors.savingFailed';
|
||||
const isDiskFullError =
|
||||
storeError?.message?.match('No space left on device') != null;
|
||||
if (isDiskFullError) {
|
||||
storeErrorTranslationPath = 'errors.diskFullError';
|
||||
}
|
||||
|
||||
//Assigning the UIN and VID from the VC details to display the idtype label
|
||||
const uin = verifiableCredential?.credentialSubject.UIN;
|
||||
@@ -139,158 +157,178 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
|
||||
const tag = useSelector(service, selectTag);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => props.onPress(service)}
|
||||
disabled={!verifiableCredential}
|
||||
style={
|
||||
props.selected
|
||||
? Theme.Styles.selectedBindedVc
|
||||
: Theme.Styles.closeCardBgContainer
|
||||
}>
|
||||
<ImageBackground
|
||||
source={!verifiableCredential ? null : Theme.CloseCard}
|
||||
resizeMode="stretch"
|
||||
borderRadius={4}
|
||||
<React.Fragment>
|
||||
<Pressable
|
||||
onPress={() => props.onPress(service)}
|
||||
disabled={!verifiableCredential}
|
||||
style={
|
||||
!verifiableCredential
|
||||
? Theme.Styles.vertloadingContainer
|
||||
: Theme.Styles.backgroundImageContainer
|
||||
props.selected
|
||||
? Theme.Styles.selectedBindedVc
|
||||
: Theme.Styles.closeCardBgContainer
|
||||
}>
|
||||
<Row style={Theme.Styles.homeCloseCardDetailsHeader}>
|
||||
<Column>
|
||||
<Text
|
||||
color={
|
||||
!verifiableCredential
|
||||
? Theme.Colors.LoadingDetailsLabel
|
||||
: Theme.Colors.DetailsLabel
|
||||
}
|
||||
weight="bold"
|
||||
size="smaller">
|
||||
{t('idType')}
|
||||
</Text>
|
||||
<Text
|
||||
weight="bold"
|
||||
color={Theme.Colors.Details}
|
||||
size="smaller"
|
||||
<ImageBackground
|
||||
source={!verifiableCredential ? null : Theme.CloseCard}
|
||||
resizeMode="stretch"
|
||||
borderRadius={4}
|
||||
style={
|
||||
!verifiableCredential
|
||||
? Theme.Styles.vertloadingContainer
|
||||
: Theme.Styles.backgroundImageContainer
|
||||
}>
|
||||
<Row style={Theme.Styles.homeCloseCardDetailsHeader}>
|
||||
<Column>
|
||||
<Text
|
||||
color={
|
||||
!verifiableCredential
|
||||
? Theme.Colors.LoadingDetailsLabel
|
||||
: Theme.Colors.DetailsLabel
|
||||
}
|
||||
weight="bold"
|
||||
size="smaller">
|
||||
{t('idType')}
|
||||
</Text>
|
||||
<Text
|
||||
weight="bold"
|
||||
color={Theme.Colors.Details}
|
||||
size="smaller"
|
||||
style={
|
||||
!verifiableCredential
|
||||
? Theme.Styles.loadingTitle
|
||||
: Theme.Styles.subtitle
|
||||
}>
|
||||
{t('nationalCard')}
|
||||
</Text>
|
||||
</Column>
|
||||
<Image
|
||||
source={Theme.MosipLogo}
|
||||
style={Theme.Styles.logo}
|
||||
resizeMethod="auto"
|
||||
/>
|
||||
</Row>
|
||||
<Row
|
||||
crossAlign="center"
|
||||
margin="5 0 0 0"
|
||||
style={
|
||||
!verifiableCredential ? Theme.Styles.loadingContainer : null
|
||||
}>
|
||||
<Column
|
||||
style={
|
||||
!verifiableCredential
|
||||
? Theme.Styles.loadingTitle
|
||||
: Theme.Styles.subtitle
|
||||
? Theme.Styles.loadingContainer
|
||||
: Theme.Styles.closeDetails
|
||||
}>
|
||||
{t('nationalCard')}
|
||||
</Text>
|
||||
</Column>
|
||||
<Image
|
||||
source={Theme.MosipLogo}
|
||||
style={Theme.Styles.logo}
|
||||
resizeMethod="auto"
|
||||
/>
|
||||
</Row>
|
||||
<Row
|
||||
crossAlign="center"
|
||||
margin="5 0 0 0"
|
||||
style={!verifiableCredential ? Theme.Styles.loadingContainer : null}>
|
||||
<Column
|
||||
style={
|
||||
!verifiableCredential
|
||||
? Theme.Styles.loadingContainer
|
||||
: Theme.Styles.closeDetails
|
||||
}>
|
||||
<Image
|
||||
source={
|
||||
!verifiableCredential
|
||||
? Theme.ProfileIcon
|
||||
: { uri: context.credential.biometrics.face }
|
||||
}
|
||||
style={Theme.Styles.closeCardImage}
|
||||
/>
|
||||
<Image
|
||||
source={
|
||||
!verifiableCredential
|
||||
? Theme.ProfileIcon
|
||||
: { uri: context.credential.biometrics.face }
|
||||
}
|
||||
style={Theme.Styles.closeCardImage}
|
||||
/>
|
||||
|
||||
<Column margin="0 0 0 25" style={{ alignItems: 'flex-start' }}>
|
||||
{getDetails(t('fullName'), fullName, verifiableCredential)}
|
||||
{!verifiableCredential
|
||||
? getDetails(t('id'), uin || vid, verifiableCredential)
|
||||
: null}
|
||||
{uin ? getDetails(t('uin'), uin, verifiableCredential) : null}
|
||||
{vid ? getDetails(t('vid'), vid, verifiableCredential) : null}
|
||||
{getDetails(t('generatedOn'), generatedOn, verifiableCredential)}
|
||||
{getDetails(t('status'), t('valid'), verifiableCredential)}
|
||||
<Column margin="0 0 0 25" style={{ alignItems: 'flex-start' }}>
|
||||
{getDetails(t('fullName'), fullName, verifiableCredential)}
|
||||
{!verifiableCredential
|
||||
? getDetails(t('id'), uin || vid, verifiableCredential)
|
||||
: null}
|
||||
{uin ? getDetails(t('uin'), uin, verifiableCredential) : null}
|
||||
{vid ? getDetails(t('vid'), vid, verifiableCredential) : null}
|
||||
{getDetails(
|
||||
t('generatedOn'),
|
||||
generatedOn,
|
||||
verifiableCredential
|
||||
)}
|
||||
{getDetails(t('status'), t('valid'), verifiableCredential)}
|
||||
</Column>
|
||||
</Column>
|
||||
</Column>
|
||||
|
||||
{!verifiableCredential && (
|
||||
<RotatingIcon name="sync" color={Theme.Colors.rotatingIcon} />
|
||||
)}
|
||||
</Row>
|
||||
<VcItemTags tag={tag} />
|
||||
</ImageBackground>
|
||||
{props.activeTab !== 'receivedVcsTab' &&
|
||||
props.activeTab != 'sharingVcScreen' && (
|
||||
<Row>
|
||||
{emptyWalletBindingId ? (
|
||||
<Row
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
align="space-between"
|
||||
crossAlign="center">
|
||||
<Row crossAlign="center" style={{ flex: 1 }}>
|
||||
{verifiableCredential && <WalletUnverified />}
|
||||
<Text
|
||||
color={Theme.Colors.Details}
|
||||
weight="semibold"
|
||||
size="small"
|
||||
margin="10 33 10 10"
|
||||
style={
|
||||
!verifiableCredential
|
||||
? Theme.Styles.loadingTitle
|
||||
: Theme.Styles.statusLabel
|
||||
}
|
||||
children={t('offlineAuthDisabledHeader')}></Text>
|
||||
</Row>
|
||||
{!verifiableCredential && (
|
||||
<RotatingIcon name="sync" color={Theme.Colors.rotatingIcon} />
|
||||
)}
|
||||
</Row>
|
||||
<VcItemTags tag={tag} />
|
||||
</ImageBackground>
|
||||
{props.activeTab !== 'receivedVcsTab' &&
|
||||
props.activeTab != 'sharingVcScreen' && (
|
||||
<Row>
|
||||
{emptyWalletBindingId ? (
|
||||
<Row
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
align="space-between"
|
||||
crossAlign="center">
|
||||
<Row crossAlign="center" style={{ flex: 1 }}>
|
||||
{verifiableCredential && <WalletUnverified />}
|
||||
<Text
|
||||
color={Theme.Colors.Details}
|
||||
weight="semibold"
|
||||
size="small"
|
||||
margin="10 33 10 10"
|
||||
style={
|
||||
!verifiableCredential
|
||||
? Theme.Styles.loadingTitle
|
||||
: Theme.Styles.statusLabel
|
||||
}
|
||||
children={t('offlineAuthDisabledHeader')}></Text>
|
||||
</Row>
|
||||
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
verifiableCredential ? props.onPress(service) : null
|
||||
}>
|
||||
<Icon
|
||||
name="dots-three-horizontal"
|
||||
type="entypo"
|
||||
color={Theme.Colors.GrayIcon}
|
||||
/>
|
||||
</Pressable>
|
||||
</Row>
|
||||
) : (
|
||||
<Row
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
align="space-between"
|
||||
crossAlign="center">
|
||||
<Row crossAlign="center" style={{ flex: 1 }}>
|
||||
<WalletVerified />
|
||||
<Text
|
||||
color={Theme.Colors.statusLabel}
|
||||
weight="semibold"
|
||||
size="smaller"
|
||||
margin="10 10 10 10"
|
||||
style={
|
||||
!verifiableCredential
|
||||
? Theme.Styles.loadingTitle
|
||||
: Theme.Styles.subtitle
|
||||
}
|
||||
children={t('profileAuthenticated')}></Text>
|
||||
</Row>
|
||||
|
||||
{props.showOnlyBindedVc ? null : (
|
||||
<Pressable onPress={() => props.onPress(service)}>
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
verifiableCredential ? props.onPress(service) : null
|
||||
}>
|
||||
<Icon
|
||||
name="dots-three-horizontal"
|
||||
type="entypo"
|
||||
color={Theme.Colors.GrayIcon}
|
||||
/>
|
||||
</Pressable>
|
||||
)}
|
||||
</Row>
|
||||
)}
|
||||
</Row>
|
||||
)}
|
||||
</Pressable>
|
||||
</Row>
|
||||
) : (
|
||||
<Row
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
align="space-between"
|
||||
crossAlign="center">
|
||||
<Row crossAlign="center" style={{ flex: 1 }}>
|
||||
<WalletVerified />
|
||||
<Text
|
||||
color={Theme.Colors.statusLabel}
|
||||
weight="semibold"
|
||||
size="smaller"
|
||||
margin="10 10 10 10"
|
||||
style={
|
||||
!verifiableCredential
|
||||
? Theme.Styles.loadingTitle
|
||||
: Theme.Styles.subtitle
|
||||
}
|
||||
children={t('profileAuthenticated')}></Text>
|
||||
</Row>
|
||||
|
||||
{props.showOnlyBindedVc ? null : (
|
||||
<Pressable onPress={() => props.onPress(service)}>
|
||||
<Icon
|
||||
name="dots-three-horizontal"
|
||||
type="entypo"
|
||||
color={Theme.Colors.GrayIcon}
|
||||
/>
|
||||
</Pressable>
|
||||
)}
|
||||
</Row>
|
||||
)}
|
||||
</Row>
|
||||
)}
|
||||
</Pressable>
|
||||
<MessageOverlay
|
||||
isVisible={isSavingFailedInIdle}
|
||||
title={commonTranslate(storeErrorTranslationPath + '.title', {
|
||||
vcLabelSingular: vcLabel.singular,
|
||||
vcLabelPlural: vcLabel.plural,
|
||||
})}
|
||||
message={commonTranslate(storeErrorTranslationPath + '.message', {
|
||||
vcLabelSingular: vcLabel.singular,
|
||||
vcLabelPlural: vcLabel.plural,
|
||||
})}
|
||||
onBackdropPress={DISMISS}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -391,7 +391,15 @@
|
||||
"allowAccess": "Allow access to the camera"
|
||||
},
|
||||
"errors": {
|
||||
"genericError": "Something went wrong. Please try again after some time!"
|
||||
"genericError": "Something went wrong. Please try again after some time!",
|
||||
"savingFailed": {
|
||||
"title": "Failed to save the {{vcLabelSingular}}",
|
||||
"message": "Something went wrong while saving {{vcLabelSingular}} to the store."
|
||||
},
|
||||
"diskFullError": {
|
||||
"title": "Failed to save the {{vcLabelSingular}}",
|
||||
"message": "No more {{vcLabelPlural}} can be received or saved as App Data is full."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ const model = createModel(
|
||||
APPEND: (key: string, value: unknown) => ({ key, value }),
|
||||
PREPEND: (key: string, value: unknown) => ({ key, value }),
|
||||
REMOVE: (key: string, value: string) => ({ key, value }),
|
||||
REMOVE_VC_METADATA: (key: string, value: string) => ({ key, value }),
|
||||
REMOVE_ITEMS: (key: string, values: string[]) => ({ key, values }),
|
||||
CLEAR: () => ({}),
|
||||
ERROR: (error: Error) => ({ error }),
|
||||
@@ -119,6 +120,9 @@ export const storeMachine =
|
||||
REMOVE: {
|
||||
actions: 'forwardStoreRequest',
|
||||
},
|
||||
REMOVE_VC_METADATA: {
|
||||
actions: 'forwardStoreRequest',
|
||||
},
|
||||
REMOVE_ITEMS: {
|
||||
actions: 'forwardStoreRequest',
|
||||
},
|
||||
@@ -137,12 +141,12 @@ export const storeMachine =
|
||||
],
|
||||
},
|
||||
STORE_ERROR: {
|
||||
actions: send(
|
||||
(_, event) => model.events.STORE_ERROR(event.error),
|
||||
{
|
||||
actions: [
|
||||
send((_, event) => model.events.STORE_ERROR(event.error), {
|
||||
to: (_, event) => event.requester,
|
||||
}
|
||||
),
|
||||
}),
|
||||
sendUpdate(),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -214,6 +218,15 @@ export const storeMachine =
|
||||
response = event.value;
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_VC_METADATA': {
|
||||
await removeVCMetaData(
|
||||
event.key,
|
||||
event.value,
|
||||
context.encryptionKey
|
||||
);
|
||||
response = event.value;
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_ITEMS': {
|
||||
await removeItems(
|
||||
event.key,
|
||||
@@ -365,6 +378,26 @@ export async function removeItem(
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeVCMetaData(
|
||||
key: string,
|
||||
value: string,
|
||||
encryptionKey: string
|
||||
) {
|
||||
try {
|
||||
const data = await Storage.getItem(key);
|
||||
const decryptedData = decryptJson(encryptionKey, data);
|
||||
const list = JSON.parse(decryptedData);
|
||||
const newList = list.filter((vc: string) => {
|
||||
return !vc.includes(value);
|
||||
});
|
||||
|
||||
await setItem(key, newList, encryptionKey);
|
||||
} catch (e) {
|
||||
console.error('error remove VC metadata:', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeItems(
|
||||
key: string,
|
||||
values: string[],
|
||||
|
||||
@@ -1,5 +1,66 @@
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'internalEvents': {
|
||||
'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.';
|
||||
};
|
||||
'error.platform._store': { type: 'error.platform._store'; data: unknown };
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
'invokeSrcNameMap': {
|
||||
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': {
|
||||
actions: never;
|
||||
delays: never;
|
||||
guards: never;
|
||||
services: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
forwardStoreRequest:
|
||||
| 'APPEND'
|
||||
| 'CLEAR'
|
||||
| 'GET'
|
||||
| 'PREPEND'
|
||||
| 'REMOVE'
|
||||
| 'REMOVE_ITEMS'
|
||||
| 'REMOVE_VC_METADATA'
|
||||
| 'SET';
|
||||
notifyParent:
|
||||
| 'KEY_RECEIVED'
|
||||
| 'done.invoke.store.resettingStorage:invocation[0]';
|
||||
setEncryptionKey: 'KEY_RECEIVED';
|
||||
};
|
||||
'eventsCausingDelays': {};
|
||||
'eventsCausingGuards': {};
|
||||
'eventsCausingServices': {
|
||||
clear: 'KEY_RECEIVED';
|
||||
generateEncryptionKey: 'ERROR';
|
||||
getEncryptionKey: 'xstate.init';
|
||||
store: 'KEY_RECEIVED' | 'done.invoke.store.resettingStorage:invocation[0]';
|
||||
};
|
||||
'matchesStates':
|
||||
| 'generatingEncryptionKey'
|
||||
| 'gettingEncryptionKey'
|
||||
| 'ready'
|
||||
| 'resettingStorage';
|
||||
'tags': never;
|
||||
}
|
||||
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'internalEvents': {
|
||||
|
||||
@@ -26,6 +26,7 @@ const model = createModel(
|
||||
STORE_RESPONSE: (response: unknown) => ({ response }),
|
||||
STORE_ERROR: (error: Error) => ({ error }),
|
||||
VC_ADDED: (vcKey: string) => ({ vcKey }),
|
||||
REMOVE_VC_FROM_CONTEXT: (vcKey: string) => ({ vcKey }),
|
||||
VC_RECEIVED: (vcKey: string) => ({ vcKey }),
|
||||
VC_DOWNLOADED: (vc: VC) => ({ vc }),
|
||||
REFRESH_MY_VCS: () => ({}),
|
||||
@@ -133,6 +134,9 @@ export const vcMachine =
|
||||
VC_ADDED: {
|
||||
actions: 'prependToMyVcs',
|
||||
},
|
||||
REMOVE_VC_FROM_CONTEXT: {
|
||||
actions: 'removeVcFromMyVcs',
|
||||
},
|
||||
VC_DOWNLOADED: {
|
||||
actions: 'setDownloadedVc',
|
||||
},
|
||||
@@ -185,6 +189,11 @@ export const vcMachine =
|
||||
myVcs: (context, event) => [event.vcKey, ...context.myVcs],
|
||||
}),
|
||||
|
||||
removeVcFromMyVcs: model.assign({
|
||||
myVcs: (context, event) =>
|
||||
context.myVcs.filter((vc: string) => !vc.includes(event.vcKey)),
|
||||
}),
|
||||
|
||||
prependToReceivedVcs: model.assign({
|
||||
receivedVcs: (context, event) => [
|
||||
event.vcKey,
|
||||
|
||||
@@ -53,6 +53,7 @@ const model = createModel(
|
||||
walletBindingError: '',
|
||||
publicKey: '',
|
||||
privateKey: '',
|
||||
storeError: null as Error,
|
||||
},
|
||||
{
|
||||
events: {
|
||||
@@ -75,6 +76,7 @@ const model = createModel(
|
||||
ADD_WALLET_BINDING_ID: () => ({}),
|
||||
CANCEL: () => ({}),
|
||||
CONFIRM: () => ({}),
|
||||
STORE_ERROR: (error: Error) => ({ error }),
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -183,14 +185,29 @@ export const vcItemMachine =
|
||||
},
|
||||
],
|
||||
CREDENTIAL_DOWNLOADED: {
|
||||
actions: [
|
||||
'setCredential',
|
||||
'storeContext',
|
||||
'updateVc',
|
||||
'logDownloaded',
|
||||
],
|
||||
actions: ['setCredential', 'storeContext'],
|
||||
},
|
||||
STORE_RESPONSE: {
|
||||
actions: ['updateVc', 'logDownloaded'],
|
||||
target: '#vc-item.checkingVerificationStatus',
|
||||
},
|
||||
STORE_ERROR: {
|
||||
target: '#vc-item.checkingServerData.savingFailed',
|
||||
},
|
||||
},
|
||||
},
|
||||
savingFailed: {
|
||||
entry: ['setStoreError', 'removeVcMetaDataFromStorage'],
|
||||
initial: 'idle',
|
||||
states: {
|
||||
idle: {},
|
||||
viewingVc: {},
|
||||
},
|
||||
on: {
|
||||
DISMISS: {
|
||||
actions: ['removeVcMetaDataFromVcMachine'],
|
||||
target: '.viewingVc',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -501,6 +518,36 @@ export const vcItemMachine =
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
setStoreError: assign({
|
||||
storeError: (_context, event) => event.error,
|
||||
}),
|
||||
|
||||
removeVcMetaDataFromStorage: send(
|
||||
(context) => {
|
||||
const { serviceRefs, ...data } = context;
|
||||
return StoreEvents.REMOVE_VC_METADATA(
|
||||
MY_VCS_STORE_KEY,
|
||||
VC_ITEM_STORE_KEY(context)
|
||||
);
|
||||
},
|
||||
{
|
||||
to: (context) => context.serviceRefs.store,
|
||||
}
|
||||
),
|
||||
|
||||
removeVcMetaDataFromVcMachine: send(
|
||||
(context, _event) => {
|
||||
const { serviceRefs, ...data } = context;
|
||||
return {
|
||||
type: 'REMOVE_VC_FROM_CONTEXT',
|
||||
vcKey: VC_ITEM_STORE_KEY(context),
|
||||
};
|
||||
},
|
||||
{
|
||||
to: (context) => context.serviceRefs.vc,
|
||||
}
|
||||
),
|
||||
|
||||
setWalletBindingError: assign({
|
||||
walletBindingError: (context, event) =>
|
||||
i18n.t(`errors.genericError`, {
|
||||
@@ -618,14 +665,16 @@ export const vcItemMachine =
|
||||
}),
|
||||
|
||||
logDownloaded: send(
|
||||
(_, event) =>
|
||||
ActivityLogEvents.LOG_ACTIVITY({
|
||||
_vcKey: VC_ITEM_STORE_KEY(event.vc),
|
||||
(context) => {
|
||||
const { serviceRefs, ...data } = context;
|
||||
return ActivityLogEvents.LOG_ACTIVITY({
|
||||
_vcKey: VC_ITEM_STORE_KEY(data),
|
||||
type: 'VC_DOWNLOADED',
|
||||
timestamp: Date.now(),
|
||||
deviceName: '',
|
||||
vcLabel: event.vc.tag || event.vc.id,
|
||||
}),
|
||||
vcLabel: data.tag || data.id,
|
||||
});
|
||||
},
|
||||
{
|
||||
to: (context) => context.serviceRefs.activityLog,
|
||||
}
|
||||
@@ -1083,3 +1132,11 @@ export function isWalletBindingInProgress(state: State) {
|
||||
export function isShowingBindingWarning(state: State) {
|
||||
return state.matches('showBindingWarning');
|
||||
}
|
||||
|
||||
export function selectStoreError(state: State) {
|
||||
return state.context.storeError;
|
||||
}
|
||||
|
||||
export function selectIsSavingFailedInIdle(state: State) {
|
||||
return state.matches('checkingServerData.savingFailed.idle');
|
||||
}
|
||||
|
||||
@@ -75,6 +75,10 @@ export interface Typegen0 {
|
||||
type: 'error.platform.vc-item.addingWalletBindingId:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'error.platform.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]': {
|
||||
type: 'error.platform.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'error.platform.vc-item.requestingBindingOtp:invocation[0]': {
|
||||
type: 'error.platform.vc-item.requestingBindingOtp:invocation[0]';
|
||||
data: unknown;
|
||||
@@ -141,6 +145,12 @@ export interface Typegen0 {
|
||||
incrementDownloadCounter: 'POLL';
|
||||
logDownloaded: 'CREDENTIAL_DOWNLOADED';
|
||||
logRevoked: 'STORE_RESPONSE';
|
||||
logWalletBindingFailure:
|
||||
| 'error.platform.vc-item.addKeyPair:invocation[0]'
|
||||
| 'error.platform.vc-item.addingWalletBindingId:invocation[0]'
|
||||
| 'error.platform.vc-item.requestingBindingOtp:invocation[0]'
|
||||
| 'error.platform.vc-item.updatingPrivateKey:invocation[0]';
|
||||
logWalletBindingSuccess: 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
|
||||
markVcValid: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
|
||||
requestStoredContext: 'GET_VC_RESPONSE' | 'REFRESH';
|
||||
requestVcContext: 'xstate.init';
|
||||
@@ -149,6 +159,7 @@ export interface Typegen0 {
|
||||
| 'CREDENTIAL_DOWNLOADED'
|
||||
| 'GET_VC_RESPONSE'
|
||||
| 'STORE_RESPONSE';
|
||||
setDownloadInterval: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
|
||||
setLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
|
||||
setMaxDownloadCount: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
|
||||
setOtp: 'INPUT_OTP';
|
||||
@@ -159,6 +170,7 @@ export interface Typegen0 {
|
||||
setPublicKey: 'done.invoke.vc-item.addKeyPair:invocation[0]';
|
||||
setRevoke: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
|
||||
setTag: 'SAVE_TAG';
|
||||
setThumbprintForWalletBindingId: 'done.invoke.vc-item.addingWalletBindingId:invocation[0]';
|
||||
setTransactionId:
|
||||
| 'INPUT_OTP'
|
||||
| 'REVOKE_VC'
|
||||
@@ -196,7 +208,9 @@ export interface Typegen0 {
|
||||
'eventsCausingServices': {
|
||||
addWalletBindnigId: 'done.invoke.vc-item.addKeyPair:invocation[0]';
|
||||
checkDownloadExpiryLimit: 'STORE_RESPONSE';
|
||||
checkStatus: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
|
||||
checkStatus:
|
||||
| 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]'
|
||||
| 'error.platform.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
|
||||
downloadCredential: 'DOWNLOAD_READY';
|
||||
generateKeyPair: 'INPUT_OTP';
|
||||
requestBindingOtp: 'CONFIRM';
|
||||
|
||||
@@ -12,10 +12,19 @@ import { OnboardingOverlay } from './OnboardingOverlay';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { VcItem } from '../../components/VcItem';
|
||||
import { GET_INDIVIDUAL_ID } from '../../shared/constants';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
|
||||
export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
|
||||
const { t } = useTranslation('MyVcsTab');
|
||||
const { t: commonTranslate } = useTranslation('common');
|
||||
const controller = useMyVcsTab(props);
|
||||
let storeErrorTranslationPath = 'errors.savingFailed';
|
||||
const isDiskFullError =
|
||||
controller.storeError?.message?.match('No space left on device') != null;
|
||||
|
||||
if (isDiskFullError) {
|
||||
storeErrorTranslationPath = 'errors.diskFullError';
|
||||
}
|
||||
|
||||
const getId = () => {
|
||||
controller.DISMISS();
|
||||
@@ -114,6 +123,18 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
|
||||
onDone={controller.ONBOARDING_DONE}
|
||||
onAddVc={controller.ADD_VC}
|
||||
/>
|
||||
<MessageOverlay
|
||||
isVisible={controller.isSavingFailedInIdle}
|
||||
title={commonTranslate(storeErrorTranslationPath + '.title', {
|
||||
vcLabelSingular: controller.vcLabel.singular,
|
||||
vcLabelPlural: controller.vcLabel.plural,
|
||||
})}
|
||||
message={commonTranslate(storeErrorTranslationPath + '.message', {
|
||||
vcLabelSingular: controller.vcLabel.singular,
|
||||
vcLabelPlural: controller.vcLabel.plural,
|
||||
})}
|
||||
onBackdropPress={controller.DISMISS}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
selectIsOnboarding,
|
||||
selectIsRequestSuccessful,
|
||||
selectGetVcModal,
|
||||
selectStoreError,
|
||||
selectIsSavingFailedInIdle,
|
||||
} from './MyVcsTabMachine';
|
||||
|
||||
export function useMyVcsTab(props: HomeScreenTabProps) {
|
||||
@@ -36,6 +38,8 @@ export function useMyVcsTab(props: HomeScreenTabProps) {
|
||||
isRefreshingVcs: useSelector(vcService, selectIsRefreshingMyVcs),
|
||||
isRequestSuccessful: useSelector(service, selectIsRequestSuccessful),
|
||||
isOnboarding: useSelector(service, selectIsOnboarding),
|
||||
storeError: useSelector(service, selectStoreError),
|
||||
isSavingFailedInIdle: useSelector(service, selectIsSavingFailedInIdle),
|
||||
|
||||
DISMISS: () => service.send(MyVcsTabEvents.DISMISS()),
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { GetVcModalMachine } from './MyVcs/GetVcModalMachine';
|
||||
const model = createModel(
|
||||
{
|
||||
serviceRefs: {} as AppServices,
|
||||
storeError: null as Error,
|
||||
},
|
||||
{
|
||||
events: {
|
||||
@@ -30,6 +31,7 @@ const model = createModel(
|
||||
}),
|
||||
DISMISS: () => ({}),
|
||||
STORE_RESPONSE: (response?: unknown) => ({ response }),
|
||||
STORE_ERROR: (error: Error) => ({ error }),
|
||||
ADD_VC: () => ({}),
|
||||
GET_VC: () => ({}),
|
||||
ONBOARDING_DONE: () => ({}),
|
||||
@@ -43,6 +45,7 @@ type ViewVcEvent = EventFrom<typeof model, 'VIEW_VC'>;
|
||||
|
||||
export const MyVcsTabMachine = model.createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QFkCeA1AxrAKgQwCMA6TACzEwGsBLAOygHlaCB7PAJwjqgGUAXPHwCusAMQ8cDAEoBRAPqyeABQYA5HjIDaABgC6iUAAcWsan2otaBkAA9EANgBMAGhCpEAFidEA7PY+O2j4ArPbajo4AjPYAvjGuaFi4hCTkVNxMrBxc9PyCIuKSsgoyymoampH6SCDGpuaW1nYIjgCcABxEwa0AzK32PU492l6tru4Ike2OXT2Rkdrac5GOPq3BcQkY2PjEllmc3KIAggAip3LoAMI61UYmZhZWNc2RrTPTSyPtPtphwR4fONEO12toiNEPO8PD0PFMej52psQIkdil9mxDvRRGoAEIMY5SU4ASVUAHE5Kc1Fo9NY6o9Gi9EG8PoFhh4fn9tACgW5EMFpl1uYEFt1Wj4QsjUcliNQIAAbMAnc6XG60mr0hrPUDNLw+LprAJvcVBJbAhA+Rz2Ig9EXrH5vdrvKXbGVEOWK0ToYkyADqqtudIeWqa-JcfJakT6vh80UR-g50R6LqSu3dCqVZJkOAD6vu9SeoZaayIgN67WCCO6flC5ui-SIf3ssfmXiWPhTaOIADdqGAAO7cLCiEk8ZDEng8QMa4OFpkIJzmwJOoiOJ32VqQysjWLxFGutN4CA5KDDiCWMDu2jdliUS-HY9YZAsCB4eXT-MM7W2fnhA2tI1WhNX4enNDpWlXADNz6ewBXsKNOzdI8T2HUdx0nD9alnRkdWZKNOnaQEKy8SIAmCRE60BSJfA3S1elhYIo13LZUxSZCh0wIhYD4Fh2COCRpHkRQVHUGk7iwgscJ-C1ohtDw23k94phCSigIhCVY2CcJIj8dZEMPY8OK4ni+OxATihkKQpGkTDNTnXCZOtWEFKhKIfmCOtBmojc2gWdp7HaW0Oz3aUDJQzjkKwHghEwTA4FgAAzIR5RHCd0KnPMJK-It5h6AiiIBeCyIoiNImCCJV0Y2MBlhcJK30lIYD4cx6DPC8rxvO8iDJMA+CfF831s7Dv2aVYILLPLKxCcVYPsMC-F8KCo36OCEJCg9Gt6lrT0wVKxwnDLxLsqTRpLCaKyrGba1K9pZLBCUdw8cjejiPdaBfOBrFCwgg0kkbEAAWkcDxzXIxbm2CSHuRg+r1tY4gyAoGh6EyTETzyYR4BnP6iwCSjglXH5-IGFZ7AGJYGr2Zg0e4X7svncjzSdfU5jBNcOVu7oPEp9NFTpkN5yjJYDVgwL-McdleQmKY-2B4Gyf8ALhg2OGuyIXsBw4-n7OkqZrUtH42mBsEvFBJcFh6G14I08V1h6ZNVaQwzWswbWTs8AF-0A4CzQjYiiE+KIVkBWDIh59iXaIfs8EeegADFeO7TAAGkwAmT8BYcrSZme73fhAusdxjMnAvKvw1yRR2wqM7jeNp7H6aznSvaiID8996WEWtbQyrWN5lnKlWWLViOdqISLMGi2L4qS+U3f+hBN31KZIbmeCnAWMZSvKmYfC8XzAqepjw+dsfYDwXt45jxUIHnosnQglfK2iaIom0LfpYr1cNy01ovh0gKJ9wpcQvtwOO19IC8zAHfec-ROiWjhN0YGQQIhS2ZFWGifxHQInkvbIBNdQFX2oDfdWfZBwuxgQ5ACy8KzP3Xm-D+zIYQeEqgxAUPRqwVh5k1baWBKHSWBnWf4NoJagn8mVTm3CtpGWjrHKACd2BJ1TunLKmdpKWjrD4aMawKzQVqo4WGcQgA */
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
tsTypes: {} as import('./MyVcsTabMachine.typegen').Typegen0,
|
||||
@@ -107,6 +110,22 @@ export const MyVcsTabMachine = model.createMachine(
|
||||
target: 'addVcSuccessful',
|
||||
actions: ['sendVcAdded'],
|
||||
},
|
||||
STORE_ERROR: {
|
||||
actions: 'setStoreError',
|
||||
target: '#MyVcsTab.addingVc.savingFailed',
|
||||
},
|
||||
},
|
||||
},
|
||||
savingFailed: {
|
||||
initial: 'idle',
|
||||
states: {
|
||||
idle: {},
|
||||
viewingVc: {},
|
||||
},
|
||||
on: {
|
||||
DISMISS: {
|
||||
target: '.viewingVc',
|
||||
},
|
||||
},
|
||||
},
|
||||
addVcSuccessful: {
|
||||
@@ -164,6 +183,10 @@ export const MyVcsTabMachine = model.createMachine(
|
||||
to: (context) => context.serviceRefs.vc,
|
||||
}
|
||||
),
|
||||
|
||||
setStoreError: model.assign({
|
||||
storeError: (_context, event) => event.error,
|
||||
}),
|
||||
},
|
||||
|
||||
guards: {
|
||||
@@ -198,3 +221,11 @@ export function selectIsOnboarding(state: State) {
|
||||
export function selectIsRequestSuccessful(state: State) {
|
||||
return state.matches('addingVc.addVcSuccessful');
|
||||
}
|
||||
|
||||
export function selectStoreError(state: State) {
|
||||
return state.context.storeError;
|
||||
}
|
||||
|
||||
export function selectIsSavingFailedInIdle(state: State) {
|
||||
return state.matches('addingVc.savingFailed.idle');
|
||||
}
|
||||
|
||||
@@ -18,25 +18,32 @@ export interface Typegen0 {
|
||||
'invokeSrcNameMap': {};
|
||||
'missingImplementations': {
|
||||
actions: never;
|
||||
services: never;
|
||||
guards: never;
|
||||
delays: never;
|
||||
guards: never;
|
||||
services: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
completeOnboarding: 'ADD_VC' | 'ONBOARDING_DONE';
|
||||
getOnboardingStatus: 'xstate.init';
|
||||
sendVcAdded: 'STORE_RESPONSE';
|
||||
setStoreError: 'STORE_ERROR';
|
||||
storeVcItem: 'done.invoke.AddVcModal';
|
||||
viewVcFromParent: 'VIEW_VC';
|
||||
};
|
||||
'eventsCausingServices': {};
|
||||
'eventsCausingDelays': {};
|
||||
'eventsCausingGuards': {
|
||||
isOnboardingDone: 'STORE_RESPONSE';
|
||||
};
|
||||
'eventsCausingDelays': {};
|
||||
'eventsCausingServices': {
|
||||
AddVcModal: 'ADD_VC' | 'done.invoke.GetVcModal';
|
||||
GetVcModal: 'GET_VC';
|
||||
};
|
||||
'matchesStates':
|
||||
| 'addingVc'
|
||||
| 'addingVc.addVcSuccessful'
|
||||
| 'addingVc.savingFailed'
|
||||
| 'addingVc.savingFailed.idle'
|
||||
| 'addingVc.savingFailed.viewingVc'
|
||||
| 'addingVc.storing'
|
||||
| 'addingVc.waitingForvcKey'
|
||||
| 'checkingOnboardingStatus'
|
||||
@@ -46,7 +53,12 @@ export interface Typegen0 {
|
||||
| 'onboarding'
|
||||
| 'viewingVc'
|
||||
| {
|
||||
addingVc?: 'addVcSuccessful' | 'storing' | 'waitingForvcKey';
|
||||
addingVc?:
|
||||
| 'addVcSuccessful'
|
||||
| 'savingFailed'
|
||||
| 'storing'
|
||||
| 'waitingForvcKey'
|
||||
| { savingFailed?: 'idle' | 'viewingVc' };
|
||||
gettingVc?: 'waitingForvcKey';
|
||||
};
|
||||
'tags': never;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { MMKVLoader } from 'react-native-mmkv-storage';
|
||||
import { VC_ITEM_STORE_KEY_REGEX } from './constants';
|
||||
import {
|
||||
DocumentDirectoryPath,
|
||||
exists,
|
||||
mkdir,
|
||||
readFile,
|
||||
unlink,
|
||||
writeFile,
|
||||
exists,
|
||||
} from 'react-native-fs';
|
||||
import { MMKVLoader } from 'react-native-mmkv-storage';
|
||||
import { VC_ITEM_STORE_KEY_REGEX } from './constants';
|
||||
|
||||
const MMKV = new MMKVLoader().initialize();
|
||||
const vcKeyRegExp = new RegExp(VC_ITEM_STORE_KEY_REGEX);
|
||||
@@ -23,12 +23,17 @@ class Storage {
|
||||
};
|
||||
|
||||
static setItem = async (key: string, data: string) => {
|
||||
if (vcKeyRegExp.exec(key)) {
|
||||
await mkdir(vcDirectoryPath);
|
||||
const path = getFilePath(key);
|
||||
return await writeFile(path, data, 'utf8');
|
||||
try {
|
||||
if (vcKeyRegExp.exec(key)) {
|
||||
await mkdir(vcDirectoryPath);
|
||||
const path = getFilePath(key);
|
||||
return await writeFile(path, data, 'utf8');
|
||||
}
|
||||
await MMKV.setItem(key, data);
|
||||
} catch (error) {
|
||||
console.log('Error Occurred while saving in Storage.', error);
|
||||
throw error;
|
||||
}
|
||||
await MMKV.setItem(key, data);
|
||||
};
|
||||
|
||||
static clear = async () => {
|
||||
|
||||
Reference in New Issue
Block a user