feat: download credentials from Esignet using openId4VCI (#851)

* feat(INJI-245): dowload and view card via issuers

Co-authored-by: Harsh Vardhan <harsh59v@gmail.com>

* fix(INJI-245): remove vc from wallet

Co-authored-by: Harsh Vardhan <harsh59v@gmail.com>

* feat(INJI-245): pin card downloaded via eSignet

* refactor(INJI-245): remove debug logs

* refactor(INJI-245): rename vcItem related component to ExistingVcItem

* refactor(INJI-245): add lock file modifications

* refactor(INJI-245): add styles in purple theme for issuer related components

* refactor(INJI-245): update VID for wallet binding usecase and issuer logo display in vc

* refactor(INJI-245): remove duplicate loader component

* refactor(INJI-245): remove unused props in vc details container

---------

Co-authored-by: Harsh Vardhan <harsh59v@gmail.com>
Co-authored-by: Vijay <94220135+vijay151096@users.noreply.github.com>
This commit is contained in:
KiruthikaJeyashankar
2023-09-22 17:22:59 +05:30
committed by GitHub
parent d71c34c569
commit 55c666b121
72 changed files with 7501 additions and 574 deletions

3
.env
View File

@@ -18,3 +18,6 @@ CREDENTIAL_REGISTRY_EDIT=true
#supported languages( en, fil, ar, hi, kn, ta)
APPLICATION_LANGUAGE=en
#Toggle for openID for VC
ENABLE_OPENID_FOR_VC=false

View File

@@ -121,7 +121,8 @@ android {
manifestPlaceholders = [
APP_NAME: APP_NAME_RELEASE,
GOOGLE_NEARBY_MESSAGES_API_KEY: "${properties.getProperty('GOOGLE_NEARBY_MESSAGES_API_KEY')}"
GOOGLE_NEARBY_MESSAGES_API_KEY: "${properties.getProperty('GOOGLE_NEARBY_MESSAGES_API_KEY')}",
appAuthRedirectScheme: 'io.mosip.residentapp.inji'
]
}
splits {

BIN
assets/digit-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -5,7 +5,7 @@ import {WalletBinding} from '../screens/Home/MyVcs/WalletBinding';
import {Pressable, View} from 'react-native';
import {useKebabPopUp} from './KebabPopUpController';
import {ActorRefFrom} from 'xstate';
import {vcItemMachine} from '../machines/vcItem';
import {ExistingMosipVCItemMachine} from '../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {useTranslation} from 'react-i18next';
import {HistoryTab} from '../screens/Home/MyVcs/HistoryTab';
import {RemoveVcWarningOverlay} from '../screens/Home/MyVcs/RemoveVcWarningOverlay';
@@ -96,5 +96,5 @@ export interface KebabPopUpProps {
vcMetadata: VCMetadata;
isVisible: boolean;
onDismiss: () => void;
service: ActorRefFrom<typeof vcItemMachine>;
service: ActorRefFrom<typeof ExistingMosipVCItemMachine>;
}

View File

@@ -1,5 +1,5 @@
import { useSelector } from '@xstate/react';
import { ActorRefFrom } from 'xstate';
import {useSelector} from '@xstate/react';
import {ActorRefFrom} from 'xstate';
import {
selectKebabPopUpWalletBindingInProgress,
selectKebabPopUp,
@@ -11,49 +11,60 @@ import {
selectOtpError,
selectShowWalletBindingError,
selectWalletBindingError,
VcItemEvents,
vcItemMachine,
ExistingMosipVCItemEvents,
ExistingMosipVCItemMachine,
selectShowActivities,
} from '../machines/vcItem';
import { selectActivities } from '../machines/activityLog';
import { GlobalContext } from '../shared/GlobalContext';
import { useContext } from 'react';
import { VCMetadata } from '../shared/VCMetadata';
} from '../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {selectActivities} from '../machines/activityLog';
import {GlobalContext} from '../shared/GlobalContext';
import {useContext} from 'react';
import {VCMetadata} from '../shared/VCMetadata';
import {
EsignetMosipVCItemEvents,
EsignetMosipVCItemMachine,
} from '../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
import {isVCFromOpenId4VCI} from '../shared/openId4VCI/Utils';
export function useKebabPopUp(props) {
const service = props.service as ActorRefFrom<typeof vcItemMachine>;
const PIN_CARD = () => service.send(VcItemEvents.PIN_CARD());
const KEBAB_POPUP = () => service.send(VcItemEvents.KEBAB_POPUP());
const service = props.service as
| ActorRefFrom<typeof ExistingMosipVCItemMachine>
| ActorRefFrom<typeof EsignetMosipVCItemMachine>;
const vcEvents =
props.vcKey !== undefined && isVCFromOpenId4VCI(props.vcMetadata)
? EsignetMosipVCItemEvents
: ExistingMosipVCItemEvents;
const PIN_CARD = () => service.send(vcEvents.PIN_CARD());
const KEBAB_POPUP = () => service.send(vcEvents.KEBAB_POPUP());
const ADD_WALLET_BINDING_ID = () =>
service.send(VcItemEvents.ADD_WALLET_BINDING_ID());
const CONFIRM = () => service.send(VcItemEvents.CONFIRM());
service.send(vcEvents.ADD_WALLET_BINDING_ID());
const CONFIRM = () => service.send(vcEvents.CONFIRM());
const REMOVE = (vcMetadata: VCMetadata) =>
service.send(VcItemEvents.REMOVE(vcMetadata));
const DISMISS = () => service.send(VcItemEvents.DISMISS());
const CANCEL = () => service.send(VcItemEvents.CANCEL());
const SHOW_ACTIVITY = () => service.send(VcItemEvents.SHOW_ACTIVITY());
const INPUT_OTP = (otp: string) => service.send(VcItemEvents.INPUT_OTP(otp));
service.send(vcEvents.REMOVE(vcMetadata));
const DISMISS = () => service.send(vcEvents.DISMISS());
const CANCEL = () => service.send(vcEvents.CANCEL());
const SHOW_ACTIVITY = () => service.send(vcEvents.SHOW_ACTIVITY());
const INPUT_OTP = (otp: string) => service.send(vcEvents.INPUT_OTP(otp));
const isPinned = useSelector(service, selectIsPinned);
const isBindingWarning = useSelector(service, selectKebabPopUpBindingWarning);
const isRemoveWalletWarning = useSelector(service, selectRemoveWalletWarning);
const isAcceptingOtpInput = useSelector(
service,
selectKebabPopUpAcceptingBindingOtp
selectKebabPopUpAcceptingBindingOtp,
);
const isWalletBindingError = useSelector(
service,
selectShowWalletBindingError
selectShowWalletBindingError,
);
const otpError = useSelector(service, selectOtpError);
const walletBindingError = useSelector(service, selectWalletBindingError);
const WalletBindingInProgress = useSelector(
service,
selectKebabPopUpWalletBindingInProgress
selectKebabPopUpWalletBindingInProgress,
);
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
const isKebabPopUp = useSelector(service, selectKebabPopUp);
const isShowActivities = useSelector(service, selectShowActivities);
const { appService } = useContext(GlobalContext);
const {appService} = useContext(GlobalContext);
const activityLogService = appService.children.get('activityLog');
return {

View File

@@ -0,0 +1,105 @@
import React, {useContext, useRef} from 'react';
import {useInterpret, useSelector} from '@xstate/react';
import {Pressable} from 'react-native';
import {ActorRefFrom} from 'xstate';
import {GlobalContext} from '../../../shared/GlobalContext';
import {logState} from '../../../machines/app';
import {Theme} from '../../ui/styleUtils';
import {Row} from '../../ui';
import {KebabPopUp} from '../../KebabPopUp';
import {VCMetadata} from '../../../shared/VCMetadata';
import {EsignetMosipVCItemContent} from './EsignetMosipVCItemContent';
import {EsignetMosipVCActivationStatus} from './EsignetMosipVCItemActivationStatus';
import {
EsignetMosipVCItemEvents,
EsignetMosipVCItemMachine,
createEsignetMosipVCItemMachine,
selectContext,
selectGeneratedOn,
selectKebabPopUp,
selectVerifiableCredentials,
} from '../../../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
export const EsignetMosipVCItem: React.FC<EsignetMosipVCItemProps> = props => {
const {appService} = useContext(GlobalContext);
const machine = useRef(
createEsignetMosipVCItemMachine(
appService.getSnapshot().context.serviceRefs,
props.vcMetadata,
),
);
const service = useInterpret(machine.current, {devTools: __DEV__});
service.subscribe(logState);
const context = useSelector(service, selectContext);
const isKebabPopUp = useSelector(service, selectKebabPopUp);
const DISMISS = () => service.send(EsignetMosipVCItemEvents.DISMISS());
const KEBAB_POPUP = () =>
service.send(EsignetMosipVCItemEvents.KEBAB_POPUP());
const credentials = useSelector(service, selectVerifiableCredentials);
const generatedOn = useSelector(service, selectGeneratedOn);
return (
<React.Fragment>
<Pressable
onPress={() => props.onPress(service)}
disabled={!credentials}
style={
props.selected
? Theme.Styles.selectedBindedVc
: Theme.Styles.closeCardBgContainer
}>
<EsignetMosipVCItemContent
context={context}
credential={credentials}
generatedOn={generatedOn}
selectable={props.selectable}
selected={props.selected}
service={service}
iconName={props.iconName}
iconType={props.iconType}
onPress={() => props.onPress(service)}
/>
{props.isSharingVc ? null : (
<Row crossAlign="center">
{props.activeTab !== 'receivedVcsTab' &&
props.activeTab != 'sharingVcScreen' && (
<EsignetMosipVCActivationStatus
verifiableCredential={credentials}
showOnlyBindedVc={props.showOnlyBindedVc}
emptyWalletBindingId
/>
)}
<Pressable onPress={KEBAB_POPUP}>
<KebabPopUp
testID="ellipsis"
vcMetadata={props.vcMetadata}
iconName="dots-three-horizontal"
iconType="entypo"
isVisible={isKebabPopUp}
onDismiss={DISMISS}
service={service}
/>
</Pressable>
</Row>
)}
</Pressable>
</React.Fragment>
);
};
export interface EsignetMosipVCItemProps {
vcMetadata: VCMetadata;
margin?: string;
selectable?: boolean;
selected?: boolean;
showOnlyBindedVc?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof EsignetMosipVCItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof EsignetMosipVCItemMachine>) => void;
activeTab?: string;
iconName?: string;
iconType?: string;
isSharingVc?: boolean;
}

View File

@@ -0,0 +1,130 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Dimensions} from 'react-native';
import {Icon} from 'react-native-elements';
import {Theme} from '../../ui/styleUtils';
import {Row, Text} from '../../ui';
import {VerifiableCredential} from './vc';
const WalletUnverifiedIcon: React.FC = () => {
return (
<Icon
name="shield-alert"
color={Theme.Colors.Icon}
size={28}
type="material-community"
containerStyle={{marginStart: 4, bottom: 1}}
/>
);
};
const WalletVerifiedIcon: React.FC = () => {
return (
<Icon
name="verified-user"
color={Theme.Colors.VerifiedIcon}
size={28}
containerStyle={{marginStart: 4, bottom: 1}}
/>
);
};
const WalletUnverifiedActivationDetails: React.FC<
WalletUnVerifiedDetailsProps
> = props => {
const {t} = useTranslation('VcDetails');
return (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row
crossAlign="center"
style={{
flex: 1,
}}>
{props.verifiableCredential && <WalletUnverifiedIcon />}
<Text
color={Theme.Colors.Details}
testID="activationPending"
weight="semibold"
size="small"
margin="10 33 10 10"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}>
{t('profileAuthenticated')}
</Text>
</Row>
</Row>
);
};
const WalletVerifiedActivationDetails: React.FC<
WalletVerifiedDetailsProps
> = props => {
const {t} = useTranslation('VcDetails');
return (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row
crossAlign="center"
style={{
flex: 1,
}}>
<WalletVerifiedIcon />
<Text
color={Theme.Colors.statusLabel}
testID="activated"
weight="semibold"
size="smaller"
margin="10 10 10 10"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{t('profileAuthenticated')}
</Text>
</Row>
</Row>
);
};
export const EsignetMosipVCActivationStatus: React.FC<
EsignetMosipVCActivationStatusProps
> = props => {
return (
<Row>
{props.emptyWalletBindingId ? (
<WalletUnverifiedActivationDetails
verifiableCredential={props.verifiableCredential}
/>
) : (
<WalletVerifiedActivationDetails
verifiableCredential={props.verifiableCredential}
showOnlyBindedVc={props.showOnlyBindedVc}
/>
)}
</Row>
);
};
export interface EsignetMosipVCActivationStatusProps {
showOnlyBindedVc: boolean;
verifiableCredential: VerifiableCredential;
emptyWalletBindingId: boolean;
}
interface WalletVerifiedDetailsProps {
showOnlyBindedVc: boolean;
verifiableCredential: VerifiableCredential;
}
interface WalletUnVerifiedDetailsProps {
verifiableCredential: VerifiableCredential;
}

View File

@@ -0,0 +1,201 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Image, ImageBackground} from 'react-native';
import {CheckBox, Icon} from 'react-native-elements';
import {Column, Row, Text} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import VerifiedIcon from '../../VerifiedIcon';
import {getLocalizedField} from '../../../i18n';
import {Credential, VerifiableCredential} from './vc';
import testIDProps from '../../../shared/commonUtil';
const getDetails = (arg1: string, arg2: string, credential: Credential) => {
if (arg1 === 'Status') {
return (
<Column>
<Text
testID="status"
weight="bold"
size="smaller"
color={
!credential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}>
{arg1}
</Text>
<Row>
<Text
testID="valid"
numLines={1}
color={Theme.Colors.Details}
weight="bold"
size="smaller"
style={
!credential ? Theme.Styles.loadingTitle : Theme.Styles.subtitle
}>
{!credential ? '' : arg2}
</Text>
{!credential ? null : <VerifiedIcon />}
</Row>
</Column>
);
} else {
return (
<Column>
<Text
color={
!credential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}
size="smaller"
weight={'bold'}
style={Theme.Styles.vcItemLabelHeader}>
{arg1}
</Text>
<Text
numLines={4}
color={Theme.Colors.Details}
weight="bold"
size="smaller"
style={
!credential ? Theme.Styles.loadingTitle : Theme.Styles.subtitle
}>
{!credential ? '' : arg2}
</Text>
</Column>
);
}
};
export const EsignetMosipVCItemContent: React.FC<
EsignetMosipVCItemContentProps
> = props => {
const fullName = !props.credential
? ''
: getLocalizedField(
props.credential?.credential.credentialSubject?.fullName,
);
const {t} = useTranslation('VcDetails');
const isvalid = !props.credential ? '' : t('valid');
const selectableOrCheck = props.selectable ? (
<CheckBox
checked={props.selected}
checkedIcon={<Icon name="radio-button-checked" />}
uncheckedIcon={<Icon name="radio-button-unchecked" />}
onPress={() => props.onPress()}
/>
) : null;
return (
<ImageBackground
source={!props.credential ? null : Theme.CloseCard}
resizeMode="stretch"
borderRadius={4}
style={
!props.credential
? Theme.Styles.vertloadingContainer
: Theme.Styles.backgroundImageContainer
}>
<Column>
<Row align="space-between">
<Row>
<ImageBackground
source={
!props.credential
? Theme.ProfileIcon
: {uri: props.credential?.credential.credentialSubject.face}
}
style={Theme.Styles.closeCardImage}>
{props.iconName && (
<Icon
{...testIDProps('pinIcon')}
name={props.iconName}
type={props.iconType}
color={Theme.Colors.Icon}
style={{marginLeft: -80}}
/>
)}
</ImageBackground>
<Column margin="0 0 0 10">
{getDetails(
t('fullName'),
fullName,
props.credential?.credential,
)}
<Column margin="10 0 0 0">
<Text
color={
!props.credential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}
weight="semibold"
size="smaller"
align="left">
{t('idType')}
</Text>
<Text
weight="semibold"
color={Theme.Colors.Details}
size="smaller"
style={
!props.credential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{t('nationalCard')}
</Text>
</Column>
</Column>
</Row>
<Column>{props.credential ? selectableOrCheck : null}</Column>
</Row>
<Row
align="space-between"
margin="5 0 0 0"
style={!props.credential ? Theme.Styles.loadingContainer : null}>
<Column>
{!props.credential
? getDetails(t('id'), 'newid', props.credential?.credential)
: null}
{getDetails(
t('generatedOn'),
props.generatedOn,
props.credential?.credential,
)}
</Column>
<Column>
{props.credential
? getDetails(t('status'), isvalid, props.credential?.credential)
: null}
</Column>
<Column
style={{display: props.credential?.credential ? 'flex' : 'none'}}>
<Image
src={props.credential?.issuerLogo}
style={Theme.Styles.issuerLogo}
resizeMethod="auto"
/>
</Column>
</Row>
</Column>
</ImageBackground>
);
};
interface EsignetMosipVCItemContentProps {
context: any;
credential: VerifiableCredential;
generatedOn: string;
selectable: boolean;
selected: boolean;
iconName?: string;
iconType?: string;
service: any;
onPress?: () => void;
}

View File

@@ -0,0 +1,401 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Button, Column, Row, Text} from '../../ui';
import {Image, ImageBackground, View} from 'react-native';
import {Theme} from '../../ui/styleUtils';
import {QrCodeOverlay} from '../../QrCodeOverlay';
import {getLocalizedField} from '../../../i18n';
import VerifiedIcon from '../../VerifiedIcon';
import {CREDENTIAL_REGISTRY_EDIT} from 'react-native-dotenv';
import {TextItem} from '../../ui/TextItem';
import {format, formatDistanceToNow, parse} from 'date-fns';
import DateFnsLocale from 'date-fns/locale';
import {Icon} from 'react-native-elements';
import {WalletBindingResponse} from '../../../shared/cryptoutil/cryptoUtil';
import {
CredentialSubject,
VCSharingReason,
VcIdType,
VerifiableCredential,
VerifiablePresentation,
} from './vc';
export const EsignetMosipVCItemDetails: React.FC<
EsignetMosipVCItemDetailsProps
> = props => {
const {t, i18n} = useTranslation('VcDetails');
if (props.vc?.verifiableCredential == null) {
return <Text align="center">Loading details...</Text>;
}
return (
<Column margin="10">
<ImageBackground
borderRadius={10}
style={Theme.Styles.openCardBgContainer}
source={Theme.OpenCard}>
<Row align="space-between">
<Column align="space-evenly" crossAlign="center">
<Image
source={
props.vc?.verifiableCredential.credential.credentialSubject
?.face
? {
uri: props.vc?.verifiableCredential.credential
.credentialSubject.face,
}
: Theme.ProfileIcon
}
style={Theme.Styles.openCardImage}
/>
<QrCodeOverlay
qrCodeDetailes={String(props.vc.verifiableCredential)}
/>
<Column margin="20 0 0 0">
<Image
src={props.vc.verifiableCredential.issuerLogo}
style={Theme.Styles.issuerLogo}
/>
</Column>
</Column>
<Column align="space-evenly">
<Column>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('fullName')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credential.credentialSubject
.fullName,
)}
</Text>
</Column>
<Row>
<Column>
<Column>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('idType')}
</Text>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.Details}>
{t('nationalCard')}
</Text>
</Column>
{props.vc?.verifiableCredential.credential.credentialSubject
.VID ? (
<Column margin="20 0 0 0">
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('vid')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{
props.vc?.verifiableCredential.credential
.credentialSubject.VID
}
</Text>
</Column>
) : null}
<Column margin="20 0 0 0">
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('dateOfBirth')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{format(
parse(
getLocalizedField(
props.vc?.verifiableCredential.credential
.credentialSubject.dateOfBirth,
),
'yyyy/MM/dd',
new Date(),
),
'yyy/MM/dd',
)}
</Text>
</Column>
</Column>
<Column margin="0 0 0 40">
<Column>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('gender')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credential
.credentialSubject.gender,
)}
</Text>
</Column>
<Column margin="20 0 0 0">
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('generatedOn')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{new Date(props.vc?.generatedOn).toLocaleDateString()}
</Text>
</Column>
<Column margin="20 0 0 0">
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('status')}
</Text>
<Row>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{t('valid')}
</Text>
{props.vc?.isVerified && <VerifiedIcon />}
</Row>
</Column>
<Column margin="20 0 0 0">
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('phoneNumber')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credential
.credentialSubject.phone,
)}
</Text>
</Column>
</Column>
</Row>
</Column>
</Row>
<View style={Theme.Styles.hrLine}></View>
<Column>
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('email')}
</Text>
<Row>
<Text
style={
props.vc?.verifiableCredential.credential.credentialSubject
.email.length > 25
? {flex: 1}
: {flex: 0}
}
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credential.credentialSubject
.email,
)}
</Text>
</Row>
</Column>
<Column style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('address')}
</Text>
<Row>
<Text
style={{flex: 1}}
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getFullAddress(
props.vc?.verifiableCredential.credential.credentialSubject,
)}
</Text>
</Row>
</Column>
{CREDENTIAL_REGISTRY_EDIT === 'true' && (
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('credentialRegistry')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{props.vc?.credentialRegistry}
</Text>
</Column>
)}
</Column>
</ImageBackground>
{props.vc?.reason?.length > 0 && (
<Text margin="24 24 16 24" weight="semibold">
{t('reasonForSharing')}
</Text>
)}
{props.vc?.reason?.map((reason, index) => (
<TextItem
key={index}
divider
label={formatDistanceToNow(reason.timestamp, {
addSuffix: true,
locale: DateFnsLocale[i18n.language],
})}
text={reason.message}
/>
))}
{props.activeTab !== 1 ? (
props.isBindingPending ? (
<Column style={Theme.Styles.openCardBgContainer}>
<Row margin={'0 0 5 0'} crossAlign={'center'}>
<Icon
name="shield-alert"
color={Theme.Colors.Icon}
size={30}
type="material-community"
/>
<Text
style={{flex: 1}}
weight="semibold"
size="small"
margin={'0 0 5 0'}
color={Theme.Colors.statusLabel}>
{t('offlineAuthDisabledHeader')}
</Text>
</Row>
<Text
style={{flex: 1}}
weight="regular"
size="small"
margin={'0 0 5 0'}
color={Theme.Colors.statusLabel}>
{t('offlineAuthDisabledMessage')}
</Text>
<Button
title={t('enableVerification')}
onPress={props.onBinding}
type="radius"
/>
</Column>
) : (
<Column style={Theme.Styles.openCardBgContainer}>
<Row crossAlign="center">
<Icon
name="verified-user"
color={Theme.Colors.VerifiedIcon}
size={28}
containerStyle={{marginStart: 4, bottom: 1}}
/>
<Text
numLines={1}
color={Theme.Colors.statusLabel}
weight="bold"
size="smaller"
margin="10 10 10 10"
children={t('profileAuthenticated')}></Text>
</Row>
</Column>
)
) : (
<></>
)}
</Column>
);
};
export interface EsignetMosipVCItemDetailsProps {
vc: VC;
isBindingPending: boolean;
onBinding?: () => void;
activeTab?: number;
}
export interface VC {
id: string;
idType: VcIdType;
tag: string;
verifiableCredential: VerifiableCredential;
verifiablePresentation?: VerifiablePresentation;
generatedOn: Date;
requestId: string;
isVerified: boolean;
lastVerifiedOn: number;
locked: boolean;
reason?: VCSharingReason[];
shouldVerifyPresence?: boolean;
walletBindingResponse?: WalletBindingResponse;
credentialRegistry: string;
isPinned?: boolean;
hashedId: string;
}
function getFullAddress(credential: CredentialSubject) {
if (!credential) {
return '';
}
const fields = [
'addressLine1',
'addressLine2',
'addressLine3',
'city',
'province',
'region',
];
return fields
.map(field => getLocalizedField(credential[field]))
.concat(credential.postalCode)
.filter(Boolean)
.join(', ');
}

View File

@@ -0,0 +1,117 @@
import {WalletBindingResponse} from '../../../shared/cryptoutil/cryptoUtil';
export interface VC {
id?: string;
idType?: VcIdType;
credential?: DecodedCredential;
verifiableCredential: VerifiableCredential;
verifiablePresentation?: VerifiablePresentation;
requestId?: string;
isVerified?: boolean;
lastVerifiedOn: number;
reason?: VCSharingReason[];
shouldVerifyPresence?: boolean;
walletBindingResponse?: WalletBindingResponse;
credentialRegistry?: string;
isPinned?: boolean;
hashedId?: string;
}
export interface VCSharingReason {
timestamp: number;
message: string;
}
export type VcIdType = 'UIN' | 'VID';
export interface DecodedCredential {
biometrics: {
face: string;
finger: {
left_thumb: string;
right_thumb: string;
};
};
}
export interface CredentialSubject {
//TODO: This should change to mandatory field if uin is also issued
UIN?: string;
VID?: string;
addressLine1: LocalizedField[] | string;
city: LocalizedField[] | string;
dateOfBirth: string;
email: string;
fullName: LocalizedField[] | string;
gender: LocalizedField[] | string;
id: string;
phone: string;
face: string;
}
type VCContext = (string | Record<string, unknown>)[];
export interface Credential {
'@context': VCContext;
credentialSubject: CredentialSubject;
id: string;
issuanceDate: string;
issuer: string;
proof: {
created: string;
jws: string;
proofPurpose: 'assertionMethod' | string;
type: 'RsaSignature2018' | string;
verificationMethod: string;
};
type: VerifiableCredentialType[];
}
export interface VerifiableCredential {
format: string;
credential: Credential;
identifier: string;
generatedOn: Date;
issuerLogo: string;
}
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'
| string;
export interface VCLabel {
singular: string;
plural: string;
}
export interface LocalizedField {
language: string;
value: string;
}
export interface linkTransactionResponse {
authFactors: Object[];
authorizeScopes: null;
clientName: string;
configs: {};
essentialClaims: string[];
linkTransactionId: string;
logoUrl: string;
voluntaryClaims: string[];
}

View File

@@ -3,32 +3,34 @@ import {useInterpret, useSelector} from '@xstate/react';
import {View, Pressable} from 'react-native';
import {ActorRefFrom} from 'xstate';
import {
createVcItemMachine,
createExistingMosipVCItemMachine,
selectVerifiableCredential,
selectGeneratedOn,
vcItemMachine,
ExistingMosipVCItemMachine,
selectContext,
selectTag,
selectEmptyWalletBindingId,
selectIsSavingFailedInIdle,
selectKebabPopUp,
} from '../machines/vcItem';
import {VcItemEvents} from '../machines/vcItem';
import {ErrorMessageOverlay} from './MessageOverlay';
import {Theme} from './ui/styleUtils';
import {GlobalContext} from '../shared/GlobalContext';
import {VcItemContent} from './VcItemContent';
import {VcItemActivationStatus} from './VcItemActivationStatus';
import {Row} from './ui';
import {KebabPopUp} from './KebabPopUp';
import {logState} from '../machines/app';
import {VCMetadata} from '../shared/VCMetadata';
} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {ExistingMosipVCItemEvents} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {ErrorMessageOverlay} from '../../MessageOverlay';
import {Theme} from '../../ui/styleUtils';
import {GlobalContext} from '../../../shared/GlobalContext';
import {ExistingMosipVCItemContent} from './ExistingMosipVCItemContent';
import {ExistingMosipVCItemActivationStatus} from './ExistingMosipVCItemActivationStatus';
import {Row} from '../../ui';
import {KebabPopUp} from '../../KebabPopUp';
import {logState} from '../../../machines/app';
import {VCMetadata} from '../../../shared/VCMetadata';
import {format} from 'date-fns';
export const VcItem: React.FC<VcItemProps> = props => {
export const ExistingMosipVCItem: React.FC<
ExistingMosipVCItemProps
> = props => {
const {appService} = useContext(GlobalContext);
const machine = useRef(
createVcItemMachine(
createExistingMosipVCItemMachine(
appService.getSnapshot().context.serviceRefs,
props.vcMetadata,
),
@@ -44,8 +46,9 @@ export const VcItem: React.FC<VcItemProps> = props => {
const verifiableCredential = useSelector(service, selectVerifiableCredential);
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
const isKebabPopUp = useSelector(service, selectKebabPopUp);
const DISMISS = () => service.send(VcItemEvents.DISMISS());
const KEBAB_POPUP = () => service.send(VcItemEvents.KEBAB_POPUP());
const DISMISS = () => service.send(ExistingMosipVCItemEvents.DISMISS());
const KEBAB_POPUP = () =>
service.send(ExistingMosipVCItemEvents.KEBAB_POPUP());
const isSavingFailedInIdle = useSelector(service, selectIsSavingFailedInIdle);
const storeErrorTranslationPath = 'errors.savingFailed';
@@ -64,7 +67,7 @@ export const VcItem: React.FC<VcItemProps> = props => {
? Theme.Styles.selectedBindedVc
: Theme.Styles.closeCardBgContainer
}>
<VcItemContent
<ExistingMosipVCItemContent
context={context}
verifiableCredential={verifiableCredential}
generatedOn={formattedDate}
@@ -81,7 +84,7 @@ export const VcItem: React.FC<VcItemProps> = props => {
<Row style={Theme.Styles.activationTab}>
{props.activeTab !== 'receivedVcsTab' &&
props.activeTab != 'sharingVcScreen' && (
<VcItemActivationStatus
<ExistingMosipVCItemActivationStatus
verifiableCredential={verifiableCredential}
emptyWalletBindingId={emptyWalletBindingId}
onPress={() => props.onPress(service)}
@@ -114,14 +117,14 @@ export const VcItem: React.FC<VcItemProps> = props => {
);
};
interface VcItemProps {
export interface ExistingMosipVCItemProps {
vcMetadata: VCMetadata;
margin?: string;
selectable?: boolean;
selected?: boolean;
showOnlyBindedVc?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onPress?: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
activeTab?: string;
iconName?: string;
iconType?: string;

View File

@@ -3,10 +3,10 @@ import {useTranslation} from 'react-i18next';
import {Dimensions} from 'react-native';
import {Icon} from 'react-native-elements';
import {ActorRefFrom} from 'xstate';
import {vcItemMachine} from '../machines/vcItem';
import {VerifiableCredential} from '../types/vc';
import {Row, Text} from './ui';
import {Theme} from './ui/styleUtils';
import {ExistingMosipVCItemMachine} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {VerifiableCredential} from '../../../types/vc';
import {Row, Text} from '../../ui';
import {Theme} from '../../ui/styleUtils';
const WalletUnverifiedIcon: React.FC = () => {
return (
@@ -94,8 +94,8 @@ const WalletVerifiedActivationDetails: React.FC<
);
};
export const VcItemActivationStatus: React.FC<
VcItemActivationStatusProps
export const ExistingMosipVCItemActivationStatus: React.FC<
ExistingMosipVCItemActivationStatusProps
> = props => {
return (
<Row margin="0 0 0 -6">
@@ -115,20 +115,20 @@ export const VcItemActivationStatus: React.FC<
);
};
interface VcItemActivationStatusProps {
interface ExistingMosipVCItemActivationStatusProps {
showOnlyBindedVc: boolean;
onPress: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onPress: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
verifiableCredential: VerifiableCredential;
emptyWalletBindingId: boolean;
}
interface WalletVerifiedDetailsProps {
showOnlyBindedVc: boolean;
onPress: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onPress: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
verifiableCredential: VerifiableCredential;
}
interface WalletUnVerifiedDetailsProps {
onPress: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onPress: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
verifiableCredential: VerifiableCredential;
}

View File

@@ -1,14 +1,14 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Image, ImageBackground, View} from 'react-native';
import {getLocalizedField} from '../i18n';
import {VerifiableCredential} from '../types/vc';
import {VcItemTags} from './VcItemTags';
import VerifiedIcon from './VerifiedIcon';
import {Column, Row, Text} from './ui';
import {Theme} from './ui/styleUtils';
import {getLocalizedField} from '../../../i18n';
import {VerifiableCredential} from '../../../types/vc';
import {VcItemTags} from '../../VcItemTags';
import VerifiedIcon from '../../VerifiedIcon';
import {Column, Row, Text} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import {CheckBox, Icon} from 'react-native-elements';
import testIDProps from '../shared/commonUtil';
import testIDProps from '../../../shared/commonUtil';
const getDetails = (arg1, arg2, verifiableCredential) => {
if (arg1 === 'Status') {
@@ -88,7 +88,9 @@ function getIdNumber(id: string) {
return '*'.repeat(id.length - 4) + id.slice(-4);
}
export const VcItemContent: React.FC<VcItemContentProps> = props => {
export const ExistingMosipVCItemContent: React.FC<
ExistingMosipVCItemContentProps
> = props => {
//Assigning the UIN and VID from the VC details to display the idtype label
const uin = props.verifiableCredential?.credentialSubject.UIN;
const vid = props.verifiableCredential?.credentialSubject.VID;
@@ -293,7 +295,7 @@ export const VcItemContent: React.FC<VcItemContentProps> = props => {
);
};
interface VcItemContentProps {
interface ExistingMosipVCItemContentProps {
context: any;
verifiableCredential: VerifiableCredential;
generatedOn: string;

View File

@@ -4,17 +4,19 @@ import * as DateFnsLocale from 'date-fns/locale';
import {useTranslation} from 'react-i18next';
import {Image, ImageBackground, View} from 'react-native';
import {Icon} from 'react-native-elements';
import {VC, CredentialSubject} from '../types/vc';
import {Button, Column, Row, Text} from './ui';
import {Theme} from './ui/styleUtils';
import {TextItem} from './ui/TextItem';
import {VcItemTags} from './VcItemTags';
import VerifiedIcon from './VerifiedIcon';
import {getLocalizedField} from '../i18n';
import {VC, CredentialSubject} from '../../../types/vc';
import {Button, Column, Row, Text} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import {TextItem} from '../../ui/TextItem';
import {VcItemTags} from '../../VcItemTags';
import VerifiedIcon from '../../VerifiedIcon';
import {getLocalizedField} from '../../../i18n';
import {CREDENTIAL_REGISTRY_EDIT} from 'react-native-dotenv';
import {QrCodeOverlay} from './QrCodeOverlay';
import {QrCodeOverlay} from '../../QrCodeOverlay';
export const VcDetails: React.FC<VcDetailsProps> = props => {
export const ExistingMosipVCItemDetails: React.FC<
ExistingMosipVCItemDetailsProps
> = props => {
const {t, i18n} = useTranslation('VcDetails');
//Assigning the UIN and VID from the VC details to display the idtype label
@@ -382,7 +384,7 @@ export const VcDetails: React.FC<VcDetailsProps> = props => {
);
};
interface VcDetailsProps {
export interface ExistingMosipVCItemDetailsProps {
vc: VC;
isBindingPending: boolean;
onBinding?: () => void;

View File

@@ -0,0 +1,19 @@
import React from 'react';
import {
EsignetMosipVCItemDetails,
EsignetMosipVCItemDetailsProps,
} from './EsignetMosipVCItem/EsignetMosipVCItemDetails';
import {VCMetadata} from '../../shared/VCMetadata';
import {
ExistingMosipVCItemDetails,
ExistingMosipVCItemDetailsProps,
} from './ExistingMosipVCItem/ExistingMosipVCItemDetails';
export const VcDetailsContainer: React.FC<
EsignetMosipVCItemDetailsProps | ExistingMosipVCItemDetailsProps
> = props => {
if (VCMetadata.fromVC(props.vc.vcMetadata).isFromOpenId4VCI()) {
return <EsignetMosipVCItemDetails {...props} />;
}
return <ExistingMosipVCItemDetails {...props} />;
};

View File

@@ -0,0 +1,18 @@
import {
EsignetMosipVCItem,
EsignetMosipVCItemProps,
} from './EsignetMosipVCItem/EsignetMosipVCItem';
import React from 'react';
import {
ExistingMosipVCItem,
ExistingMosipVCItemProps,
} from './ExistingMosipVCItem/ExistingMosipVCItem';
export const VcItemContainer: React.FC<
ExistingMosipVCItemProps | EsignetMosipVCItemProps
> = props => {
if (props.vcMetadata.isFromOpenId4VCI()) {
return <EsignetMosipVCItem {...props} />;
}
return <ExistingMosipVCItem {...props} />;
};

View File

@@ -1,30 +1,30 @@
import React, { useContext, useRef } from 'react';
import { useInterpret, useSelector } from '@xstate/react';
import { Pressable } from 'react-native';
import { CheckBox } from 'react-native-elements';
import React, {useContext, useRef} from 'react';
import {useInterpret, useSelector} from '@xstate/react';
import {Pressable} from 'react-native';
import {CheckBox} from 'react-native-elements';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { ActorRefFrom } from 'xstate';
import {ActorRefFrom} from 'xstate';
import {
createVcItemMachine,
createExistingMosipVCItemMachine,
selectVerifiableCredential,
selectGeneratedOn,
selectId,
vcItemMachine,
} from '../machines/vcItem';
import { Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils';
import { RotatingIcon } from './RotatingIcon';
import { GlobalContext } from '../shared/GlobalContext';
import { getLocalizedField } from '../i18n';
import { VCMetadata } from '../shared/VCMetadata';
ExistingMosipVCItemMachine,
} from '../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {Column, Row, Text} from './ui';
import {Theme} from './ui/styleUtils';
import {RotatingIcon} from './RotatingIcon';
import {GlobalContext} from '../shared/GlobalContext';
import {getLocalizedField} from '../i18n';
import {VCMetadata} from '../shared/VCMetadata';
export const VidItem: React.FC<VcItemProps> = (props) => {
const { appService } = useContext(GlobalContext);
export const VidItem: React.FC<VcItemProps> = props => {
const {appService} = useContext(GlobalContext);
const machine = useRef(
createVcItemMachine(
createExistingMosipVCItemMachine(
appService.getSnapshot().context.serviceRefs,
props.vcMetadata
)
props.vcMetadata,
),
);
const service = useInterpret(machine.current);
const uin = useSelector(service, selectId);
@@ -83,7 +83,7 @@ export const VidItem: React.FC<VcItemProps> = (props) => {
{!verifiableCredential
? ''
: getLocalizedField(
verifiableCredential.credentialSubject.fullName
verifiableCredential.credentialSubject.fullName,
) +
' · ' +
generatedOn}
@@ -104,5 +104,5 @@ interface VcItemProps {
margin?: string;
selectable?: boolean;
selected?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onPress?: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
}

View File

@@ -0,0 +1,66 @@
import React from 'react';
import {Image, Pressable} from 'react-native';
import {Theme} from '../ui/styleUtils';
import {useTranslation} from 'react-i18next';
import testIDProps from '../../shared/commonUtil';
import {Text} from '../ui/Text';
function isValidURL(urlString: string) {
const urlPattern = new RegExp(
`^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$`,
'i',
);
return !!urlPattern.test(urlString);
}
export const Issuer: React.FC<IssuerProps> = (props: IssuerProps) => {
/**
* This check is added since the logo for Donwload via UIN/VID is present in the repo where as
* other issuers has the logo url specfied in its data itself
*/
const {t} = useTranslation('IssuersScreen');
function getSource() {
if (isValidURL(props.logoUrl)) return {uri: props.logoUrl};
return props.logoUrl;
}
return (
<Pressable
{...testIDProps(props.testID)}
onPress={props.onPress}
style={({pressed}) =>
pressed
? [
Theme.issuersScreenStyles.issuerBoxContainerPressed,
Theme.Styles.boxShadow,
]
: [
Theme.issuersScreenStyles.issuerBoxContainer,
Theme.Styles.boxShadow,
]
}>
<Image
{...testIDProps('issuerIcon')}
style={Theme.issuersScreenStyles.issuerIcon}
source={getSource()}
/>
<Text testID="heading" style={Theme.issuersScreenStyles.issuerHeading}>
{t('itemHeading', {issuer: props.displayName})}
</Text>
<Text
testID="description"
style={Theme.issuersScreenStyles.issuerDescription}>
{t('itemSubHeading')}
</Text>
</Pressable>
);
};
interface IssuerProps {
id: string;
displayName: string;
logoUrl: string;
onPress: () => void;
testID: string;
}

View File

@@ -9,7 +9,8 @@ import {Theme, Spacing} from './styleUtils';
import testIDProps from '../../shared/commonUtil';
export const Button: React.FC<ButtonProps> = props => {
const type = props.type || 'solid' || 'radius' || 'gradient';
const type =
props.type || 'solid' || 'radius' || 'gradient' || 'clearAddIdBtnBg';
const buttonStyle: StyleProp<ViewStyle> = [
props.fill ? Theme.ButtonStyles.fill : null,
Theme.ButtonStyles[type],

75
components/ui/Error.tsx Normal file
View File

@@ -0,0 +1,75 @@
import {useFocusEffect} from '@react-navigation/native';
import React from 'react';
import {useTranslation} from 'react-i18next';
import {BackHandler, Dimensions, View} from 'react-native';
import {Button, Column, Row, Text} from '.';
import {Header} from './Header';
import {Theme} from './styleUtils';
import testIDProps from '../../shared/commonUtil';
export const Error: React.FC<ErrorProps> = props => {
const {t} = useTranslation('common');
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
props.goBack();
return true;
};
const disableBackHandler = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => disableBackHandler.remove();
}, []),
);
return (
<View
style={{
...Theme.ModalStyles.modal,
backgroundColor: Theme.Colors.whiteBackgroundColor,
}}
{...testIDProps(props.testID)}>
<Column fill safe>
{props.goBack && <Header testID="errorHeader" goBack={props.goBack} />}
<Column fill safe align="space-evenly">
<View style={{alignItems: 'center'}}>
<View>
<Row align="center" style={Theme.ErrorStyles.image}>
{props.image}
</Row>
<Text style={Theme.ErrorStyles.title} testID="errorTitle">
{props.title}
</Text>
<Text style={Theme.ErrorStyles.message} testID="errorMessage">
{props.message}
</Text>
</View>
{props.tryAgain && (
<Button
onPress={props.tryAgain}
width={Dimensions.get('screen').width * 0.46}
title={t('tryAgain')}
type="outline"
testID="tryAgain"
/>
)}
</View>
</Column>
</Column>
</View>
);
};
export interface ErrorProps {
isVisible: boolean;
title: string;
message: string;
image: React.ReactElement;
goBack: () => void;
tryAgain: null | (() => void);
testID: string;
}

54
components/ui/Header.tsx Normal file
View File

@@ -0,0 +1,54 @@
import React from 'react';
import {Text, TouchableOpacity, View} from 'react-native';
import {Icon} from 'react-native-elements';
import {Column, Row} from './Layout';
import {Theme} from './styleUtils';
import testIDProps from '../../shared/commonUtil';
export const Header: React.FC<HeaderProps> = ({goBack, title, testID}) => {
return (
<Column safe align="center" testID={testID}>
<Row elevation={2}>
<View
style={{
flex: 1,
flexDirection: 'row',
alignItems: 'center',
marginTop: 18,
marginBottom: 22,
marginVertical: 16,
}}>
<TouchableOpacity onPress={goBack} {...testIDProps('goBack')}>
<Icon
name="arrow-left"
type="material-community"
onPress={goBack}
containerStyle={{
...Theme.Styles.backArrowContainer,
marginLeft: 10,
}}
color={Theme.Colors.Icon}
/>
</TouchableOpacity>
<Row fill align={'center'}>
<Column>
<View style={{alignItems: 'center', marginLeft: -40}}>
<Text
style={Theme.TextStyles.semiBoldHeader}
{...testIDProps('title')}>
{title}
</Text>
</View>
</Column>
</Row>
</View>
</Row>
</Column>
);
};
interface HeaderProps {
title?: string;
goBack: () => void;
testID: string;
}

86
components/ui/Loader.tsx Normal file
View File

@@ -0,0 +1,86 @@
import React, {Fragment} from 'react';
import {useTranslation} from 'react-i18next';
import {Image, SafeAreaView, View} from 'react-native';
import Spinner from 'react-native-spinkit';
import {Button, Centered, Column, Row, Text} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
import testIDProps from '../../shared/commonUtil';
export const Loader: React.FC<LoaderProps> = props => {
const {t} = useTranslation('ScanScreen');
return (
<Fragment>
<Row elevation={3}>
<SafeAreaView style={Theme.ModalStyles.header}>
<Row
fill
align={'flex-start'}
style={Theme.LoaderStyles.titleContainer}>
<View style={Theme.issuersScreenStyles.loaderHeadingText}>
<Text style={Theme.TextStyles.header} testID="loaderTitle">
{props.title}
</Text>
{props.subTitle && (
<Text
style={Theme.TextStyles.subHeader}
color={Theme.Colors.profileValue}
testID="loaderSubTitle">
{props.subTitle}
</Text>
)}
</View>
</Row>
</SafeAreaView>
</Row>
<Centered crossAlign="center" fill>
<Column margin="24 0" align="space-around">
<Image
source={Theme.InjiProgressingLogo}
height={2}
width={2}
style={{marginLeft: -6}}
{...testIDProps('progressingLogo')}
/>
<View {...testIDProps('threeDotsLoader')}>
<Spinner
type="ThreeBounce"
color={Theme.Colors.Loading}
style={{marginLeft: 6}}
/>
</View>
</Column>
<Column style={{display: props.hint ? 'flex' : 'none'}}>
<Column style={Theme.SelectVcOverlayStyles.timeoutHintContainer}>
<Text
align="center"
color={Theme.Colors.TimoutText}
style={Theme.TextStyles.bold}>
{props.hint}
</Text>
{props.onCancel && (
<Button
type="clear"
title={t('common:cancel')}
onPress={props.onCancel}
/>
)}
</Column>
</Column>
</Centered>
</Fragment>
);
};
export interface LoaderProps {
isVisible: boolean;
title?: string;
subTitle?: string;
label?: string;
hint?: string;
onCancel?: () => void;
requester?: boolean;
progress?: boolean | number;
onBackdropPress?: () => void;
}

View File

@@ -1,5 +1,5 @@
/* eslint-disable sonarjs/no-duplicate-string */
import {Dimensions, StyleSheet, ViewStyle} from 'react-native';
import {Dimensions, Platform, StyleSheet, ViewStyle} from 'react-native';
import {Spacing} from '../styleUtils';
const Colors = {
@@ -17,6 +17,7 @@ const Colors = {
Orange: '#F2811D',
LightGrey: '#F7F7F7',
ShadeOfGrey: '#6F6F6F',
mediumDarkGrey: '#7B7B7B',
White: '#FFFFFF',
Red: '#D52929',
Green: '#4B9D20',
@@ -88,6 +89,7 @@ export const DefaultTheme = {
DefaultToggle: Colors.LightOrange,
ProfileIconBg: Colors.LightOrange,
GrayText: Colors.GrayText,
errorGrayText: Colors.mediumDarkGrey,
gradientBtn: ['#F59B4B', '#E86E04'],
dotColor: Colors.dorColor,
plainText: Colors.plainText,
@@ -324,6 +326,11 @@ export const DefaultTheme = {
width: 40,
marginRight: 4,
},
issuerLogo: {
resizeMode: 'contain',
aspectRatio: 1,
height: 60,
},
vcDetailsLogo: {
height: 35,
width: 90,
@@ -495,6 +502,20 @@ export const DefaultTheme = {
marginLeft: 10,
marginRight: 10,
},
downloadFabIcon: {
height: 70,
width: 70,
borderRadius: 200,
padding: 10,
backgroundColor: Colors.Orange,
shadowColor: '#000',
shadowOpacity: 0.4,
elevation: 5,
position: 'absolute',
bottom: Dimensions.get('window').width * 0.1,
right: Dimensions.get('window').width * 0.1,
},
boxShadow: generateBoxShadowStyle(),
}),
QrCodeStyles: StyleSheet.create({
magnifierZoom: {
@@ -570,6 +591,19 @@ export const DefaultTheme = {
lineHeight: 19,
paddingTop: 4,
},
subHeader: {
fontFamily: 'Inter_600SemiBold',
lineHeight: 19,
fontSize: 15,
paddingTop: 10,
},
semiBoldHeader: {
color: Colors.Black,
fontFamily: 'Inter_600SemiBold',
fontSize: 18,
lineHeight: 21,
paddingTop: 4,
},
retrieveIdLabel: {
color: Colors.ShadeOfGrey,
fontFamily: 'Inter_600SemiBold',
@@ -600,6 +634,12 @@ export const DefaultTheme = {
fontFamily: 'Inter_400Regular',
fontSize: 14,
},
regularGrey: {
fontFamily: 'Inter_400Regular',
fontSize: 15,
lineHeight: 19,
color: Colors.ShadeOfGrey,
},
semibold: {
fontFamily: 'Inter_600SemiBold',
fontSize: 15,
@@ -660,6 +700,12 @@ export const DefaultTheme = {
fontSize: 12,
},
}),
LoaderStyles: StyleSheet.create({
titleContainer: {
marginLeft: Dimensions.get('screen').width * 0.08,
marginVertical: Dimensions.get('screen').height * 0.025,
},
}),
ButtonStyles: StyleSheet.create({
fill: {
flex: 1,
@@ -778,6 +824,13 @@ export const DefaultTheme = {
width: Dimensions.get('screen').width,
height: Dimensions.get('screen').height,
},
header: {
flex: 1,
flexDirection: 'row',
alignItems: 'flex-start',
marginHorizontal: 18,
marginVertical: 8,
},
}),
UpdateModalStyles: StyleSheet.create({
modal: {
@@ -1039,6 +1092,85 @@ export const DefaultTheme = {
backgroundColor: Colors.Transparent,
},
}),
issuersScreenStyles: StyleSheet.create({
issuerListOuterContainer: {
padding: 10,
flex: 1,
backgroundColor: Colors.White,
},
issuersContainer: {marginHorizontal: 3},
issuerBoxContainer: {
margin: 5,
flex: 1,
padding: 10,
borderRadius: 6,
alignItems: 'flex-start',
justifyContent: 'space-evenly',
flexDirection: 'column',
paddingHorizontal: 6,
paddingVertical: 8,
backgroundColor: Colors.White,
},
issuerBoxContainerPressed: {
margin: 5,
flex: 1,
padding: 10,
borderRadius: 6,
alignItems: 'flex-start',
justifyContent: 'space-evenly',
flexDirection: 'column',
paddingHorizontal: 6,
paddingVertical: 8,
backgroundColor: Colors.Grey,
},
issuerHeading: {
fontFamily: 'Inter_600SemiBold',
fontSize: 14,
lineHeight: 17,
paddingHorizontal: 3,
paddingBottom: 4,
},
issuerDescription: {
fontSize: 11,
lineHeight: 14,
color: Colors.ShadeOfGrey,
paddingVertical: 5,
paddingHorizontal: 3,
},
issuerIcon: {
resizeMode: 'contain',
height: 33,
width: 32,
marginBottom: 9,
marginTop: 8,
marginLeft: 2.5,
},
loaderHeadingText: {
flex: 1,
flexDirection: 'column',
},
}),
ErrorStyles: StyleSheet.create({
image: {marginTop: -60, paddingBottom: 26},
title: {
color: Colors.Black,
fontFamily: 'Inter_600SemiBold',
fontSize: 18,
lineHeight: 21,
paddingTop: 4,
textAlign: 'center',
},
message: {
textAlign: 'center',
fontFamily: 'Inter_400Regular',
fontSize: 14,
lineHeight: 20,
marginTop: 6,
marginBottom: 25,
marginHorizontal: 40,
color: Colors.mediumDarkGrey,
},
}),
ICON_SMALL_SIZE: 16,
ICON_MID_SIZE: 22,
@@ -1070,6 +1202,9 @@ export const DefaultTheme = {
IntroScanner: require('../../../assets/intro-scanner.png'),
injiSmallLogo: require('../../../assets/inji_small_logo.png'),
protectPrivacy: require('../../../assets/phone_mockup_1.png'),
NoInternetConnection: require('../../../assets/no-internet-connection.png'),
SomethingWentWrong: require('../../../assets/something-went-wrong.png'),
DigitIcon: require('../../../assets/digit-icon.png'),
elevation(level: ElevationLevel): ViewStyle {
// https://ethercreative.github.io/react-native-shadow-generator/
@@ -1113,3 +1248,19 @@ export const DefaultTheme = {
};
},
};
function generateBoxShadowStyle() {
if (Platform.OS === 'ios') {
return {
shadowColor: '#000',
shadowOffset: {width: 1, height: 1.2},
shadowOpacity: 0.3,
shadowRadius: 2.5,
};
} else if (Platform.OS === 'android') {
return {
elevation: 4,
shadowColor: '#000',
};
}
}

View File

@@ -1,5 +1,5 @@
/* eslint-disable sonarjs/no-duplicate-string */
import {Dimensions, StyleSheet, ViewStyle} from 'react-native';
import {Dimensions, Platform, StyleSheet, ViewStyle} from 'react-native';
import {Spacing} from '../styleUtils';
const Colors = {
@@ -18,6 +18,7 @@ const Colors = {
LightOrange: '#FDF1E6',
LightGrey: '#FAF9FF',
ShadeOfGrey: '#6F6F6F',
mediumDarkGrey: '#7B7B7B',
White: '#FFFFFF',
Red: '#EB5757',
Green: '#219653',
@@ -58,6 +59,7 @@ export const PurpleTheme = {
borderBottomColor: Colors.Grey6,
whiteBackgroundColor: Colors.White,
lightGreyBackgroundColor: Colors.LightGrey,
errorGrayText: Colors.mediumDarkGrey,
profileLanguageValue: Colors.Grey,
aboutVersion: Colors.Gray40,
profileAuthFactorUnlock: Colors.Grey,
@@ -325,6 +327,11 @@ export const PurpleTheme = {
width: 40,
marginRight: 4,
},
issuerLogo: {
resizeMode: 'contain',
aspectRatio: 1,
height: 60,
},
vcDetailsLogo: {
height: 35,
width: 90,
@@ -496,6 +503,20 @@ export const PurpleTheme = {
marginLeft: 10,
marginRight: 10,
},
downloadFabIcon: {
height: 70,
width: 70,
borderRadius: 200,
padding: 10,
backgroundColor: Colors.Purple,
shadowColor: '#000',
shadowOpacity: 0.4,
elevation: 5,
position: 'absolute',
bottom: Dimensions.get('window').width * 0.1,
right: Dimensions.get('window').width * 0.1,
},
boxShadow: generateBoxShadowStyle(),
}),
QrCodeStyles: StyleSheet.create({
magnifierZoom: {
@@ -571,6 +592,19 @@ export const PurpleTheme = {
lineHeight: 19,
paddingTop: 4,
},
subHeader: {
fontFamily: 'Inter_600SemiBold',
lineHeight: 19,
fontSize: 15,
paddingTop: 10,
},
semiBoldHeader: {
color: Colors.Black,
fontFamily: 'Inter_600SemiBold',
fontSize: 18,
lineHeight: 21,
paddingTop: 4,
},
retrieveIdLabel: {
color: Colors.ShadeOfGrey,
fontFamily: 'Inter_600SemiBold',
@@ -601,6 +635,12 @@ export const PurpleTheme = {
fontFamily: 'Inter_400Regular',
fontSize: 14,
},
regularGrey: {
fontFamily: 'Inter_400Regular',
fontSize: 15,
lineHeight: 19,
color: Colors.ShadeOfGrey,
},
semibold: {
fontFamily: 'Inter_600SemiBold',
fontSize: 15,
@@ -661,6 +701,12 @@ export const PurpleTheme = {
fontSize: 12,
},
}),
LoaderStyles: StyleSheet.create({
titleContainer: {
marginLeft: Dimensions.get('screen').width * 0.08,
marginVertical: Dimensions.get('screen').height * 0.025,
},
}),
ButtonStyles: StyleSheet.create({
fill: {
flex: 1,
@@ -779,6 +825,13 @@ export const PurpleTheme = {
width: Dimensions.get('screen').width,
height: Dimensions.get('screen').height,
},
header: {
flex: 1,
flexDirection: 'row',
alignItems: 'flex-start',
marginHorizontal: 18,
marginVertical: 8,
},
}),
UpdateModalStyles: StyleSheet.create({
modal: {
@@ -1040,6 +1093,81 @@ export const PurpleTheme = {
backgroundColor: Colors.Transparent,
},
}),
issuersScreenStyles: StyleSheet.create({
issuerListOuterContainer: {
padding: 10,
flex: 1,
backgroundColor: Colors.White,
},
issuersContainer: {marginHorizontal: 3},
issuerBoxContainer: {
margin: 5,
flex: 1,
padding: 10,
borderRadius: 6,
alignItems: 'flex-start',
justifyContent: 'space-evenly',
flexDirection: 'column',
paddingHorizontal: 6,
paddingVertical: 8,
backgroundColor: Colors.White,
},
issuerBoxContainerPressed: {
margin: 5,
flex: 1,
padding: 10,
borderRadius: 6,
alignItems: 'flex-start',
justifyContent: 'space-evenly',
flexDirection: 'column',
paddingHorizontal: 6,
paddingVertical: 8,
backgroundColor: Colors.Grey,
},
issuerHeading: {
fontFamily: 'Inter_600SemiBold',
fontSize: 14,
lineHeight: 17,
},
issuerDescription: {
fontSize: 11,
lineHeight: 14,
color: Colors.ShadeOfGrey,
},
issuerIcon: {
resizeMode: 'contain',
height: 33,
width: 32,
marginBottom: 9,
marginTop: 8,
marginLeft: 2.5,
},
loaderHeadingText: {
flex: 1,
flexDirection: 'column',
},
}),
ErrorStyles: StyleSheet.create({
image: {marginTop: -60, paddingBottom: 26},
title: {
color: Colors.Black,
fontFamily: 'Inter_600SemiBold',
fontSize: 18,
lineHeight: 21,
paddingTop: 4,
textAlign: 'center',
},
message: {
textAlign: 'center',
fontFamily: 'Inter_400Regular',
fontSize: 14,
lineHeight: 20,
marginTop: 6,
marginBottom: 25,
marginHorizontal: 40,
color: Colors.mediumDarkGrey,
},
}),
ICON_SMALL_SIZE: 16,
ICON_MID_SIZE: 22,
PinIcon: require('../../../assets/pin_icon.png'),
@@ -1070,6 +1198,9 @@ export const PurpleTheme = {
IntroScanner: require('../../../assets/intro-scanner.png'),
injiSmallLogo: require('../../../assets/inji_small_logo.png'),
protectPrivacy: require('../../../assets/phone_mockup_1.png'),
NoInternetConnection: require('../../../assets/no-internet-connection.png'),
SomethingWentWrong: require('../../../assets/something-went-wrong.png'),
DigitIcon: require('../../../assets/digit-icon.png'),
elevation(level: ElevationLevel): ViewStyle {
// https://ethercreative.github.io/react-native-shadow-generator/
@@ -1113,3 +1244,19 @@ export const PurpleTheme = {
};
},
};
function generateBoxShadowStyle() {
if (Platform.OS === 'ios') {
return {
shadowColor: '#000',
shadowOffset: {width: 1, height: 1.2},
shadowOpacity: 0.3,
shadowRadius: 2.5,
};
} else if (Platform.OS === 'android') {
return {
elevation: 4,
shadowColor: '#000',
};
}
}

View File

@@ -1,7 +1,12 @@
#import <RCTAppDelegate.h>
#import <Foundation/Foundation.h>
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
#import "RNAppAuthAuthorizationFlowManager.h"
#import <Expo/Expo.h>
@interface AppDelegate : EXAppDelegateWrapper
@interface AppDelegate : EXAppDelegateWrapper <UIApplicationDelegate, RCTBridgeDelegate, RNAppAuthAuthorizationFlowManager>
@end
@property(nonatomic, weak)id<RNAppAuthAuthorizationFlowManagerDelegate>authorizationFlowManagerDelegate;
@end

View File

@@ -1,4 +1,10 @@
PODS:
- AppAuth (1.6.2):
- AppAuth/Core (= 1.6.2)
- AppAuth/ExternalUserAgent (= 1.6.2)
- AppAuth/Core (1.6.2)
- AppAuth/ExternalUserAgent (1.6.2):
- AppAuth/Core
- ASN1Decoder (1.8.0)
- boost (1.76.0)
- BVLinearGradient (2.8.2):
@@ -345,6 +351,9 @@ PODS:
- React-jsinspector (0.71.8)
- React-logger (0.71.8):
- glog
- react-native-app-auth (7.0.0):
- AppAuth (~> 1.6)
- React-Core
- react-native-location (2.5.0):
- React
- react-native-mmkv-storage (0.9.1):
@@ -366,6 +375,8 @@ PODS:
- React-Core
- react-native-secure-keystore (0.1.1):
- React-Core
- react-native-spinkit (1.4.1):
- React
- react-native-tuvali (0.4.4):
- CrcSwift (~> 0.0.3)
- GzipSwift
@@ -471,6 +482,8 @@ PODS:
- React-Core
- RNKeychain (8.0.0):
- React-Core
- RNLocalize (3.0.2):
- React-Core
- RNPermissions (3.8.4):
- React-Core
- RNScreens (3.20.0):
@@ -538,6 +551,7 @@ DEPENDENCIES:
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- react-native-app-auth (from `../node_modules/react-native-app-auth`)
- react-native-location (from `../node_modules/react-native-location`)
- react-native-mmkv-storage (from `../node_modules/react-native-mmkv-storage`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
@@ -546,6 +560,7 @@ DEPENDENCIES:
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-secure-key-store (from `../node_modules/react-native-secure-key-store`)
- react-native-secure-keystore (from `../node_modules/react-native-secure-keystore`)
- react-native-spinkit (from `../node_modules/react-native-spinkit`)
- react-native-tuvali (from `../node_modules/react-native-tuvali`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
@@ -568,6 +583,7 @@ DEPENDENCIES:
- RNFS (from `../node_modules/react-native-fs`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNKeychain (from `../node_modules/react-native-keychain`)
- RNLocalize (from `../node_modules/react-native-localize`)
- RNPermissions (from `../node_modules/react-native-permissions`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNSecureRandom (from `../node_modules/react-native-securerandom`)
@@ -577,6 +593,7 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- AppAuth
- ASN1Decoder
- CatCrypto
- CrcSwift
@@ -683,6 +700,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
React-logger:
:path: "../node_modules/react-native/ReactCommon/logger"
react-native-app-auth:
:path: "../node_modules/react-native-app-auth"
react-native-location:
:path: "../node_modules/react-native-location"
react-native-mmkv-storage:
@@ -699,6 +718,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-secure-key-store"
react-native-secure-keystore:
:path: "../node_modules/react-native-secure-keystore"
react-native-spinkit:
:path: "../node_modules/react-native-spinkit"
react-native-tuvali:
:path: "../node_modules/react-native-tuvali"
React-perflogger:
@@ -743,6 +764,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-gesture-handler"
RNKeychain:
:path: "../node_modules/react-native-keychain"
RNLocalize:
:path: "../node_modules/react-native-localize"
RNPermissions:
:path: "../node_modules/react-native-permissions"
RNScreens:
@@ -757,12 +780,13 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
ASN1Decoder: 6110fdeacfdb41559b1481457a1645be716610aa
boost: 57d2868c099736d80fcd648bf211b4431e51a558
BVLinearGradient: 916632041121a658c704df89d99f04acb038de0f
CatCrypto: a477899b6be4954e75be4897e732da098cc0a5a8
CrcSwift: f85dea6b41dddb5f98bb3743fd777ce58b77bc2e
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
EASClient: 950674e1098ebc09c4c2cf064a61e42e84d9d4c6
EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903
EXBarCodeScanner: 8e23fae8d267dbef9f04817833a494200f1fce35
@@ -785,7 +809,7 @@ SPEC CHECKSUMS:
FBLazyVector: f637f31eacba90d4fdeff3fa41608b8f361c173b
FBReactNativeSpec: 0d9a4f4de7ab614c49e98c00aedfd3bfbda33d59
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
GoogleInterchangeUtilities: d5bc4d88d5b661ab72f9d70c58d02ca8c27ad1f7
GoogleNetworkingUtilities: 3edd3a8161347494f2da60ea0deddc8a472d94cb
GoogleSymbolUtilities: 631ee17048aa5e9ab133470d768ea997a5ef9b96
@@ -815,6 +839,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: 747911ab5921641b4ed7e4900065896597142125
React-jsinspector: c712f9e3bb9ba4122d6b82b4f906448b8a281580
React-logger: 342f358b8decfbf8f272367f4eacf4b6154061be
react-native-app-auth: 1d12b6874a24152715a381d8e9149398ce7c2c95
react-native-location: 5a40ec1cc6abf2f6d94df979f98ec76c3a415681
react-native-mmkv-storage: cfb6854594cfdc5f7383a9e464bb025417d1721c
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
@@ -823,6 +848,7 @@ SPEC CHECKSUMS:
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
react-native-secure-key-store: 910e6df6bc33cb790aba6ee24bc7818df1fe5898
react-native-secure-keystore: d9e791a495d23c33db7711732f3a761533b882db
react-native-spinkit: da294fd828216ad211fe36a5c14c1e09f09e62db
react-native-tuvali: 6c4660b1d72c8d049ea8f6470cc67aa22c89b40d
React-perflogger: d21f182895de9d1b077f8a3cd00011095c8c9100
React-RCTActionSheet: 0151f83ef92d2a7139bba7dfdbc8066632a6d47b
@@ -845,6 +871,7 @@ SPEC CHECKSUMS:
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94
RNLocalize: dbea38dcb344bf80ff18a1757b1becf11f70cae4
RNPermissions: f1b49dd05fa9b83993cd05a9ee115247944d8f1a
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
@@ -855,4 +882,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 01f58b130fa221dabb14b2d82d981ef24dcaba53
COCOAPODS: 1.11.3
COCOAPODS: 1.12.1

View File

@@ -157,6 +157,34 @@
"version": "Version",
"tuvaliVersion": "Tuvali-version"
},
"IssuersScreen": {
"title": "Add new card",
"header": "Please select a preferred method from below to add a new card",
"itemHeading":"Download via {{issuer}}",
"itemSubHeading": "Enter the mentioned ID and get your card",
"modal": {
"title": "In Progress",
"hint": "downloading your credential from issuer"
},
"loaders": {
"loading": "Loading...",
"subTitle": {
"displayIssuers": "Fetching Issuers",
"settingUp": "Setting up",
"downloadingCredentials": "Downloading Credentials"
}
},
"errors": {
"noInternetConnection": {
"title": "No internet connection",
"message": "Please connect with internet and retry."
},
"generic": {
"title": "Something went wrong!",
"message": "Our experts are working hard to make things working again."
}
}
},
"HelpScreen": {
"header": "Help",
"whatIsDigitalCredential?": "What is a digital credential?",

View File

@@ -0,0 +1,782 @@
import {assign, ErrorPlatformEvent, EventFrom, send, StateFrom} from 'xstate';
import {createModel} from 'xstate/lib/model';
import {AppServices} from '../../../shared/GlobalContext';
import {VCMetadata} from '../../../shared/VCMetadata';
import {VC} from '../../../types/vc';
import {
generateKeys,
isCustomSecureKeystore,
WalletBindingResponse,
} from '../../../shared/cryptoutil/cryptoUtil';
import {log} from 'xstate/lib/actions';
import {OpenId4VCIProtocol} from '../../../shared/openId4VCI/Utils';
import {StoreEvents} from '../../../machines/store';
import {MIMOTO_BASE_URL, MY_VCS_STORE_KEY} from '../../../shared/constants';
import {VcEvents} from '../../../machines/vc';
import i18n from '../../../i18n';
import {KeyPair} from 'react-native-rsa-native';
import {
getBindingCertificateConstant,
savePrivateKey,
} from '../../../shared/keystore/SecureKeystore';
import {ActivityLogEvents} from '../../../machines/activityLog';
import {request} from '../../../shared/request';
import SecureKeystore from 'react-native-secure-keystore';
import {VerifiableCredential} from './vc';
const model = createModel(
{
serviceRefs: {} as AppServices,
vcMetadata: {} as VCMetadata,
generatedOn: new Date() as Date,
verifiableCredential: null as VerifiableCredential,
isPinned: false,
hashedId: '',
publicKey: '',
privateKey: '',
myVcs: [] as string[],
otp: '',
otpError: '',
idError: '',
transactionId: '',
bindingTransactionId: '',
walletBindingResponse: null as WalletBindingResponse,
walletBindingError: '',
},
{
events: {
KEY_RECEIVED: (key: string) => ({key}),
KEY_ERROR: (error: Error) => ({error}),
STORE_READY: () => ({}),
DISMISS: () => ({}),
CREDENTIAL_DOWNLOADED: (vc: VC) => ({vc}),
STORE_RESPONSE: (response: VC) => ({response}),
POLL: () => ({}),
DOWNLOAD_READY: () => ({}),
GET_VC_RESPONSE: (vc: VC) => ({vc}),
VERIFY: () => ({}),
LOCK_VC: () => ({}),
INPUT_OTP: (otp: string) => ({otp}),
REFRESH: () => ({}),
REVOKE_VC: () => ({}),
ADD_WALLET_BINDING_ID: () => ({}),
CANCEL: () => ({}),
CONFIRM: () => ({}),
STORE_ERROR: (error: Error) => ({error}),
PIN_CARD: () => ({}),
KEBAB_POPUP: () => ({}),
SHOW_ACTIVITY: () => ({}),
REMOVE: (vcMetadata: VCMetadata) => ({vcMetadata}),
},
},
);
export const EsignetMosipVCItemEvents = model.events;
export const EsignetMosipVCItemMachine = model.createMachine(
{
predictableActionArguments: true,
preserveActionOrder: true,
tsTypes: {} as import('./EsignetMosipVCItemMachine.typegen').Typegen0,
schema: {
context: model.initialContext,
events: {} as EventFrom<typeof model>,
},
on: {
REFRESH: {
target: '.checkingStore',
},
},
description: 'VC',
id: 'vc-item-openid4vci',
initial: 'checkingVc',
states: {
checkingVc: {
entry: ['requestVcContext'],
description: 'Fetch the VC data from the Memory.',
on: {
GET_VC_RESPONSE: [
{
actions: ['setVerifiableCredential', 'setGeneratedOn'],
cond: 'hasCredential',
target: 'idle',
},
{
target: 'checkingStore',
},
],
},
},
checkingStore: {
entry: 'requestStoredContext',
description: 'Check if VC data is in secured local storage.',
on: {
STORE_RESPONSE: {
actions: ['setVerifiableCredential', 'setGeneratedOn', 'updateVc'],
target: 'idle',
},
},
},
showBindingWarning: {
on: {
CONFIRM: {
target: 'requestingBindingOtp',
},
CANCEL: {
target: 'idle',
},
},
},
requestingBindingOtp: {
invoke: {
src: 'requestBindingOtp',
onDone: [
{
target: 'acceptingBindingOtp',
},
],
onError: [
{
actions: ['setWalletBindingError', 'logWalletBindingFailure'],
target: 'showingWalletBindingError',
},
],
},
},
showingWalletBindingError: {
on: {
CANCEL: {
target: 'idle',
actions: 'setWalletBindingErrorEmpty',
},
},
},
acceptingBindingOtp: {
entry: ['clearOtp'],
on: {
INPUT_OTP: {
target: 'addKeyPair',
actions: ['setOtp'],
},
DISMISS: {
target: 'idle',
actions: ['clearOtp', 'clearTransactionId'],
},
},
},
addKeyPair: {
invoke: {
src: 'generateKeyPair',
onDone: [
{
cond: 'isCustomSecureKeystore',
target: 'addingWalletBindingId',
actions: ['setPublicKey'],
},
{
target: 'addingWalletBindingId',
actions: ['setPublicKey', 'setPrivateKey'],
},
],
onError: [
{
actions: ['setWalletBindingError', 'logWalletBindingFailure'],
target: 'showingWalletBindingError',
},
],
},
},
addingWalletBindingId: {
invoke: {
src: 'addWalletBindnigId',
onDone: [
{
cond: 'isCustomSecureKeystore',
target: 'idle',
actions: [
'setWalletBindingId',
'setThumbprintForWalletBindingId',
'storeContext',
'updateVc',
'setWalletBindingErrorEmpty',
'logWalletBindingSuccess',
],
},
{
target: 'updatingPrivateKey',
actions: [
'setWalletBindingId',
'setThumbprintForWalletBindingId',
],
},
],
onError: [
{
actions: ['setWalletBindingError', 'logWalletBindingFailure'],
target: 'showingWalletBindingError',
},
],
},
},
updatingPrivateKey: {
invoke: {
src: 'updatePrivateKey',
onDone: {
actions: [
'storeContext',
'updatePrivateKey',
'updateVc',
'setWalletBindingErrorEmpty',
'logWalletBindingSuccess',
],
target: 'idle',
},
onError: {
actions: ['setWalletBindingError', 'logWalletBindingFailure'],
target: 'showingWalletBindingError',
},
},
},
idle: {
on: {
DISMISS: {
target: 'checkingVc',
},
KEBAB_POPUP: {
target: 'kebabPopUp',
},
ADD_WALLET_BINDING_ID: {
target: 'showBindingWarning',
},
PIN_CARD: {
target: 'pinCard',
actions: 'setPinCard',
},
},
},
pinCard: {
entry: 'storeContext',
on: {
STORE_RESPONSE: {
actions: ['sendVcUpdated', 'VcUpdated'],
target: 'idle',
},
},
},
kebabPopUp: {
on: {
DISMISS: {
target: 'idle',
},
ADD_WALLET_BINDING_ID: {
target: '#vc-item-openid4vci.kebabPopUp.showBindingWarning',
},
PIN_CARD: {
target: '#vc-item-openid4vci.pinCard',
actions: 'setPinCard',
},
SHOW_ACTIVITY: {
target: '#vc-item-openid4vci.kebabPopUp.showActivities',
},
REMOVE: {
actions: 'setVcKey',
target: '#vc-item-openid4vci.kebabPopUp.removeWallet',
},
},
initial: 'idle',
states: {
idle: {},
showBindingWarning: {
on: {
CONFIRM: {
target: '#vc-item-openid4vci.kebabPopUp.requestingBindingOtp',
},
CANCEL: {
target: '#vc-item-openid4vci.kebabPopUp',
},
},
},
requestingBindingOtp: {
invoke: {
src: 'requestBindingOtp',
onDone: [
{
target: '#vc-item-openid4vci.kebabPopUp.acceptingBindingOtp',
actions: [log('accceptingOTP')],
},
],
onError: [
{
actions: 'setWalletBindingError',
target:
'#vc-item-openid4vci.kebabPopUp.showingWalletBindingError',
},
],
},
},
showingWalletBindingError: {
on: {
CANCEL: {
target: '#vc-item-openid4vci.kebabPopUp',
actions: 'setWalletBindingErrorEmpty',
},
},
},
acceptingBindingOtp: {
entry: ['clearOtp'],
on: {
INPUT_OTP: {
target: '#vc-item-openid4vci.kebabPopUp.addKeyPair',
actions: ['setOtp'],
},
DISMISS: {
target: '#vc-item-openid4vci.kebabPopUp',
actions: ['clearOtp', 'clearTransactionId'],
},
},
},
addKeyPair: {
invoke: {
src: 'generateKeyPair',
onDone: [
{
cond: 'isCustomSecureKeystore',
target:
'#vc-item-openid4vci.kebabPopUp.addingWalletBindingId',
actions: ['setPublicKey'],
},
{
target:
'#vc-item-openid4vci.kebabPopUp.addingWalletBindingId',
actions: ['setPublicKey', 'setPrivateKey'],
},
],
onError: [
{
actions: 'setWalletBindingError',
target:
'#vc-item-openid4vci.kebabPopUp.showingWalletBindingError',
},
],
},
},
addingWalletBindingId: {
invoke: {
src: 'addWalletBindnigId',
onDone: [
{
cond: 'isCustomSecureKeystore',
target: '#vc-item-openid4vci.kebabPopUp',
actions: [
'setWalletBindingId',
'setThumbprintForWalletBindingId',
'storeContext',
'updateVc',
'setWalletBindingErrorEmpty',
'logWalletBindingSuccess',
],
},
{
target: '#vc-item-openid4vci.kebabPopUp.updatingPrivateKey',
actions: [
'setWalletBindingId',
'setThumbprintForWalletBindingId',
],
},
],
onError: [
{
actions: ['setWalletBindingError', 'logWalletBindingFailure'],
target:
'#vc-item-openid4vci.kebabPopUp.showingWalletBindingError',
},
],
},
},
updatingPrivateKey: {
invoke: {
src: 'updatePrivateKey',
onDone: {
actions: [
'storeContext',
'updatePrivateKey',
'updateVc',
'setWalletBindingErrorEmpty',
'logWalletBindingSuccess',
],
target: '#vc-item-openid4vci.kebabPopUp',
},
onError: {
actions: 'setWalletBindingError',
target:
'#vc-item-openid4vci.kebabPopUp.showingWalletBindingError',
},
},
},
showActivities: {
on: {
DISMISS: '#vc-item-openid4vci.kebabPopUp',
},
},
removeWallet: {
on: {
CONFIRM: {
target: 'removingVc',
},
CANCEL: {
target: 'idle',
},
},
},
removingVc: {
entry: 'removeVcItem',
on: {
STORE_RESPONSE: {
actions: ['removedVc', 'logVCremoved'],
target: '#vc-item-openid4vci',
},
},
},
},
},
},
},
{
actions: {
requestVcContext: send(
context => ({
type: 'GET_VC_ITEM',
vcMetadata: context.vcMetadata,
protocol: OpenId4VCIProtocol,
}),
{
to: context => context.serviceRefs.vc,
},
),
requestStoredContext: send(
context => {
return StoreEvents.GET(
VCMetadata.fromVC(context.vcMetadata).getVcKey(),
);
},
{
to: context => context.serviceRefs.store,
},
),
updateVc: send(
context => {
const {verifiableCredential} = context;
return {
type: 'VC_DOWNLOADED_FROM_OPENID4VCI',
verifiableCredential,
};
},
{
to: context => context.serviceRefs.vc,
},
),
setVerifiableCredential: model.assign({
verifiableCredential: (_, event) => {
if (event.type === 'GET_VC_RESPONSE') {
return event.vc;
}
return event.response;
},
}),
setGeneratedOn: model.assign({
generatedOn: (context, _event) => {
return context.verifiableCredential.generatedOn;
},
}),
storeContext: send(
context => {
const {serviceRefs, ...data} = context;
data.credentialRegistry = MIMOTO_BASE_URL;
return StoreEvents.SET(
VCMetadata.fromVC(context, true).getVcKey(),
data,
);
},
{
to: context => context.serviceRefs.store,
},
),
setPinCard: assign(context => {
return {
...context,
isPinned: !context.isPinned,
};
}),
VcUpdated: send(
context => {
const {serviceRefs, ...vc} = context;
return {type: 'VC_UPDATE', vc};
},
{
to: context => context.serviceRefs.vc,
},
),
sendVcUpdated: send(
context =>
VcEvents.VC_METADATA_UPDATED(
new VCMetadata({...context.vcMetadata, isPinned: context.isPinned}),
),
{
to: context => context.serviceRefs.vc,
},
),
setWalletBindingError: assign({
walletBindingError: (context, event) =>
i18n.t(`errors.genericError`, {
ns: 'common',
}),
}),
setWalletBindingErrorEmpty: assign({
walletBindingError: () => '',
}),
setPublicKey: assign({
publicKey: (context, event) => {
if (!isCustomSecureKeystore()) {
return (event.data as KeyPair).public;
}
return event.data as string;
},
}),
setPrivateKey: assign({
privateKey: (context, event) => (event.data as KeyPair).private,
}),
updatePrivateKey: assign({
privateKey: () => '',
}),
setWalletBindingId: assign({
walletBindingResponse: (context, event) =>
event.data as WalletBindingResponse,
}),
setThumbprintForWalletBindingId: send(
context => {
const {walletBindingResponse} = context;
const walletBindingIdKey = getBindingCertificateConstant(
walletBindingResponse.walletBindingId,
);
return StoreEvents.SET(
walletBindingIdKey,
walletBindingResponse.thumbprint,
);
},
{
to: context => context.serviceRefs.store,
},
),
removedVc: send(
() => ({
type: 'REFRESH_MY_VCS',
}),
{
to: context => context.serviceRefs.vc,
},
),
logDownloaded: send(
context => {
const {serviceRefs, ...data} = context;
return ActivityLogEvents.LOG_ACTIVITY({
_vcKey: VCMetadata.fromVC(data, true).getVcKey(),
type: 'VC_DOWNLOADED',
timestamp: Date.now(),
deviceName: '',
vcLabel: data.id,
});
},
{
to: context => context.serviceRefs.activityLog,
},
),
logWalletBindingSuccess: send(
context =>
ActivityLogEvents.LOG_ACTIVITY({
_vcKey: VCMetadata.fromVC(context, true).getVcKey(),
type: 'WALLET_BINDING_SUCCESSFULL',
timestamp: Date.now(),
deviceName: '',
vcLabel: context.id,
}),
{
to: context => context.serviceRefs.activityLog,
},
),
logWalletBindingFailure: send(
context =>
ActivityLogEvents.LOG_ACTIVITY({
_vcKey: VCMetadata.fromVC(context, true).getVcKey(),
type: 'WALLET_BINDING_FAILURE',
timestamp: Date.now(),
deviceName: '',
vcLabel: context.id,
}),
{
to: context => context.serviceRefs.activityLog,
},
),
setOtp: model.assign({
otp: (_, event) => event.otp,
}),
setOtpError: assign({
otpError: (_context, event) =>
(event as ErrorPlatformEvent).data.message,
}),
clearOtp: assign({otp: ''}),
removeVcItem: send(
_context => {
return StoreEvents.REMOVE(
MY_VCS_STORE_KEY,
_context.vcMetadata.getVcKey(),
);
},
{to: context => context.serviceRefs.store},
),
setVcKey: model.assign({
vcMetadata: (_, event) => event.vcMetadata,
}),
logVCremoved: send(
(context, _) =>
ActivityLogEvents.LOG_ACTIVITY({
_vcKey: VCMetadata.fromVC(context).getVcKey(),
type: 'VC_REMOVED',
timestamp: Date.now(),
deviceName: '',
vcLabel: context.id,
}),
{
to: context => context.serviceRefs.activityLog,
},
),
},
services: {
updatePrivateKey: async context => {
const hasSetPrivateKey: boolean = await savePrivateKey(
context.walletBindingResponse.walletBindingId,
context.privateKey,
);
if (!hasSetPrivateKey) {
throw new Error('Could not store private key in keystore.');
}
return '';
},
addWalletBindnigId: async context => {
const response = await request(
'POST',
'/residentmobileapp/wallet-binding',
{
requestTime: String(new Date().toISOString()),
request: {
authFactorType: 'WLA',
format: 'jwt',
individualId:
context.verifiableCredential.credential.credentialSubject.VID,
transactionId: context.transactionId,
publicKey: context.publicKey,
challengeList: [
{
authFactorType: 'OTP',
challenge: context.otp,
format: 'alpha-numeric',
},
],
},
},
);
const certificate = response.response.certificate;
await savePrivateKey(
getBindingCertificateConstant(context.id),
certificate,
);
const walletResponse: WalletBindingResponse = {
walletBindingId: response.response.encryptedWalletBindingId,
keyId: response.response.keyId,
thumbprint: response.response.thumbprint,
expireDateTime: response.response.expireDateTime,
};
return walletResponse;
},
generateKeyPair: async context => {
if (!isCustomSecureKeystore()) {
return await generateKeys();
}
const isBiometricsEnabled = SecureKeystore.hasBiometricsEnabled();
return SecureKeystore.generateKeyPair(
context.id,
isBiometricsEnabled,
0,
);
},
requestBindingOtp: async context => {
const response = await request(
'POST',
'/residentmobileapp/binding-otp',
{
requestTime: String(new Date().toISOString()),
request: {
individualId:
context.verifiableCredential.credential.credentialSubject.VID,
otpChannels: ['EMAIL', 'PHONE'],
},
},
);
if (response.response == null) {
throw new Error('Could not process request');
}
},
},
guards: {
hasCredential: (_, event) => {
const vc = event.vc;
return vc != null;
},
isCustomSecureKeystore: () => isCustomSecureKeystore(),
},
},
);
export const createEsignetMosipVCItemMachine = (
serviceRefs: AppServices,
vcMetadata: VCMetadata,
) => {
return EsignetMosipVCItemMachine.withContext({
...EsignetMosipVCItemMachine.context,
serviceRefs,
vcMetadata,
});
};
type State = StateFrom<typeof EsignetMosipVCItemMachine>;
export function selectVerifiableCredentials(state: State) {
return state.context.verifiableCredential;
}
export function selectKebabPopUp(state: State) {
return state.matches('kebabPopUp');
}
export function selectContext(state: State) {
return state.context;
}
export function selectGeneratedOn(state: State) {
return new Date(state.context.generatedOn).toLocaleDateString();
}

View File

@@ -0,0 +1,230 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
'done.invoke.vc-item-openid4vci.addKeyPair:invocation[0]': {
type: 'done.invoke.vc-item-openid4vci.addKeyPair:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]': {
type: 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item-openid4vci.kebabPopUp.addKeyPair:invocation[0]': {
type: 'done.invoke.vc-item-openid4vci.kebabPopUp.addKeyPair:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]': {
type: 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item-openid4vci.kebabPopUp.requestingBindingOtp:invocation[0]': {
type: 'done.invoke.vc-item-openid4vci.kebabPopUp.requestingBindingOtp:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]': {
type: 'done.invoke.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item-openid4vci.requestingBindingOtp:invocation[0]': {
type: 'done.invoke.vc-item-openid4vci.requestingBindingOtp:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item-openid4vci.updatingPrivateKey:invocation[0]': {
type: 'done.invoke.vc-item-openid4vci.updatingPrivateKey:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform.vc-item-openid4vci.addKeyPair:invocation[0]': {
type: 'error.platform.vc-item-openid4vci.addKeyPair:invocation[0]';
data: unknown;
};
'error.platform.vc-item-openid4vci.addingWalletBindingId:invocation[0]': {
type: 'error.platform.vc-item-openid4vci.addingWalletBindingId:invocation[0]';
data: unknown;
};
'error.platform.vc-item-openid4vci.kebabPopUp.addKeyPair:invocation[0]': {
type: 'error.platform.vc-item-openid4vci.kebabPopUp.addKeyPair:invocation[0]';
data: unknown;
};
'error.platform.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]': {
type: 'error.platform.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]';
data: unknown;
};
'error.platform.vc-item-openid4vci.kebabPopUp.requestingBindingOtp:invocation[0]': {
type: 'error.platform.vc-item-openid4vci.kebabPopUp.requestingBindingOtp:invocation[0]';
data: unknown;
};
'error.platform.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]': {
type: 'error.platform.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]';
data: unknown;
};
'error.platform.vc-item-openid4vci.requestingBindingOtp:invocation[0]': {
type: 'error.platform.vc-item-openid4vci.requestingBindingOtp:invocation[0]';
data: unknown;
};
'error.platform.vc-item-openid4vci.updatingPrivateKey:invocation[0]': {
type: 'error.platform.vc-item-openid4vci.updatingPrivateKey:invocation[0]';
data: unknown;
};
'xstate.init': {type: 'xstate.init'};
};
invokeSrcNameMap: {
addWalletBindnigId:
| 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]';
generateKeyPair:
| 'done.invoke.vc-item-openid4vci.addKeyPair:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addKeyPair:invocation[0]';
requestBindingOtp:
| 'done.invoke.vc-item-openid4vci.kebabPopUp.requestingBindingOtp:invocation[0]'
| 'done.invoke.vc-item-openid4vci.requestingBindingOtp:invocation[0]';
updatePrivateKey:
| 'done.invoke.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item-openid4vci.updatingPrivateKey:invocation[0]';
};
missingImplementations: {
actions: 'clearTransactionId';
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
VcUpdated: 'STORE_RESPONSE';
clearOtp:
| 'DISMISS'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.requestingBindingOtp:invocation[0]'
| 'done.invoke.vc-item-openid4vci.requestingBindingOtp:invocation[0]';
clearTransactionId: 'DISMISS';
logVCremoved: 'STORE_RESPONSE';
logWalletBindingFailure:
| 'error.platform.vc-item-openid4vci.addKeyPair:invocation[0]'
| 'error.platform.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'error.platform.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]'
| 'error.platform.vc-item-openid4vci.requestingBindingOtp:invocation[0]'
| 'error.platform.vc-item-openid4vci.updatingPrivateKey:invocation[0]';
logWalletBindingSuccess:
| 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item-openid4vci.updatingPrivateKey:invocation[0]';
removeVcItem: 'CONFIRM';
removedVc: 'STORE_RESPONSE';
requestStoredContext: 'GET_VC_RESPONSE' | 'REFRESH';
requestVcContext: 'DISMISS' | 'xstate.init';
sendVcUpdated: 'STORE_RESPONSE';
setGeneratedOn: 'GET_VC_RESPONSE' | 'STORE_RESPONSE';
setOtp: 'INPUT_OTP';
setPinCard: 'PIN_CARD';
setPrivateKey:
| 'done.invoke.vc-item-openid4vci.addKeyPair:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addKeyPair:invocation[0]';
setPublicKey:
| 'done.invoke.vc-item-openid4vci.addKeyPair:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addKeyPair:invocation[0]';
setThumbprintForWalletBindingId:
| 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]';
setVcKey: 'REMOVE';
setVerifiableCredential: 'GET_VC_RESPONSE' | 'STORE_RESPONSE';
setWalletBindingError:
| 'error.platform.vc-item-openid4vci.addKeyPair:invocation[0]'
| 'error.platform.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'error.platform.vc-item-openid4vci.kebabPopUp.addKeyPair:invocation[0]'
| 'error.platform.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]'
| 'error.platform.vc-item-openid4vci.kebabPopUp.requestingBindingOtp:invocation[0]'
| 'error.platform.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'error.platform.vc-item-openid4vci.requestingBindingOtp:invocation[0]'
| 'error.platform.vc-item-openid4vci.updatingPrivateKey:invocation[0]';
setWalletBindingErrorEmpty:
| 'CANCEL'
| 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item-openid4vci.updatingPrivateKey:invocation[0]';
setWalletBindingId:
| 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]';
storeContext:
| 'PIN_CARD'
| 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item-openid4vci.updatingPrivateKey:invocation[0]';
updatePrivateKey:
| 'done.invoke.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item-openid4vci.updatingPrivateKey:invocation[0]';
updateVc:
| 'STORE_RESPONSE'
| 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item-openid4vci.updatingPrivateKey:invocation[0]';
};
eventsCausingDelays: {};
eventsCausingGuards: {
hasCredential: 'GET_VC_RESPONSE';
isCustomSecureKeystore:
| 'done.invoke.vc-item-openid4vci.addKeyPair:invocation[0]'
| 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addKeyPair:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]';
};
eventsCausingServices: {
addWalletBindnigId:
| 'done.invoke.vc-item-openid4vci.addKeyPair:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addKeyPair:invocation[0]';
generateKeyPair: 'INPUT_OTP';
requestBindingOtp: 'CONFIRM';
updatePrivateKey:
| 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]';
};
matchesStates:
| 'acceptingBindingOtp'
| 'addKeyPair'
| 'addingWalletBindingId'
| 'checkingStore'
| 'checkingVc'
| 'idle'
| 'kebabPopUp'
| 'kebabPopUp.acceptingBindingOtp'
| 'kebabPopUp.addKeyPair'
| 'kebabPopUp.addingWalletBindingId'
| 'kebabPopUp.idle'
| 'kebabPopUp.removeWallet'
| 'kebabPopUp.removingVc'
| 'kebabPopUp.requestingBindingOtp'
| 'kebabPopUp.showActivities'
| 'kebabPopUp.showBindingWarning'
| 'kebabPopUp.showingWalletBindingError'
| 'kebabPopUp.updatingPrivateKey'
| 'pinCard'
| 'requestingBindingOtp'
| 'showBindingWarning'
| 'showingWalletBindingError'
| 'updatingPrivateKey'
| {
kebabPopUp?:
| 'acceptingBindingOtp'
| 'addKeyPair'
| 'addingWalletBindingId'
| 'idle'
| 'removeWallet'
| 'removingVc'
| 'requestingBindingOtp'
| 'showActivities'
| 'showBindingWarning'
| 'showingWalletBindingError'
| 'updatingPrivateKey';
};
tags: never;
}

View File

@@ -1,41 +1,45 @@
import {assign, ErrorPlatformEvent, EventFrom, send, StateFrom} from 'xstate';
import {createModel} from 'xstate/lib/model';
import {HOST, MIMOTO_BASE_URL, MY_VCS_STORE_KEY} from '../shared/constants';
import {AppServices} from '../shared/GlobalContext';
import {CredentialDownloadResponse, request} from '../shared/request';
import {
HOST,
MIMOTO_BASE_URL,
MY_VCS_STORE_KEY,
} from '../../../shared/constants';
import {AppServices} from '../../../shared/GlobalContext';
import {CredentialDownloadResponse, request} from '../../../shared/request';
import {
VC,
VerifiableCredential,
VcIdType,
DecodedCredential,
} from '../types/vc';
import {StoreEvents} from './store';
import {ActivityLogEvents} from './activityLog';
import {verifyCredential} from '../shared/vcjs/verifyCredential';
} from '../../../types/vc';
import {StoreEvents} from '../../store';
import {ActivityLogEvents} from '../../activityLog';
import {verifyCredential} from '../../../shared/vcjs/verifyCredential';
import {log} from 'xstate/lib/actions';
import {
generateKeys,
isCustomSecureKeystore,
WalletBindingResponse,
} from '../shared/cryptoutil/cryptoUtil';
} from '../../../shared/cryptoutil/cryptoUtil';
import {KeyPair} from 'react-native-rsa-native';
import {
getBindingCertificateConstant,
savePrivateKey,
} from '../shared/keystore/SecureKeystore';
} from '../../../shared/keystore/SecureKeystore';
import getAllConfigurations, {
DownloadProps,
} from '../shared/commonprops/commonProps';
import {VcEvents} from './vc';
import i18n from '../i18n';
} from '../../../shared/commonprops/commonProps';
import {VcEvents} from '../../vc';
import i18n from '../../../i18n';
import SecureKeystore from 'react-native-secure-keystore';
import {VCMetadata} from '../shared/VCMetadata';
import {VCMetadata} from '../../../shared/VCMetadata';
import {
sendStartEvent,
getData,
getEndData,
sendEndEvent,
} from '../shared/telemetry/TelemetryUtils';
} from '../../../shared/telemetry/TelemetryUtils';
const model = createModel(
{
@@ -98,15 +102,15 @@ const model = createModel(
},
);
export const VcItemEvents = model.events;
export const ExistingMosipVCItemEvents = model.events;
export const vcItemMachine =
export const ExistingMosipVCItemMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QDcDGBaAlgFzAWwGIAlAUQDFSBlACQG0AGAXUVAAcB7WHTdgOxZAAPRABYATABoQAT0QBGABwBWAHT0FAZjEB2bSKUBOA9rliAvmalosuPCtQALMKgDWmXlABqqAgHESACoA+p4AwkFUAAoA8gBylCQMzEggHFzYPPwpwggKBiIqcvqaAGwGSvT0SnJSsgjicio69Np5GvTFSiIWVhg4+PZOru5ePv7BYREklDHxiXLJbJzcfAI5eQVFSqXlldW1iO2qmlpKhtpnGkbdliDW-XaOzm4elNjsAE5gBJQB0aRTGZxBJJARpFZZUA5OSmA4IMRyDQFDRKDQmMRiDRyAwKG69GwDJ7DV7vL4-P4AqLA+aLVLLDKrbLyJRiBRNAxybT0MRnQwKBSSGTyXFNERGPIwhFo-k9O59WyDZ4jShgD7IVUAEQAhtgtYriVA3jqAK6wAgxAAyFtBKXBDMhQkQPO0TTORQMJRZ2hKyjhihEbLkHRaXVRcmqCll9wVRJehtV6o+2t1+rjRuwpoIGuiAHVYhbogBBDVTYsATRtS3SmTWokMhWD9HyJRKRUxfuMYjUJQRBnaii6VSj8sJQzTCc1Or1EHYAHdeAAbdhaiAjUJfCBgXgZLUL83RK2VunVxlQxAKIMqETiCVo6pnBR+hQlDSFBTaMQlEQdL8dPFyglHjHZUJyTKcVBneclxXNcNy3Hc91CUgNRIWIAgASULC0gmzPMC2LEgNSPO0ayZBAg3yQoURMGjLg9DsxAKD88gxERsQDVph0AlRMAgBdvkI9DggCQtfGI+lSLPeERBdNiShMIMtDFb8lDhT9ORUNERDKZ9rw-JQuIeHi+O+TwSCIdCyArJgwQk09HXhT8ShUd8ujyFkMUMEo1LEFoVBZbl6BhBQQzRQyFV4-iCALUIAGkQlCcSTwdHIdA0V8kW01k5BKegexaNTvUad9r206of3MW5owGSLvlITxolikgEqSiFa1yQU6lZEKXL0JRPS5AMXwMqqRzsWqCGLEscywi1AiCAAhdDYg1ZbfCCdCiJs207JS+ROv28KBkgbgPACLUoCzdDKAAWWuyhWvtdrMVknKFPoJT8iqNT0voQp9By79TB7AwjrsE6MjOi6fkLMyghEsTtqrNqyMY175JhD7GK+1ShWk6pChbREBQMDpPzBlRYFJEZzsu35-maqk5keySHP645yhbDolMUbQ1IBwnWw0EmyZKCnE0wAAzaRYMgeDMF3AgZ14MAeN4ZB2BcVXqrsCXpdlzdtwVhcEHcDXUB1TIkhZ+zUqc3q3PKDEeXovHfNbFQvwqSoUSbF9xdVKWZY8dc5aNxXVQ+T4VFYBcdUlz5dbGlQ9eDqBQ8NhDTfV9gLfta2kePFGpM85zXIDJ3PNdupsR9T3n3KFFlDYnKKdjEZPEDyXMDzzJ00zG29vI5QDBc5QuSMfqPUUJ8KjfMogp5apurb4CPE7j4pZ7y2+H7s1aAWWzkvagdR-5JQJ856fHzdxRR69gMZP5FptAps3d14ghlsiABVYJogCJEQe7UtAujyjyHQRR5JiD7HCEQVxVBP2MHlPq-tRrcXfguT+q1br3WAWRUB3YIEmG0h+WBeNhZflFMYVsYoSGVXxEZL4ABHY0cBIZQGiNgVgSs+CqzNprbWycWFsKpiMLhrBs7mx3rwAutISK23kDCV8MDfLpX6pUD6cCuTOT7NoShbEL75ApiI9h4juEEEjtHWO8dE4p2EWAVhZiPASKkbnGRcij7FwcjCRE7I1Eolyj7OBQsVDGFJp6fQMCigUy1KgVAYBWAcIkehXgrBjTYC-rEX+-9AH4KkmUVQ4C3S4lKTiOBzQVDMQFEDC+SJYnxMSck7hqT0mZJwXdSgD1C4KKHqYJs9cQrtHKHoRicINAY38iTTQ1waIaAaQkpJIwiBgA1lrVpGSsk5KCAAoBPTdonzeoUIov1tBfRgbjGuiJjjpS0O0TQLIRqMIVHExZHCVlrLABs9p11OndPkQcsiRhGimH0GKAUXIsZwjIVebYPIfSIkfq-dBTDHGiI4RaXOLheEqzVp8+x3FTFiI8Ji1wbje58E8TtY+ZEfRFJ7CUx+eQb51CuM+KpXJzj3MUGLFFCoiUYqxZYj4UcPgxzjtgBOHwk6ErRc4qApKXDko8UwfJDlClEMZWUllog2L3w+n2KUDyhx8oGEuA03hyQM0BLMEE+yaVSV8SogwASNHBLxt+PyRgPTvhyuoF1Ji5XEqgB8wROL+E5y1gS1FTjg2hq1sq-Oqr7XeNSqTJo6g0TekxBeE54yfaC17PJF86hA2xveassNVixU2MlXYnWKgBXLMrQmgRFLZHJoBQ6hyhDimQNITAjQcCYSqACmKPKTt0qBrWR3Hw9NKTTFtYkFNT1aVnJcuUAUFQrifj0PmtEntGKtmbPJb8vLnlmvYFAKAzbPlXVwV0tV0IX5Xh9N6xEaJWQHQQO0A9PZtLsRbHoPKZb0UjAWu4VcLiLHKwjfihtTaPAQd4FBzh3DE1W07V41dUlf1hMgQ8oMRjv3A1UEGM9T8ijiCeQBGNYGkOQfMTw6t4rbHSujfyoNHDkOodcW2lVjAn2HA+vhkwhGqhnO-RURogVi1C36vAimsAHBzhICKz4BBQiFliKEEg1oV2sxyJ6ZypgKjPm9HkPKOqEAeWod6eBjFFJyAWU08DjHoM8O-n-HZeSDOKIQBcWSrQZk5UoeGP0S8qnRPDF+i4wsXNLIYyhpj96-lCYCxfK8wWrihefOFvGF8ux6G9byZ0sSIAQFimAaQkQtSYA+OGvFgiOMDBXJV6rtX6sYcpVh6lqalHyWOfA9oZzvwXIiyKLkJM2ItBkvo8r7Wat1Yayx2tUqZVGTa1VpbXX+NJsE35vpRyYTDZaOc8ofoya9TFIDdK4TkUXrsG1kYOZdz8WwDxkY6EICNYEVGhtz2PCvYXO9z7HhvvdY7Qdrt-Wf0aT7SQ6B5DWWFc0noeS6V9H9Xqaap7FWXtvbAB99zUBvvCtFaxut7GAf46B4T4nyXwcQEh1S5GOGe1+QR1AshQ6KFYk2IxVEAomwohow240rAICWw8JETeyAdRgG279yNQjuIS6lxw2XmB5e4G2yz3rbPDOIFaGRlETKZKYhfOMupmlyiDkChcEDuOVDq+l1ALXOvFfVfJ9YiV62Wt2Fd5ruXCu9d7cw9D7DRuAvKCom5JFlveesv0CZj6ehwxIlMA92jCplNzjB4aXUGYzRLRWmtHCcRl0w-Z9CXy99n7DIvuIEQ4yewyZJhMowPIQojVuLwdgm54ApB1lH-z6AJlwnQP0tQmjTBIgFFnhhOfRxKnXqgUfQ9bPexyuUD0YpDDjNj8V5sQUX4nFXqvwvnwwAb5PgiAomi-YIlZMUfmzlj-FFaF+zEF+DQqjVJOLqLfmRCdn6GUM5LlN+M+O0PJE7o9qmCBAAWBCmO3CSCaEPobv5jlCRvkKPOAr5OoKUoYGLsnKgfGEgcmNOHOIuMuKhhnPLLuMAQUnXEiFmnUqiLoDgVQlsMLPkK0L4uesvuNCZEwQ5NjA-pKKiEYPoroIVDyNdkNPkCOq3M7hDDTBdKIc+t+p+AMmKMYFiO+B6KDM7lTJ8OoVAJoYcLHuEkFMLJ+FiC6t5G7PoH9O5DoB6J+M+BiAHJvPrCHHBOHAuJYfCNyEVtiBiKwb5J+GpIDLbl0F+kYBMkvg2mQRvFvO2nvMEYiINufDRB+BZloDEcLC5KYHXopNsLiG-OrB-BAMEWxFeO+pUDApUB+nAkiH9MUHlNiPoiFMkcnJgrxCoOwNwsEXkA0cCk0aTLYUnrqjoGEtsFnl7MVlUfLlghACoAAEZxJawobBEupwJDSwpnJ8FVD8jZ4IZcZMbBGshUK5QsgTJBQXgi4hIDJTYIjchGB3EJbNKsDfKjHrpfhCzwJ5b6EVKaBhKhYuq6R0IkHcSvKuYeDxpfJpIZLBGBZZb8g5Yvh5aXJKKVBxE95iY3aRjO6IYKpYp1EfiFA8iIichcgnbfrwLlAZq5q+SYxBQUzmpxjeD-F-ThIyQfQwh6B5DjI4gFDeqFLcgfgwiCEXHlq3qCLBHyTDqmBRYN475jbTqayzp1EcgEl6IBjVC8z5pFCHr6BohJF5QdCclXo3qIkto359Y17njrriBiYupOxVC4k-otFmn9S6C4ifioigbyoF4SJ1EkaMR-RTZojTynHGImEqazhqairBFej+QtGFJ7675wi2Yf6-gCjySwmbaNKJZQBhkjFOnR4shsgnCmDKCVAmDlJ4ymZ-S3LNDBiwHGHwFbYdbLZ7FIiaRC6fh0Kfj7EtnZQQmejKDhjigmo9m05QDA6g4k7fZpl8weoQLzEL55TaTzLO5B4jAe6h7VZpn6ANh6ruHwIyTelNyvgnBVAejqBfgknwF56zgF6ZFVlYEFpQKKQchvStDW44hqDTaGl5AWAWBAA */
model.createMachine(
{
predictableActionArguments: true,
preserveActionOrder: true,
tsTypes: {} as import('./vcItem.typegen').Typegen0,
tsTypes: {} as import('./ExistingMosipVCItemMachine.typegen').Typegen0,
schema: {
context: model.initialContext,
events: {} as EventFrom<typeof model>,
@@ -1339,12 +1343,12 @@ export const vcItemMachine =
},
);
export const createVcItemMachine = (
export const createExistingMosipVCItemMachine = (
serviceRefs: AppServices,
vcMetadata: VCMetadata,
) => {
return vcItemMachine.withContext({
...vcItemMachine.context,
return ExistingMosipVCItemMachine.withContext({
...ExistingMosipVCItemMachine.context,
serviceRefs,
id: vcMetadata.id,
idType: vcMetadata.idType as VcIdType,
@@ -1353,7 +1357,7 @@ export const createVcItemMachine = (
});
};
type State = StateFrom<typeof vcItemMachine>;
type State = StateFrom<typeof ExistingMosipVCItemMachine>;
export function selectVc(state: State) {
const {serviceRefs, ...data} = state.context;

View File

@@ -2,8 +2,8 @@
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
'': { type: '' };
internalEvents: {
'': {type: ''};
'done.invoke.checkStatus': {
type: 'done.invoke.checkStatus';
data: unknown;
@@ -135,9 +135,9 @@ export interface Typegen0 {
type: 'error.platform.vc-item.verifyingCredential:invocation[0]';
data: unknown;
};
'xstate.init': { type: 'xstate.init' };
'xstate.init': {type: 'xstate.init'};
};
'invokeSrcNameMap': {
invokeSrcNameMap: {
addWalletBindnigId:
| 'done.invoke.vc-item.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]';
@@ -158,14 +158,13 @@ export interface Typegen0 {
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
verifyCredential: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
};
'missingImplementations': {
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
'eventsCausingActions': {
VcUpdated: 'STORE_RESPONSE';
eventsCausingActions: {
clearOtp:
| ''
| 'CANCEL'
@@ -190,7 +189,6 @@ export interface Typegen0 {
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]'
| 'error.platform.vc-item.verifyingCredential:invocation[0]';
editVcKey: 'CREDENTIAL_DOWNLOADED';
incrementDownloadCounter: 'POLL';
logDownloaded: 'STORE_RESPONSE';
logRevoked: 'STORE_RESPONSE';
@@ -214,7 +212,7 @@ export interface Typegen0 {
requestStoredContext: 'GET_VC_RESPONSE' | 'REFRESH';
requestVcContext: 'DISMISS' | 'xstate.init';
revokeVID: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
sendVcUpdated: 'STORE_RESPONSE';
sendVcUpdated: 'PIN_CARD';
setCredential: 'GET_VC_RESPONSE' | 'STORE_RESPONSE';
setDownloadInterval: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
setLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
@@ -264,7 +262,6 @@ export interface Typegen0 {
| 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]';
storeContext:
| 'CREDENTIAL_DOWNLOADED'
| 'PIN_CARD'
| 'done.invoke.vc-item.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]'
@@ -283,8 +280,8 @@ export interface Typegen0 {
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]';
};
'eventsCausingDelays': {};
'eventsCausingGuards': {
eventsCausingDelays: {};
eventsCausingGuards: {
hasCredential: 'GET_VC_RESPONSE' | 'STORE_RESPONSE';
isCustomSecureKeystore:
| 'done.invoke.vc-item.addKeyPair:invocation[0]'
@@ -294,7 +291,7 @@ export interface Typegen0 {
isDownloadAllowed: 'POLL';
isVcValid: '';
};
'eventsCausingServices': {
eventsCausingServices: {
addWalletBindnigId:
| 'done.invoke.vc-item.addKeyPair:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.addKeyPair:invocation[0]';
@@ -313,7 +310,7 @@ export interface Typegen0 {
| 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]';
verifyCredential: '' | 'VERIFY';
};
'matchesStates':
matchesStates:
| 'acceptingBindingOtp'
| 'acceptingOtpInput'
| 'acceptingRevokeInput'
@@ -365,7 +362,7 @@ export interface Typegen0 {
| 'downloadingCredential'
| 'savingFailed'
| 'verifyingDownloadLimitExpiry'
| { savingFailed?: 'idle' | 'viewingVc' };
| {savingFailed?: 'idle' | 'viewingVc'};
invalid?: 'backend' | 'otp';
kebabPopUp?:
| 'acceptingBindingOtp'
@@ -380,5 +377,5 @@ export interface Typegen0 {
| 'showingWalletBindingError'
| 'updatingPrivateKey';
};
'tags': never;
tags: never;
}

501
machines/issuersMachine.ts Normal file
View File

@@ -0,0 +1,501 @@
import {authorize, AuthorizeResult} from 'react-native-app-auth';
import {assign, EventFrom, send, sendParent, StateFrom} from 'xstate';
import {createModel} from 'xstate/lib/model';
import {Theme} from '../components/ui/styleUtils';
import {MY_VCS_STORE_KEY} from '../shared/constants';
import {request} from '../shared/request';
import {StoreEvents} from './store';
import {AppServices} from '../shared/GlobalContext';
import {
generateKeys,
isCustomSecureKeystore,
} from '../shared/cryptoutil/cryptoUtil';
import SecureKeystore from 'react-native-secure-keystore';
import {KeyPair} from 'react-native-rsa-native';
import {ActivityLogEvents} from './activityLog';
import {log} from 'xstate/lib/actions';
import {verifyCredential} from '../shared/vcjs/verifyCredential';
import {getBody, getIdentifier} from '../shared/openId4VCI/Utils';
import {VCMetadata} from '../shared/VCMetadata';
import {VerifiableCredential} from '../components/VC/EsignetMosipVCItem/vc';
const model = createModel(
{
issuers: [] as issuerType[],
selectedIssuer: [] as issuerType[],
tokenResponse: {} as AuthorizeResult,
errorMessage: '' as string,
loadingReason: 'displayIssuers' as string,
verifiableCredential: null as VerifiableCredential | null,
serviceRefs: {} as AppServices,
publicKey: ``,
privateKey: ``,
},
{
events: {
DISMISS: () => ({}),
SELECTED_ISSUER: (id: string) => ({id}),
DOWNLOAD_ID: () => ({}),
COMPLETED: () => ({}),
TRY_AGAIN: () => ({}),
RESET_ERROR: () => ({}),
CHECK_KEY_PAIR: () => ({}),
CANCEL: () => ({}),
},
},
);
export const IssuerScreenTabEvents = model.events;
export const Issuer_Tab_Ref_Id = 'issuersMachine';
export const Issuers_Key_Ref = 'OpenId4VCI';
export const IssuersMachine = model.createMachine(
{
/** @xstate-layout N4IgpgJg5mDOIC5QEtawK5gE6wLIEMBjAC2QDswA6AG1QBcBJNTHAYggHsLLY786qqDNjxFS3WrybCcAbQAMAXUSgADh1jI6yLipAAPRAEYA7PMomj8gJwAWeSYAcTgMy2TJgDQgAnsZPWlLYArC5GAEzWEfKRtka2AL4J3kIsoiTkVLBg1GCE2mRQ0iysACIA8gDqAHIAMuUAgqUA+gBqDA3NDKUKykgg6prauv2GCMHuFo7W8sET8i4x1uHefgimJpTWoRHWJrYuc9YAbEkpzCIEGdzZufnkRRdYrADKAKK1bwDCACpvLQwXi8AKpvABKvT0gy0OjIejGE02Thmc3si0iK18iEcRiC1nxJxR22OjhcZxAqUuYkyPByeQKjxkZUBuEBL0h-Whwzho0QiKmKPm6OWq0Q4X2QXkx3C0uCOKM0pcJnJlJwV3EWTp+WK2HYXCyfAElFV6Q1tLujCeHLUGhhI1AY2mx0oRmCxyMjgcJMi8VF6xM4Uo4QWRjc2xMcqVp2SFKepppqmwADMOFgALYNdB0Yip5AAL34sNYDWBPwAEuUwQwAFr-a0DW3c+HGBaOILHeSOWwkj3hYIKv34rYmY4TUeOZzSxzR84yePcRNYFPpzPZ3MF7msMHfN4MVr-Zo-coAaTe1XrXNhzfW4VvlEW3YVMRcexlfo2Wx2kX2h2C20SMYmuqNIwHQXxYJAYBkNo+DUAAYlgHBpjqzycDchqCHGwHcKB4GQdByCwQhSEoRejZXry6zuG2yy9scSq0bY75WEGMpytM+wONsZLkmQHAQHAehAdSFBQuR9oGIgAC0xx+jJKpYSJghkFoYlDBRDqIMctiUB2EyOPpMzhM4mJrH2gTLHs8jxI44T2K6ClzthVCSJac5qXaPKaQgtimWK1lBJExwzF24QuMGtgAbOaTOea9IPChHlNpRLi2ZQ-Zur5o7PlEslYggwa4r5JwBFlLijvsjkxUpcXak8SUaZJCARi4Lq2F28izGEna2e+dgugExwmKl7hmFKVVUtcVCLsuGZZjmWD5oWEmXhJYxWCxtkxGiRjLJYjh+oVgUlXYMrlcElWAYpU2ULhEECQRRGIch9WcuJXlNfECqBR6MTOHYZgHflwZtkNVkxGdrbhBNao1cgEC5A1a2IC1bUdV1VgTn5BV7JQWOksENhyk4RhJEkQA */
predictableActionArguments: true,
preserveActionOrder: true,
id: Issuer_Tab_Ref_Id,
context: model.initialContext,
initial: 'displayIssuers',
tsTypes: {} as import('./issuersMachine.typegen').Typegen0,
schema: {
context: model.initialContext,
events: {} as EventFrom<typeof model>,
},
states: {
displayIssuers: {
description: 'displays the issuers downloaded from the server',
invoke: {
src: 'downloadIssuersList',
onDone: {
actions: ['setIssuers'],
target: 'selectingIssuer',
},
onError: {
actions: ['setError'],
target: 'error',
},
},
},
error: {
description: 'reaches here when any error happens',
on: {
TRY_AGAIN: {
actions: 'resetError',
target: 'displayIssuers',
},
RESET_ERROR: {
actions: 'resetError',
target: 'idle',
},
},
},
selectingIssuer: {
description: 'waits for the user to select any issuer',
on: {
DOWNLOAD_ID: {
actions: sendParent('DOWNLOAD_ID'),
},
SELECTED_ISSUER: {
target: 'downloadIssuerConfig',
},
},
},
downloadIssuerConfig: {
description: 'downloads the configuration of the selected issuer',
invoke: {
src: 'downloadIssuerConfig',
onDone: {
actions: 'setSelectedIssuers',
target: 'performAuthorization',
},
},
},
performAuthorization: {
description:
'invokes the issuers authorization endpoint and gets the access token',
invoke: {
src: 'invokeAuthorization',
onDone: {
actions: ['setTokenResponse', 'getKeyPairFromStore', 'loadKeyPair'],
target: 'checkKeyPair',
},
onError: {
actions: [() => console.log('error in invokeAuth - ', event.data)],
target: 'downloadCredentials',
},
},
},
checkKeyPair: {
description: 'checks whether key pair is generated',
entry: [
context =>
log(
'Reached CheckKeyPair context -> ',
JSON.stringify(context, null, 4),
),
send('CHECK_KEY_PAIR'),
],
on: {
CHECK_KEY_PAIR: [
{
cond: 'hasKeyPair',
target: 'downloadCredentials',
},
{
target: 'generateKeyPair',
},
],
},
},
generateKeyPair: {
description:
'if keypair is not generated, new one is created and stored',
invoke: {
src: 'generateKeyPair',
onDone: [
{
actions: ['setPublicKey', 'setPrivateKey', 'storeKeyPair'],
target: 'downloadCredentials',
},
{
actions: ['setPublicKey', 'storeKeyPair'],
cond: 'isCustomSecureKeystore',
target: 'downloadCredentials',
},
],
},
},
downloadCredentials: {
description: 'credential is downloaded from the selected issuer',
invoke: {
src: 'downloadCredential',
onDone: {
actions: 'setVerifiableCredential',
target: 'verifyingCredential',
},
onError: {
actions: () => console.log('in error of downloadCredential'),
},
},
on: {
CANCEL: {
target: 'selectingIssuer',
},
},
},
verifyingCredential: {
description:
'once the credential is downloaded, it is verified before saving',
invoke: {
src: 'verifyCredential',
onDone: [
{
target: 'storing',
},
],
onError: [
{
actions: log((_, event) => (event.data as Error).message),
//TODO: Move to state according to the required flow when verification of VC fails
target: 'idle',
},
],
},
},
storing: {
description: 'all the verified credential is stored.',
entry: [
'storeVerifiableCredentialMeta',
'storeVerifiableCredentialData',
'storeVcsContext',
'storeVcMetaContext',
'logDownloaded',
],
},
idle: {
on: {
COMPLETED: {
target: 'done',
},
CANCEL: {
target: 'selectingIssuer',
},
},
},
done: {
entry: () => console.log('Reached done'),
type: 'final',
},
},
},
{
actions: {
setIssuers: model.assign({
issuers: (_, event) => event.data,
loadingReason: null,
}),
setError: model.assign({
errorMessage: (_, event) => {
console.log('Error while fetching issuers ', event.data.message);
return event.data.message === 'Network request failed'
? 'noInternetConnection'
: 'generic';
},
loadingReason: null,
}),
resetError: model.assign({
errorMessage: '',
}),
loadKeyPair: assign({
publicKey: (_, event) => event.publicKey,
privateKey: (context, event) =>
event.privateKey ? event.privateKey : context.privateKey,
}),
getKeyPairFromStore: send(StoreEvents.GET(Issuers_Key_Ref), {
to: context => context.serviceRefs.store,
}),
storeKeyPair: send(
(_, event) => {
return StoreEvents.SET(Issuers_Key_Ref, {
publicKey: (event.data as KeyPair).public + ``,
privateKey: (event.data as KeyPair).private + ``,
});
},
{
to: context => context.serviceRefs.store,
},
),
storeVerifiableCredentialMeta: send(
context => {
const [issuer, protocol, id] =
context.verifiableCredential?.identifier.split(':');
return StoreEvents.PREPEND(
MY_VCS_STORE_KEY,
VCMetadata.fromVC({
id: id ? id : null,
issuer: issuer,
protocol: protocol,
}),
);
},
{
to: context => context.serviceRefs.store,
},
),
storeVerifiableCredentialData: send(
context => {
const [issuer, protocol, id] =
context.verifiableCredential?.identifier.split(':');
return StoreEvents.SET(
VCMetadata.fromVC({
id: id ? id : null,
issuer: issuer,
protocol: protocol,
}).getVcKey(),
context.verifiableCredential,
);
},
{
to: context => context.serviceRefs.store,
},
),
storeVcMetaContext: send(
context => {
const [issuer, protocol, id] =
context.verifiableCredential?.identifier.split(':');
return {
type: 'VC_ADDED',
vcMetadata: VCMetadata.fromVC({
id: id ? id : null,
issuer: issuer,
protocol: protocol,
}),
};
},
{
to: context => context.serviceRefs.vc,
},
),
storeVcsContext: send(
context => {
const [issuer, protocol, id] =
context.verifiableCredential?.identifier.split(':');
return {
type: 'VC_DOWNLOADED_FROM_OPENID4VCI',
vcMetadata: VCMetadata.fromVC({
id: id ? id : null,
issuer: issuer,
protocol: protocol,
}),
vc: context.verifiableCredential,
};
},
{
to: context => context.serviceRefs.vc,
},
),
setSelectedIssuers: model.assign({
selectedIssuer: (_, event) => event.data,
}),
setTokenResponse: model.assign({
tokenResponse: (_, event) => event.data,
loadingReason: 'settingUp',
}),
setVerifiableCredential: model.assign({
verifiableCredential: (_, event) => {
return event.data;
},
}),
setPublicKey: assign({
publicKey: (context, event) => {
if (!isCustomSecureKeystore()) {
return (event.data as KeyPair).public;
}
return event.data as string;
},
loadingReason: 'downloadingCredentials',
}),
setPrivateKey: assign({
privateKey: (context, event) => (event.data as KeyPair).private,
}),
logDownloaded: send(
context => {
const [issuer, protocol, id] =
context.verifiableCredential?.identifier.split(':');
return ActivityLogEvents.LOG_ACTIVITY({
_vcKey: id,
type: 'VC_DOWNLOADED',
timestamp: Date.now(),
deviceName: '',
vcLabel: '',
});
},
{
to: context => context.serviceRefs.activityLog,
},
),
},
services: {
downloadIssuersList: async () => {
const defaultIssuer = {
id: 'UIN, VID, AID',
displayName: 'UIN, VID, AID',
logoUrl: Theme.DigitIcon,
};
const response = await request('GET', '/residentmobileapp/issuers');
return [defaultIssuer, ...response.response.issuers];
},
downloadIssuerConfig: async (_, event) => {
const response = await request(
'GET',
`/residentmobileapp/issuers/${event.id}`,
);
return response.response;
},
downloadCredential: async context => {
const body = await getBody(context);
const response = await fetch(
'https://api-internal.dev1.mosip.net/v1/esignet/vci/credential',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + context.tokenResponse?.accessToken,
},
body: JSON.stringify(body),
},
);
let credential = await response.json();
credential = updateCredentialInformation(context, credential);
return credential;
},
invokeAuthorization: async context => {
const response = await authorize(context.selectedIssuer);
return response;
},
generateKeyPair: async context => {
if (!isCustomSecureKeystore()) {
return await generateKeys();
}
const isBiometricsEnabled = SecureKeystore.hasBiometricsEnabled();
return SecureKeystore.generateKeyPair(
Issuers_Key_Ref,
isBiometricsEnabled,
0,
);
return context;
},
verifyCredential: async context => {
return verifyCredential(context.verifiableCredential?.credential);
},
},
guards: {
hasKeyPair: context => {
return context.publicKey != null;
},
isCustomSecureKeystore: () => isCustomSecureKeystore(),
},
},
);
type State = StateFrom<typeof IssuersMachine>;
export function selectIssuers(state: State) {
return state.context.issuers;
}
export function selectErrorMessage(state: State) {
return state.context.errorMessage;
}
export function selectLoadingReason(state: State) {
return state.context.loadingReason;
}
export function selectIsDownloadCredentials(state: State) {
return state.matches('downloadCredentials');
}
export function selectIsDone(state: State) {
return state.matches('done');
}
export function selectIsIdle(state: State) {
return state.matches('idle');
}
export function selectStoring(state: State) {
return state.matches('storing');
}
interface issuerType {
id: string;
displayName: string;
logoUrl: string;
}
const updateCredentialInformation = (context, credential) => {
credential.identifier = getIdentifier(context, credential);
credential.generatedOn = new Date();
credential.issuerLogo = context.selectedIssuer.logoUrl;
console.log(
'Response from downloadCredential',
JSON.stringify(credential, null, 4),
);
return credential;
};

View File

@@ -0,0 +1,108 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
'done.invoke.issuersMachine.displayIssuers:invocation[0]': {
type: 'done.invoke.issuersMachine.displayIssuers:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.issuersMachine.downloadCredentials:invocation[0]': {
type: 'done.invoke.issuersMachine.downloadCredentials:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.issuersMachine.downloadIssuerConfig:invocation[0]': {
type: 'done.invoke.issuersMachine.downloadIssuerConfig:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.issuersMachine.generateKeyPair:invocation[0]': {
type: 'done.invoke.issuersMachine.generateKeyPair:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.issuersMachine.performAuthorization:invocation[0]': {
type: 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.issuersMachine.verifyingCredential:invocation[0]': {
type: 'done.invoke.issuersMachine.verifyingCredential:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform.issuersMachine.displayIssuers:invocation[0]': {
type: 'error.platform.issuersMachine.displayIssuers:invocation[0]';
data: unknown;
};
'error.platform.issuersMachine.performAuthorization:invocation[0]': {
type: 'error.platform.issuersMachine.performAuthorization:invocation[0]';
data: unknown;
};
'xstate.init': {type: 'xstate.init'};
};
invokeSrcNameMap: {
downloadCredential: 'done.invoke.issuersMachine.downloadCredentials:invocation[0]';
downloadIssuerConfig: 'done.invoke.issuersMachine.downloadIssuerConfig:invocation[0]';
downloadIssuersList: 'done.invoke.issuersMachine.displayIssuers:invocation[0]';
generateKeyPair: 'done.invoke.issuersMachine.generateKeyPair:invocation[0]';
invokeAuthorization: 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
verifyCredential: 'done.invoke.issuersMachine.verifyingCredential:invocation[0]';
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
getKeyPairFromStore: 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
loadKeyPair: 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
logDownloaded: 'done.invoke.issuersMachine.verifyingCredential:invocation[0]';
resetError: 'RESET_ERROR' | 'TRY_AGAIN';
setError: 'error.platform.issuersMachine.displayIssuers:invocation[0]';
setIssuers: 'done.invoke.issuersMachine.displayIssuers:invocation[0]';
setPrivateKey: 'done.invoke.issuersMachine.generateKeyPair:invocation[0]';
setPublicKey: 'done.invoke.issuersMachine.generateKeyPair:invocation[0]';
setSelectedIssuers: 'done.invoke.issuersMachine.downloadIssuerConfig:invocation[0]';
setTokenResponse: 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
setVerifiableCredential: 'done.invoke.issuersMachine.downloadCredentials:invocation[0]';
storeKeyPair: 'done.invoke.issuersMachine.generateKeyPair:invocation[0]';
storeVcMetaContext: 'done.invoke.issuersMachine.verifyingCredential:invocation[0]';
storeVcsContext: 'done.invoke.issuersMachine.verifyingCredential:invocation[0]';
storeVerifiableCredentialData: 'done.invoke.issuersMachine.verifyingCredential:invocation[0]';
storeVerifiableCredentialMeta: 'done.invoke.issuersMachine.verifyingCredential:invocation[0]';
};
eventsCausingDelays: {};
eventsCausingGuards: {
hasKeyPair: 'CHECK_KEY_PAIR';
isCustomSecureKeystore: 'done.invoke.issuersMachine.generateKeyPair:invocation[0]';
};
eventsCausingServices: {
downloadCredential:
| 'CHECK_KEY_PAIR'
| 'done.invoke.issuersMachine.generateKeyPair:invocation[0]'
| 'error.platform.issuersMachine.performAuthorization:invocation[0]';
downloadIssuerConfig: 'SELECTED_ISSUER';
downloadIssuersList: 'TRY_AGAIN' | 'xstate.init';
generateKeyPair: 'CHECK_KEY_PAIR';
invokeAuthorization: 'done.invoke.issuersMachine.downloadIssuerConfig:invocation[0]';
verifyCredential: 'done.invoke.issuersMachine.downloadCredentials:invocation[0]';
};
matchesStates:
| 'checkKeyPair'
| 'displayIssuers'
| 'done'
| 'downloadCredentials'
| 'downloadIssuerConfig'
| 'error'
| 'generateKeyPair'
| 'idle'
| 'performAuthorization'
| 'selectingIssuer'
| 'storing'
| 'verifyingCredential';
tags: never;
}

View File

@@ -4,9 +4,11 @@ import {StoreEvents} from './store';
import {VC} from '../types/vc';
import {AppServices} from '../shared/GlobalContext';
import {log, respond} from 'xstate/lib/actions';
import {VcItemEvents} from './vcItem';
import {ExistingMosipVCItemEvents} from './VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {MY_VCS_STORE_KEY, RECEIVED_VCS_STORE_KEY} from '../shared/constants';
import {parseMetadatas, VCMetadata} from '../shared/VCMetadata';
import {OpenId4VCIProtocol} from '../shared/openId4VCI/Utils';
import {EsignetMosipVCItemEvents} from './VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
const model = createModel(
{
@@ -26,6 +28,10 @@ const model = createModel(
VC_METADATA_UPDATED: (vcMetadata: VCMetadata) => ({vcMetadata}),
VC_RECEIVED: (vcMetadata: VCMetadata) => ({vcMetadata}),
VC_DOWNLOADED: (vc: VC) => ({vc}),
VC_DOWNLOADED_FROM_OPENID4VCI: (vc: VC, vcMetadata: VCMetadata) => ({
vc,
vcMetadata,
}),
VC_UPDATE: (vc: VC) => ({vc}),
REFRESH_MY_VCS: () => ({}),
REFRESH_MY_VCS_TWO: (vc: VC) => ({vc}),
@@ -146,6 +152,9 @@ export const vcMachine =
VC_DOWNLOADED: {
actions: 'setDownloadedVc',
},
VC_DOWNLOADED_FROM_OPENID4VCI: {
actions: 'setDownloadedVCFromOpenId4VCI',
},
VC_UPDATE: {
actions: 'setVcUpdate',
},
@@ -171,7 +180,10 @@ export const vcMachine =
getVcItemResponse: respond((context, event) => {
const vc = context.vcs[event.vcMetadata?.getVcKey()];
return VcItemEvents.GET_VC_RESPONSE(vc);
if (event.protocol === OpenId4VCIProtocol) {
return EsignetMosipVCItemEvents.GET_VC_RESPONSE(vc);
}
return ExistingMosipVCItemEvents.GET_VC_RESPONSE(vc);
}),
loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), {
@@ -199,6 +211,10 @@ export const vcMachine =
context.vcs[vcUniqueId] = event.vc;
},
setDownloadedVCFromOpenId4VCI: (context, event) => {
if (event.vc) context.vcs[event.vcMetadata.getVcKey()] = event.vc;
},
setVcUpdate: (context, event) => {
Object.keys(context.vcs).map(vcUniqueId => {
const eventVCMetadata = VCMetadata.fromVC(event.vc);

View File

@@ -2,17 +2,17 @@
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
'xstate.init': { type: 'xstate.init' };
internalEvents: {
'xstate.init': {type: 'xstate.init'};
};
'invokeSrcNameMap': {};
'missingImplementations': {
invokeSrcNameMap: {};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
'eventsCausingActions': {
eventsCausingActions: {
getReceivedVcsResponse: 'GET_RECEIVED_VCS';
getVcItemResponse: 'GET_VC_ITEM';
loadMyVcs: 'REFRESH_MY_VCS' | 'xstate.init';
@@ -21,19 +21,20 @@ export interface Typegen0 {
prependToMyVcs: 'VC_ADDED';
prependToReceivedVcs: 'VC_RECEIVED';
removeVcFromMyVcs: 'REMOVE_VC_FROM_CONTEXT';
setDownloadedVCFromOpenId4VCI: 'VC_DOWNLOADED_FROM_OPENID4VCI';
setDownloadedVc: 'VC_DOWNLOADED';
setMyVcs: 'STORE_RESPONSE';
setReceivedVcs: 'STORE_RESPONSE';
setUpdateVc: 'VC_UPDATED';
setUpdatedVcMetadatas: 'VC_METADATA_UPDATED';
setVcUpdate: 'VC_UPDATE';
updateMyVcs: 'VC_UPDATED';
updateMyVcs: 'VC_METADATA_UPDATED';
};
'eventsCausingDelays': {};
'eventsCausingGuards': {
eventsCausingDelays: {};
eventsCausingGuards: {
hasExistingReceivedVc: 'VC_RECEIVED';
};
'eventsCausingServices': {};
'matchesStates':
eventsCausingServices: {};
matchesStates:
| 'init'
| 'init.myVcs'
| 'init.receivedVcs'
@@ -54,5 +55,5 @@ export interface Typegen0 {
receivedVcs?: 'idle' | 'refreshing';
};
};
'tags': never;
tags: never;
}

3305
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -44,14 +44,17 @@
"expo-updates": "~0.16.4",
"i18next": "^21.6.16",
"iso-639-3": "^3.0.1",
"jwt-decode": "^3.1.2",
"mosip-inji-face-sdk": "^0.1.12",
"node-forge": "^1.3.1",
"node-jose": "^2.2.0",
"patch-package": "^6.5.1",
"postinstall-postinstall": "^2.1.0",
"react": "18.2.0",
"react-i18next": "^11.16.6",
"react-native": "0.71.8",
"react-native-animated-pagination-dot": "^0.4.0",
"react-native-app-auth": "^7.0.0",
"react-native-app-intro-slider": "^4.0.4",
"react-native-argon2": "^2.0.1",
"react-native-biometrics-changed": "^1.1.8",
@@ -79,6 +82,7 @@
"react-native-secure-keystore": "github:mosip/secure-keystore#v0.1.1",
"react-native-securerandom": "^1.0.1",
"react-native-simple-markdown": "^1.1.0",
"react-native-spinkit": "^1.5.1",
"react-native-svg": "13.4.0",
"react-native-swipe-gestures": "^1.0.5",
"react-native-tuvali": "github:mosip/tuvali#v0.4.4",

View File

@@ -4,24 +4,20 @@ import {
BottomTabScreenProps,
} from '@react-navigation/bottom-tabs';
import {Image} from 'react-native';
import {HomeScreen} from '../screens/Home/HomeScreen';
import {RootStackParamList} from './index';
import {ScanLayout} from '../screens/Scan/ScanLayout';
import {HistoryScreen} from '../screens/History/HistoryScreen';
import i18n from '../i18n';
import {BOTTOM_TAB_ROUTES} from './routesConstants';
import {HomeScreenLayout} from '../screens/HomeScreenLayout';
const home: TabScreen = {
name: BOTTOM_TAB_ROUTES.home,
component: HomeScreen,
component: HomeScreenLayout,
icon: 'home',
options: {
headerTitle: '',
headerLeft: () =>
React.createElement(Image, {
source: require('../assets/inji-home-logo.png'),
style: {width: 124, height: 27, resizeMode: 'contain'},
}),
headerShown: false,
},
};
export const scan: TabScreen = {

View File

@@ -1,21 +1,62 @@
import React from 'react';
import { Tab } from 'react-native-elements';
import { Column, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { HomeRouteProps } from '../../routes/main';
import { MyVcsTab } from './MyVcsTab';
import { ReceivedVcsTab } from './ReceivedVcsTab';
import { ViewVcModal } from './ViewVcModal';
import { useHomeScreen } from './HomeScreenController';
import { TabRef } from './HomeScreenMachine';
import { useTranslation } from 'react-i18next';
import { ActorRefFrom } from 'xstate';
import { vcItemMachine } from '../../machines/vcItem';
import React, {useEffect} from 'react';
import {Icon, Tab} from 'react-native-elements';
import {Button, Column, Text} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
import {HomeRouteProps} from '../../routes/main';
import {MyVcsTab} from './MyVcsTab';
import {ReceivedVcsTab} from './ReceivedVcsTab';
import {ViewVcModal} from './ViewVcModal';
import {useHomeScreen} from './HomeScreenController';
import {TabRef} from './HomeScreenMachine';
import {useTranslation} from 'react-i18next';
import {ActorRefFrom} from 'xstate';
import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {isOpenId4VCIEnabled} from '../../shared/openId4VCI/Utils';
import LinearGradient from 'react-native-linear-gradient';
import {EsignetMosipVCItemMachine} from '../../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
export const HomeScreen: React.FC<HomeRouteProps> = (props) => {
const { t } = useTranslation('HomeScreen');
export const HomeScreen: React.FC<HomeRouteProps> = props => {
const {t} = useTranslation('HomeScreen');
const controller = useHomeScreen(props);
useEffect(() => {
if (controller.IssuersService) {
navigateToIssuers();
}
}, [controller.IssuersService]);
const navigateToIssuers = () => {
props.navigation.navigate('IssuersScreen', {
service: controller.IssuersService,
});
};
const DownloadFABIcon: React.FC = () => {
const plusIcon = (
<Icon
name={'plus'}
type={'entypo'}
size={36}
color={Theme.Colors.whiteText}
/>
);
return (
<LinearGradient
colors={Theme.Colors.gradientBtn}
style={Theme.Styles.downloadFabIcon}>
<Button
testID="downloadIcon"
icon={plusIcon}
onPress={() => {
controller.GOTO_ISSUERS();
}}
type={'clearAddIdBtnBg'}
fill
/>
</LinearGradient>
);
};
return (
<React.Fragment>
<Column fill backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
@@ -34,6 +75,7 @@ export const HomeScreen: React.FC<HomeRouteProps> = (props) => {
</Column>
)}
</Column>
{isOpenId4VCIEnabled() && <DownloadFABIcon />}
{controller.selectedVc && (
<ViewVcModal
isVisible={controller.isViewingVc}
@@ -65,5 +107,7 @@ function TabItem(title: string) {
export interface HomeScreenTabProps {
isVisible: boolean;
service: TabRef;
vcItemActor: ActorRefFrom<typeof vcItemMachine>;
vcItemActor:
| ActorRefFrom<typeof ExistingMosipVCItemMachine>
| ActorRefFrom<typeof EsignetMosipVCItemMachine>;
}

View File

@@ -1,7 +1,7 @@
import { useInterpret, useSelector } from '@xstate/react';
import { useContext, useEffect, useRef } from 'react';
import { HomeRouteProps } from '../../routes/main';
import { GlobalContext } from '../../shared/GlobalContext';
import {useInterpret, useSelector} from '@xstate/react';
import {useContext, useEffect, useRef} from 'react';
import {HomeRouteProps} from '../../routes/main';
import {GlobalContext} from '../../shared/GlobalContext';
import {
HomeScreenEvents,
HomeScreenMachine,
@@ -10,16 +10,17 @@ import {
selectTabRefs,
selectTabsLoaded,
selectViewingVc,
selectIssuersMachine,
} from './HomeScreenMachine';
import { VcEvents } from '../../machines/vc';
import {VcEvents} from '../../machines/vc';
export function useHomeScreen(props: HomeRouteProps) {
const { appService } = useContext(GlobalContext);
const {appService} = useContext(GlobalContext);
const machine = useRef(
HomeScreenMachine.withContext({
...HomeScreenMachine.context,
serviceRefs: appService.getSnapshot().context.serviceRefs,
})
}),
);
const service = useInterpret(machine.current);
const vcService = appService.children.get('vc');
@@ -40,6 +41,9 @@ export function useHomeScreen(props: HomeRouteProps) {
isViewingVc: useSelector(service, selectViewingVc),
haveTabsLoaded: useSelector(service, selectTabsLoaded),
IssuersService: useSelector(service, selectIssuersMachine),
GOTO_ISSUERS: () => service.send(HomeScreenEvents.GOTO_ISSUERS()),
SELECT_TAB,
DISMISS_MODAL: () => service.send(HomeScreenEvents.DISMISS_MODAL()),
REVOKE: () => {

View File

@@ -1,19 +1,14 @@
import {
ActorRefFrom,
assign,
EventFrom,
send,
spawn,
StateFrom,
} from 'xstate';
import { createModel } from 'xstate/lib/model';
import { vcItemMachine } from '../../machines/vcItem';
import { AppServices } from '../../shared/GlobalContext';
import { createMyVcsTabMachine, MyVcsTabMachine } from './MyVcsTabMachine';
import {ActorRefFrom, assign, EventFrom, send, spawn, StateFrom} from 'xstate';
import {createModel} from 'xstate/lib/model';
import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {AppServices} from '../../shared/GlobalContext';
import {createMyVcsTabMachine, MyVcsTabMachine} from './MyVcsTabMachine';
import {
createReceivedVcsTabMachine,
ReceivedVcsTabMachine,
} from './ReceivedVcsTabMachine';
import {EsignetMosipVCItemMachine} from '../../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
import {IssuersMachine} from '../../machines/issuersMachine';
const model = createModel(
{
@@ -22,7 +17,9 @@ const model = createModel(
myVcs: {} as ActorRefFrom<typeof MyVcsTabMachine>,
receivedVcs: {} as ActorRefFrom<typeof ReceivedVcsTabMachine>,
},
selectedVc: null as ActorRefFrom<typeof vcItemMachine>,
selectedVc: null as
| ActorRefFrom<typeof ExistingMosipVCItemMachine>
| ActorRefFrom<typeof EsignetMosipVCItemMachine>,
activeTab: 0,
},
{
@@ -30,12 +27,18 @@ const model = createModel(
SELECT_MY_VCS: () => ({}),
SELECT_RECEIVED_VCS: () => ({}),
SELECT_HISTORY: () => ({}),
VIEW_VC: (vcItemActor: ActorRefFrom<typeof vcItemMachine>) => ({
VIEW_VC: (
vcItemActor:
| ActorRefFrom<typeof ExistingMosipVCItemMachine>
| ActorRefFrom<typeof EsignetMosipVCItemMachine>,
) => ({
vcItemActor,
}),
DISMISS_MODAL: () => ({}),
GOTO_ISSUERS: () => ({}),
DOWNLOAD_ID: () => ({}),
},
}
},
);
const MY_VCS_TAB_REF_ID = 'myVcsTab';
@@ -66,6 +69,7 @@ export const HomeScreenMachine = model.createMachine(
SELECT_MY_VCS: '.myVcs',
SELECT_RECEIVED_VCS: '.receivedVcs',
SELECT_HISTORY: '.history',
GOTO_ISSUERS: '.gotoIssuers',
},
states: {
init: {
@@ -80,7 +84,7 @@ export const HomeScreenMachine = model.createMachine(
DISMISS_MODAL: {
actions: [
send('DISMISS', {
to: (context) => context.tabRefs.myVcs,
to: context => context.tabRefs.myVcs,
}),
],
},
@@ -92,7 +96,7 @@ export const HomeScreenMachine = model.createMachine(
DISMISS_MODAL: {
actions: [
send('DISMISS', {
to: (context) => context.tabRefs.receivedVcs,
to: context => context.tabRefs.receivedVcs,
}),
],
},
@@ -101,6 +105,29 @@ export const HomeScreenMachine = model.createMachine(
history: {
entry: [setActiveTab(2)],
},
gotoIssuers: {
invoke: {
id: 'issuersMachine',
src: IssuersMachine,
data: context => ({
...IssuersMachine.context,
serviceRefs: context.serviceRefs, // the value you want to pass to child machine
}),
onDone: 'idle',
},
on: {
DOWNLOAD_ID: {
actions: 'sendAddEvent',
target: 'idle',
},
GOTO_ISSUERS: 'gotoIssuers',
},
},
idle: {
on: {
GOTO_ISSUERS: 'gotoIssuers',
},
},
},
},
modals: {
@@ -127,18 +154,22 @@ export const HomeScreenMachine = model.createMachine(
{
actions: {
spawnTabActors: assign({
tabRefs: (context) => ({
tabRefs: context => ({
myVcs: spawn(
createMyVcsTabMachine(context.serviceRefs),
MY_VCS_TAB_REF_ID
MY_VCS_TAB_REF_ID,
),
receivedVcs: spawn(
createReceivedVcsTabMachine(context.serviceRefs),
RECEIVED_VCS_TAB_REF_ID
RECEIVED_VCS_TAB_REF_ID,
),
}),
}),
sendAddEvent: send('ADD_VC', {
to: context => context.tabRefs.myVcs,
}),
setSelectedVc: model.assign({
selectedVc: (_, event) => event.vcItemActor,
}),
@@ -147,15 +178,19 @@ export const HomeScreenMachine = model.createMachine(
selectedVc: null,
}),
},
}
},
);
function setActiveTab(activeTab: number) {
return model.assign({ activeTab });
return model.assign({activeTab});
}
type State = StateFrom<typeof HomeScreenMachine>;
export function selectIssuersMachine(state: State) {
return state.children.issuersMachine as ActorRefFrom<typeof IssuersMachine>;
}
export function selectTabRefs(state: State) {
return state.context.tabRefs;
}

View File

@@ -2,39 +2,50 @@
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
internalEvents: {
'xstate.after(100)#HomeScreen.tabs.init': {
type: 'xstate.after(100)#HomeScreen.tabs.init';
};
'xstate.init': { type: 'xstate.init' };
'xstate.init': {type: 'xstate.init'};
};
'invokeSrcNameMap': {};
'missingImplementations': {
invokeSrcNameMap: {};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
'eventsCausingActions': {
eventsCausingActions: {
resetSelectedVc: 'DISMISS_MODAL' | 'xstate.init';
sendAddEvent: 'DOWNLOAD_ID';
setSelectedVc: 'VIEW_VC';
spawnTabActors: 'xstate.init';
};
'eventsCausingDelays': {};
'eventsCausingGuards': {};
'eventsCausingServices': {};
'matchesStates':
eventsCausingDelays: {};
eventsCausingGuards: {};
eventsCausingServices: {
issuersMachine: 'GOTO_ISSUERS';
};
matchesStates:
| 'modals'
| 'modals.none'
| 'modals.viewingVc'
| 'tabs'
| 'tabs.gotoIssuers'
| 'tabs.history'
| 'tabs.idle'
| 'tabs.init'
| 'tabs.myVcs'
| 'tabs.receivedVcs'
| {
modals?: 'none' | 'viewingVc';
tabs?: 'history' | 'init' | 'myVcs' | 'receivedVcs';
tabs?:
| 'gotoIssuers'
| 'history'
| 'idle'
| 'init'
| 'myVcs'
| 'receivedVcs';
};
'tags': never;
tags: never;
}

View File

@@ -5,7 +5,7 @@ import {Modal} from '../../../components/ui/Modal';
import {Centered, Column, Text} from '../../../components/ui';
import {ActivityLogText} from '../../../components/ActivityLogText';
import {ActorRefFrom} from 'xstate';
import {vcItemMachine} from '../../../machines/vcItem';
import {ExistingMosipVCItemMachine} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {useKebabPopUp} from '../../../components/KebabPopUpController';
import {Theme} from '../../../components/ui/styleUtils';
import {VCMetadata} from '../../../shared/VCMetadata';
@@ -67,5 +67,5 @@ export interface HistoryTabProps {
testID?: string;
label: string;
vcMetadata: VCMetadata;
service: ActorRefFrom<typeof vcItemMachine>;
service: ActorRefFrom<typeof ExistingMosipVCItemMachine>;
}

View File

@@ -9,7 +9,7 @@ import {MessageOverlay} from '../../../components/MessageOverlay';
import {useKebabPopUp} from '../../../components/KebabPopUpController';
import {Dimensions} from 'react-native';
import {ActorRefFrom} from 'xstate';
import {vcItemMachine} from '../../../machines/vcItem';
import {ExistingMosipVCItemMachine} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import testIDProps from '../../../shared/commonUtil';
export const WalletBinding: React.FC<WalletBindingProps> = props => {
@@ -102,5 +102,5 @@ interface WalletBindingProps {
label: string;
content?: string;
Icon?: string;
service: ActorRefFrom<typeof vcItemMachine>;
service: ActorRefFrom<typeof ExistingMosipVCItemMachine>;
}

View File

@@ -1,34 +1,33 @@
import { useSelector, useInterpret } from '@xstate/react';
import { useContext, useRef, useState } from 'react';
import { GlobalContext } from '../../../shared/GlobalContext';
import { selectMyVcsMetadata, VcEvents } from '../../../machines/vc';
import {useSelector, useInterpret} from '@xstate/react';
import {useContext, useRef, useState} from 'react';
import {GlobalContext} from '../../../shared/GlobalContext';
import {selectMyVcsMetadata, VcEvents} from '../../../machines/vc';
import {
createVcItemMachine,
createExistingMosipVCItemMachine,
isShowingBindingWarning,
selectAcceptingBindingOtp,
isWalletBindingInProgress,
VcItemEvents,
ExistingMosipVCItemEvents,
selectIsAcceptingOtpInput,
selectOtpError,
selectShowWalletBindingError,
selectWalletBindingError,
} from '../../../machines/vcItem';
import { useTranslation } from 'react-i18next';
} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {useTranslation} from 'react-i18next';
import { ActorRefFrom } from 'xstate';
import {ActorRefFrom} from 'xstate';
export function useWalletBinding(props) {
const { t } = useTranslation('ProfileScreen');
const { appService } = useContext(GlobalContext);
const {t} = useTranslation('ProfileScreen');
const {appService} = useContext(GlobalContext);
const machine = useRef(
createVcItemMachine(
createExistingMosipVCItemMachine(
appService.getSnapshot().context.serviceRefs,
props.vcMetadata
)
props.vcMetadata,
),
);
const bindingService = useInterpret(machine.current, { devTools: __DEV__ });
const bindingService = useInterpret(machine.current, {devTools: __DEV__});
const vcService = appService.children.get('vc');
@@ -52,7 +51,7 @@ export function useWalletBinding(props) {
const WalletBindingInProgress = useSelector(
bindingService,
isWalletBindingInProgress
isWalletBindingInProgress,
);
return {
@@ -66,16 +65,16 @@ export function useWalletBinding(props) {
isAcceptingOtpInput: useSelector(bindingService, selectIsAcceptingOtpInput),
isAcceptingBindingOtp: useSelector(
bindingService,
selectAcceptingBindingOtp
selectAcceptingBindingOtp,
),
isBindingError: useSelector(bindingService, selectShowWalletBindingError),
walletBindingError: useSelector(bindingService, selectWalletBindingError),
DISMISS: () => bindingService.send(VcItemEvents.DISMISS()),
DISMISS: () => bindingService.send(ExistingMosipVCItemEvents.DISMISS()),
CONFIRM: () => bindingService.send(VcItemEvents.CONFIRM()),
CONFIRM: () => bindingService.send(ExistingMosipVCItemEvents.CONFIRM()),
CANCEL: () => bindingService.send(VcItemEvents.CANCEL()),
CANCEL: () => bindingService.send(ExistingMosipVCItemEvents.CANCEL()),
REFRESH: () => vcService.send(VcEvents.REFRESH_MY_VCS()),
setAuthenticating,

View File

@@ -7,7 +7,7 @@ import {HomeScreenTabProps} from './HomeScreen';
import {AddVcModal} from './MyVcs/AddVcModal';
import {GetVcModal} from './MyVcs/GetVcModal';
import {useTranslation} from 'react-i18next';
import {VcItem} from '../../components/VcItem';
import {ExistingMosipVCItem} from '../../components/VC/ExistingMosipVCItem/ExistingMosipVCItem';
import {GET_INDIVIDUAL_ID} from '../../shared/constants';
import {
ErrorMessageOverlay,
@@ -15,6 +15,8 @@ import {
} from '../../components/MessageOverlay';
import {Icon} from 'react-native-elements';
import {groupBy} from '../../shared/javascript';
import {isOpenId4VCIEnabled} from '../../shared/openId4VCI/Utils';
import {VcItemContainer} from '../../components/VC/VcItemContainer';
const pinIconProps = {iconName: 'pushpin', iconType: 'antdesign'};
@@ -87,7 +89,7 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = props => {
{vcMetadataOrderedByPinStatus.map((vcMetadata, index) => {
const iconProps = vcMetadata.isPinned ? pinIconProps : {};
return (
<VcItem
<VcItemContainer
{...iconProps}
key={`${vcMetadata.getVcKey()}-${index}`}
vcMetadata={vcMetadata}
@@ -97,13 +99,15 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = props => {
);
})}
</Column>
<Button
testID="downloadCard"
type="gradient"
disabled={controller.isRefreshingVcs}
title={t('downloadCard')}
onPress={controller.DOWNLOAD_ID}
/>
{!isOpenId4VCIEnabled() && (
<Button
testID="downloadCard"
type="gradient"
disabled={controller.isRefreshingVcs}
title={t('downloadCard')}
onPress={controller.DOWNLOAD_ID}
/>
)}
</React.Fragment>
)}
{controller.vcMetadatas.length === 0 && (
@@ -125,13 +129,15 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = props => {
margin="0 12 30 12">
{t('generateVcDescription')}
</Text>
<Button
testID="downloadCard"
type="gradient"
disabled={controller.isRefreshingVcs}
title={t('downloadCard')}
onPress={controller.DOWNLOAD_ID}
/>
{!isOpenId4VCIEnabled() && (
<Button
testID="downloadCard"
type="gradient"
disabled={controller.isRefreshingVcs}
title={t('downloadCard')}
onPress={controller.DOWNLOAD_ID}
/>
)}
</Column>
</React.Fragment>
)}

View File

@@ -10,8 +10,8 @@ import {
import {
selectWalletBindingError,
selectShowWalletBindingError,
} from '../../machines/vcItem';
import {vcItemMachine} from '../../machines/vcItem';
} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {GlobalContext} from '../../shared/GlobalContext';
import {HomeScreenTabProps} from './HomeScreen';
import {
@@ -27,6 +27,7 @@ import {
selectShowHardwareKeystoreNotExistsAlert,
SettingsEvents,
} from '../../machines/settings';
import {EsignetMosipVCItemMachine} from '../../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
export function useMyVcsTab(props: HomeScreenTabProps) {
const service = props.service as ActorRefFrom<typeof MyVcsTabMachine>;
@@ -64,7 +65,11 @@ export function useMyVcsTab(props: HomeScreenTabProps) {
REFRESH: () => vcService.send(VcEvents.REFRESH_MY_VCS()),
VIEW_VC: (vcRef: ActorRefFrom<typeof vcItemMachine>) => {
VIEW_VC: (
vcRef:
| ActorRefFrom<typeof ExistingMosipVCItemMachine>
| ActorRefFrom<typeof EsignetMosipVCItemMachine>,
) => {
return service.send(MyVcsTabEvents.VIEW_VC(vcRef));
},

View File

@@ -9,13 +9,14 @@ import {
import {createModel} from 'xstate/lib/model';
import {StoreEvents, StoreResponseEvent} from '../../machines/store';
import {VcEvents} from '../../machines/vc';
import {vcItemMachine} from '../../machines/vcItem';
import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {AppServices} from '../../shared/GlobalContext';
import {MY_VCS_STORE_KEY} from '../../shared/constants';
import {AddVcModalMachine} from './MyVcs/AddVcModalMachine';
import {GetVcModalMachine} from './MyVcs/GetVcModalMachine';
import Storage from '../../shared/storage';
import {VCMetadata} from '../../shared/VCMetadata';
import {EsignetMosipVCItemMachine} from '../../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
const model = createModel(
{
@@ -24,7 +25,11 @@ const model = createModel(
{
events: {
REFRESH: () => ({}),
VIEW_VC: (vcItemActor: ActorRefFrom<typeof vcItemMachine>) => ({
VIEW_VC: (
vcItemActor:
| ActorRefFrom<typeof ExistingMosipVCItemMachine>
| ActorRefFrom<typeof EsignetMosipVCItemMachine>,
) => ({
vcItemActor,
}),
DISMISS: () => ({}),
@@ -35,6 +40,7 @@ const model = createModel(
STORAGE_AVAILABLE: () => ({}),
STORAGE_UNAVAILABLE: () => ({}),
IS_TAMPERED: () => ({}),
DOWNLOAD_VIA_ID: () => ({}),
},
},
);

View File

@@ -6,7 +6,7 @@ import {Centered, Column, Text} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
import {HomeScreenTabProps} from './HomeScreen';
import {useReceivedVcsTab} from './ReceivedVcsTabController';
import {VcItem} from '../../components/VcItem';
import {ExistingMosipVCItem} from '../../components/VC/ExistingMosipVCItem/ExistingMosipVCItem';
export const ReceivedVcsTab: React.FC<HomeScreenTabProps> = props => {
const {t} = useTranslation('ReceivedVcsTab');
@@ -24,7 +24,7 @@ export const ReceivedVcsTab: React.FC<HomeScreenTabProps> = props => {
/>
}>
{controller.receivedVcsMetadata.map(vcMetadata => (
<VcItem
<ExistingMosipVCItem
key={vcMetadata.getVcKey()}
vcMetadata={vcMetadata}
margin="0 2 8 2"

View File

@@ -1,18 +1,18 @@
import { useSelector, useInterpret } from '@xstate/react';
import { useContext, useRef, useState } from 'react';
import { ActorRefFrom } from 'xstate';
import {useSelector, useInterpret} from '@xstate/react';
import {useContext, useRef, useState} from 'react';
import {ActorRefFrom} from 'xstate';
import {
VcEvents,
selectIsRefreshingReceivedVcs,
selectReceivedVcsMetadata,
} from '../../machines/vc';
import { vcItemMachine } from '../../machines/vcItem';
import { GlobalContext } from '../../shared/GlobalContext';
import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {GlobalContext} from '../../shared/GlobalContext';
import {
ReceivedVcsTabEvents,
ReceivedVcsTabMachine,
} from './ReceivedVcsTabMachine';
import { MyVcsTabEvents, MyVcsTabMachine } from './MyVcsTabMachine';
import {MyVcsTabEvents, MyVcsTabMachine} from './MyVcsTabMachine';
import {
HomeScreenEvents,
HomeScreenMachine,
@@ -24,12 +24,12 @@ import {
export function useReceivedVcsTab() {
const [isVisible, setIsVisible] = useState(false);
const { appService } = useContext(GlobalContext);
const {appService} = useContext(GlobalContext);
const machine = useRef(
HomeScreenMachine.withContext({
...HomeScreenMachine.context,
serviceRefs: appService.getSnapshot().context.serviceRefs,
})
}),
);
const service = useInterpret(machine.current);
@@ -54,7 +54,7 @@ export function useReceivedVcsTab() {
TOGGLE_RECEIVED_CARDS: () => setIsVisible(!isVisible),
VIEW_VC: (vcRef: ActorRefFrom<typeof vcItemMachine>) => {
VIEW_VC: (vcRef: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => {
return myVcservice.send(MyVcsTabEvents.VIEW_VC(vcRef));
},
isViewingVc,

View File

@@ -1,7 +1,7 @@
import { ActorRefFrom, EventFrom, sendParent } from 'xstate';
import { createModel } from 'xstate/lib/model';
import { vcItemMachine } from '../../machines/vcItem';
import { AppServices } from '../../shared/GlobalContext';
import {ActorRefFrom, EventFrom, sendParent} from 'xstate';
import {createModel} from 'xstate/lib/model';
import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {AppServices} from '../../shared/GlobalContext';
const model = createModel(
{
@@ -10,17 +10,19 @@ const model = createModel(
},
{
events: {
VIEW_VC: (vcItemActor: ActorRefFrom<typeof vcItemMachine>) => ({
VIEW_VC: (
vcItemActor: ActorRefFrom<typeof ExistingMosipVCItemMachine>,
) => ({
vcItemActor,
}),
REFRESH: () => ({}),
DISMISS: () => ({}),
STORE_RESPONSE: (response?: unknown) => ({ response }),
STORE_ERROR: (error: Error) => ({ error }),
ERROR: (error: Error) => ({ error }),
GET_RECEIVED_VCS_RESPONSE: (vcMetadatas: string[]) => ({ vcMetadatas }),
STORE_RESPONSE: (response?: unknown) => ({response}),
STORE_ERROR: (error: Error) => ({error}),
ERROR: (error: Error) => ({error}),
GET_RECEIVED_VCS_RESPONSE: (vcMetadatas: string[]) => ({vcMetadatas}),
},
}
},
);
export const ReceivedVcsTabEvents = model.events;
@@ -53,10 +55,10 @@ export const ReceivedVcsTabMachine = model.createMachine(
{
actions: {
viewVcFromParent: sendParent((_context, event) =>
model.events.VIEW_VC(event.vcItemActor)
model.events.VIEW_VC(event.vcItemActor),
),
},
}
},
);
export function createReceivedVcsTabMachine(serviceRefs: AppServices) {

View File

@@ -1,20 +1,20 @@
import React from 'react';
import { DropdownIcon } from '../../components/DropdownIcon';
import { TextEditOverlay } from '../../components/TextEditOverlay';
import { Column, Text } from '../../components/ui';
import { Modal } from '../../components/ui/Modal';
import { MessageOverlay } from '../../components/MessageOverlay';
import { ToastItem } from '../../components/ui/ToastItem';
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 { OtpVerificationModal } from './MyVcs/OtpVerificationModal';
import { BindingVcWarningOverlay } from './MyVcs/BindingVcWarningOverlay';
import {DropdownIcon} from '../../components/DropdownIcon';
import {TextEditOverlay} from '../../components/TextEditOverlay';
import {Column, Text} from '../../components/ui';
import {Modal} from '../../components/ui/Modal';
import {MessageOverlay} from '../../components/MessageOverlay';
import {ToastItem} from '../../components/ui/ToastItem';
import {RevokeConfirmModal} from '../../components/RevokeConfirm';
import {OIDcAuthenticationModal} from '../../components/OIDcAuth';
import {useViewVcModal, ViewVcModalProps} from './ViewVcModalController';
import {useTranslation} from 'react-i18next';
import {OtpVerificationModal} from './MyVcs/OtpVerificationModal';
import {BindingVcWarningOverlay} from './MyVcs/BindingVcWarningOverlay';
import {VcDetailsContainer} from '../../components/VC/VcDetailsContainer';
export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
const { t } = useTranslation('ViewVcModal');
export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
const {t} = useTranslation('ViewVcModal');
const controller = useViewVcModal(props);
const DATA = [
@@ -39,7 +39,7 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
headerElevation={2}>
<Column scroll>
<Column fill>
<VcDetails
<VcDetailsContainer
vc={controller.vc}
onBinding={controller.addtoWallet}
isBindingPending={controller.isWalletBindingPending}

View File

@@ -1,10 +1,10 @@
import { useMachine, useSelector } from '@xstate/react';
import { useContext, useEffect, useState } from 'react';
import { ActorRefFrom } from 'xstate';
import { useTranslation } from 'react-i18next';
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 {ModalProps} from '../../components/ui/Modal';
import {GlobalContext} from '../../shared/GlobalContext';
import {
selectOtpError,
selectIsAcceptingOtpInput,
@@ -14,8 +14,8 @@ import {
selectIsRevokingVc,
selectIsLoggingRevoke,
selectVc,
VcItemEvents,
vcItemMachine,
ExistingMosipVCItemEvents,
ExistingMosipVCItemMachine,
selectWalletBindingError,
selectRequestBindingOtp,
selectAcceptingBindingOtp,
@@ -23,22 +23,22 @@ import {
selectWalletBindingInProgress,
selectShowWalletBindingError,
selectBindingWarning,
} from '../../machines/vcItem';
import { selectPasscode } from '../../machines/auth';
import { biometricsMachine, selectIsSuccess } from '../../machines/biometrics';
} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {selectPasscode} from '../../machines/auth';
import {biometricsMachine, selectIsSuccess} from '../../machines/biometrics';
export function useViewVcModal({
vcItemActor,
isVisible,
onRevokeDelete,
}: ViewVcModalProps) {
const { t } = useTranslation('ViewVcModal');
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 {appService} = useContext(GlobalContext);
const authService = appService.children.get('auth');
const [, bioSend, bioService] = useMachine(biometricsMachine);
@@ -49,10 +49,10 @@ export function useViewVcModal({
const vc = useSelector(vcItemActor, selectVc);
const otError = useSelector(vcItemActor, selectOtpError);
const onSuccess = () => {
bioSend({ type: 'SET_IS_AVAILABLE', data: true });
bioSend({type: 'SET_IS_AVAILABLE', data: true});
setError('');
setReAuthenticating('');
vcItemActor.send(VcItemEvents.LOCK_VC());
vcItemActor.send(ExistingMosipVCItemEvents.LOCK_VC());
};
const onError = (value: string) => {
@@ -69,11 +69,11 @@ export function useViewVcModal({
};
const netInfoFetch = (otp: string) => {
NetInfo.fetch().then((state) => {
NetInfo.fetch().then(state => {
if (state.isConnected) {
vcItemActor.send(VcItemEvents.INPUT_OTP(otp));
vcItemActor.send(ExistingMosipVCItemEvents.INPUT_OTP(otp));
} else {
vcItemActor.send(VcItemEvents.DISMISS());
vcItemActor.send(ExistingMosipVCItemEvents.DISMISS());
showToast('Request network failed');
}
});
@@ -84,10 +84,10 @@ export function useViewVcModal({
showToast(vc.locked ? t('success.locked') : t('success.unlocked'));
}
if (isRevokingVc) {
showToast(t('success.revoked', { vid: vc.id }));
showToast(t('success.revoked', {vid: vc.id}));
}
if (isLoggingRevoke) {
vcItemActor.send(VcItemEvents.DISMISS());
vcItemActor.send(ExistingMosipVCItemEvents.DISMISS());
onRevokeDelete();
}
if (isSuccessBio && reAuthenticating != '') {
@@ -104,7 +104,7 @@ export function useViewVcModal({
]);
useEffect(() => {
vcItemActor.send(VcItemEvents.REFRESH());
vcItemActor.send(ExistingMosipVCItemEvents.REFRESH());
}, [isVisible]);
return {
error,
@@ -120,7 +120,7 @@ export function useViewVcModal({
isAcceptingOtpInput: useSelector(vcItemActor, selectIsAcceptingOtpInput),
isAcceptingRevokeInput: useSelector(
vcItemActor,
selectIsAcceptingRevokeInput
selectIsAcceptingRevokeInput,
),
storedPasscode: useSelector(authService, selectPasscode),
isBindingOtp: useSelector(vcItemActor, selectRequestBindingOtp),
@@ -128,11 +128,11 @@ export function useViewVcModal({
walletBindingError: useSelector(vcItemActor, selectWalletBindingError),
isWalletBindingPending: useSelector(
vcItemActor,
selectEmptyWalletBindingId
selectEmptyWalletBindingId,
),
isWalletBindingInProgress: useSelector(
vcItemActor,
selectWalletBindingInProgress
selectWalletBindingInProgress,
),
isBindingError: useSelector(vcItemActor, selectShowWalletBindingError),
isBindingWarning: useSelector(vcItemActor, selectBindingWarning),
@@ -141,17 +141,17 @@ export function useViewVcModal({
setRevoking(true);
},
REVOKE_VC: () => {
vcItemActor.send(VcItemEvents.REVOKE_VC());
vcItemActor.send(ExistingMosipVCItemEvents.REVOKE_VC());
setRevoking(false);
},
setReAuthenticating,
setRevoking,
onError,
addtoWallet: () => {
vcItemActor.send(VcItemEvents.ADD_WALLET_BINDING_ID());
vcItemActor.send(ExistingMosipVCItemEvents.ADD_WALLET_BINDING_ID());
},
lockVc: () => {
vcItemActor.send(VcItemEvents.LOCK_VC());
vcItemActor.send(ExistingMosipVCItemEvents.LOCK_VC());
},
inputOtp: (otp: string) => {
netInfoFetch(otp);
@@ -159,20 +159,23 @@ export function useViewVcModal({
revokeVc: (otp: string) => {
netInfoFetch(otp);
},
ADD_WALLET: () => vcItemActor.send(VcItemEvents.ADD_WALLET_BINDING_ID()),
ADD_WALLET: () =>
vcItemActor.send(ExistingMosipVCItemEvents.ADD_WALLET_BINDING_ID()),
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()),
INPUT_OTP: (otp: string) => vcItemActor.send(VcItemEvents.INPUT_OTP(otp)),
CANCEL: () => vcItemActor.send(VcItemEvents.CANCEL()),
CONFIRM: () => vcItemActor.send(VcItemEvents.CONFIRM()),
EDIT_TAG: () => vcItemActor.send(ExistingMosipVCItemEvents.EDIT_TAG()),
SAVE_TAG: (tag: string) =>
vcItemActor.send(ExistingMosipVCItemEvents.SAVE_TAG(tag)),
DISMISS: () => vcItemActor.send(ExistingMosipVCItemEvents.DISMISS()),
LOCK_VC: () => vcItemActor.send(ExistingMosipVCItemEvents.LOCK_VC()),
INPUT_OTP: (otp: string) =>
vcItemActor.send(ExistingMosipVCItemEvents.INPUT_OTP(otp)),
CANCEL: () => vcItemActor.send(ExistingMosipVCItemEvents.CANCEL()),
CONFIRM: () => vcItemActor.send(ExistingMosipVCItemEvents.CONFIRM()),
};
}
export interface ViewVcModalProps extends ModalProps {
vcItemActor: ActorRefFrom<typeof vcItemMachine>;
vcItemActor: ActorRefFrom<typeof ExistingMosipVCItemMachine>;
onDismiss: () => void;
onRevokeDelete: () => void;
activeTab: Number;

View File

@@ -0,0 +1,102 @@
import {getFocusedRouteNameFromRoute} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Image} from 'react-native';
import {Icon} from 'react-native-elements';
import {HelpScreen} from '../components/HelpScreen';
import {Row} from '../components/ui';
import {Header} from '../components/ui/Header';
import {Theme} from '../components/ui/styleUtils';
import {RootRouteProps} from '../routes';
import {HomeScreen} from './Home/HomeScreen';
import {IssuersScreen} from './Issuers/IssuersScreen';
import {SettingScreen} from './Settings/SettingScreen';
const {Navigator, Screen} = createNativeStackNavigator();
export const HomeScreenLayout: React.FC<RootRouteProps> = props => {
const {t} = useTranslation('IssuersScreen');
React.useLayoutEffect(() => {
const routeName = getFocusedRouteNameFromRoute(props.route);
if (routeName === 'IssuersScreen') {
props.navigation.setOptions({tabBarStyle: {display: 'none'}});
} else {
props.navigation.setOptions({
tabBarShowLabel: true,
tabBarActiveTintColor: Theme.Colors.IconBg,
tabBarLabelStyle: {
fontSize: 12,
fontFamily: 'Inter_600SemiBold',
},
tabBarStyle: {
height: 75,
paddingHorizontal: 10,
},
tabBarItemStyle: {
height: 83,
padding: 11,
},
});
}
}, [props.navigation, props.route]);
const HomeScreenOptions = {
headerLeft: () =>
React.createElement(Image, {
source: Theme.InjiHomeLogo,
style: {width: 124, height: 27, resizeMode: 'contain'},
}),
headerTitle: '',
headerRight: () => (
<Row align="space-between">
<HelpScreen
triggerComponent={
<Image source={Theme.HelpIcon} style={{width: 36, height: 36}} />
}
navigation={undefined}
route={undefined}
/>
<SettingScreen
triggerComponent={
<Icon
name="settings"
type="simple-line-icon"
size={21}
style={Theme.Styles.IconContainer}
color={Theme.Colors.Icon}
/>
}
navigation={props.navigation}
route={undefined}
/>
</Row>
),
};
return (
<Navigator>
<Screen
key={'HomeScreen'}
name={'HomeScreen'}
component={HomeScreen}
options={HomeScreenOptions}
/>
<Screen
key={'Issuers'}
name={'IssuersScreen'}
component={IssuersScreen}
options={{
header: props => (
<Header
goBack={props.navigation.goBack}
title={t('title')}
testID="issuersScreenHeader"
/>
),
}}
/>
</Navigator>
);
};

View File

@@ -0,0 +1,45 @@
import {useSelector} from '@xstate/react';
import {
IssuerScreenTabEvents,
IssuersMachine,
selectErrorMessage,
selectIsDone,
selectIsDownloadCredentials,
selectIsIdle,
selectIssuers,
selectLoadingReason,
selectStoring,
} from '../../machines/issuersMachine';
import {ActorRefFrom} from 'xstate';
import {BOTTOM_TAB_ROUTES} from '../../routes/routesConstants';
export function useIssuerScreenController({route, navigation}) {
const service = route.params.service;
return {
issuers: useSelector(service, selectIssuers),
errorMessage: useSelector(service, selectErrorMessage),
isDownloadingCredentials: useSelector(service, selectIsDownloadCredentials),
isDone: useSelector(service, selectIsDone),
isIdle: useSelector(service, selectIsIdle),
loadingReason: useSelector(service, selectLoadingReason),
isStoring: useSelector(service, selectStoring),
CANCEL: () => service.send(IssuerScreenTabEvents.CANCEL()),
SELECTED_ISSUER: id =>
service.send(IssuerScreenTabEvents.SELECTED_ISSUER(id)),
DISMISS: () => service.send(IssuerScreenTabEvents.DISMISS()),
TRY_AGAIN: () => service.send(IssuerScreenTabEvents.TRY_AGAIN()),
RESET_ERROR: () => service.send(IssuerScreenTabEvents.RESET_ERROR()),
DOWNLOAD_ID: () => {
service.send(IssuerScreenTabEvents.DOWNLOAD_ID());
navigation.navigate(BOTTOM_TAB_ROUTES.home, {screen: 'HomeScreen'});
},
};
}
export interface IssuerModalProps {
service?: ActorRefFrom<typeof IssuersMachine>;
onPress?: () => void;
isVisible?: boolean;
}

View File

@@ -0,0 +1,141 @@
import React, {useLayoutEffect} from 'react';
import {useTranslation} from 'react-i18next';
import {FlatList, Image, Text, View} from 'react-native';
import {Issuer} from '../../components/openId4VCI/Issuer';
import {Error} from '../../components/ui/Error';
import {Header} from '../../components/ui/Header';
import {Column} from '../../components/ui/Layout';
import {Theme} from '../../components/ui/styleUtils';
import {RootRouteProps} from '../../routes';
import {HomeRouteProps} from '../../routes/main';
import {useIssuerScreenController} from './IssuerScreenController';
import {Loader} from '../../components/ui/Loader';
import testIDProps from '../../shared/commonUtil';
export const IssuersScreen: React.FC<
HomeRouteProps | RootRouteProps
> = props => {
const controller = useIssuerScreenController(props);
const {t} = useTranslation('IssuersScreen');
useLayoutEffect(() => {
if (controller.loadingReason || controller.errorMessage) {
props.navigation.setOptions({
headerShown: false,
});
} else {
props.navigation.setOptions({
headerShown: true,
header: props => (
<Header
goBack={props.navigation.goBack}
title={t('title')}
testID="issuersScreenHeader"
/>
),
});
}
if (controller.isStoring) {
props.navigation.goBack();
}
}, [controller.loadingReason, controller.errorMessage, controller.isStoring]);
const onPressHandler = (id: string) => {
if (id !== 'UIN, VID, AID') {
controller.SELECTED_ISSUER(id);
} else {
controller.DOWNLOAD_ID();
}
};
const isGenericError = () => {
return controller.errorMessage === 'generic';
};
const goBack = () => {
controller.RESET_ERROR();
setTimeout(() => {
props.navigation.goBack();
}, 0);
};
const getImage = () => {
if (isGenericError()) {
return (
<Image
source={Theme.SomethingWentWrong}
style={{width: 370, height: 150}}
{...testIDProps('somethingWentWrongImage')}
/>
);
}
return (
<Image
{...testIDProps('noInternetConnectionImage')}
source={Theme.NoInternetConnection}
/>
);
};
if (controller.loadingReason) {
return (
<Loader
isVisible
title={t('loaders.loading')}
subTitle={t(`loaders.subTitle.${controller.loadingReason}`)}
progress
/>
);
}
return (
<React.Fragment>
{controller.issuers.length > 0 && (
<Column style={Theme.issuersScreenStyles.issuerListOuterContainer}>
<Text
{...testIDProps('addCardDescription')}
style={{
...Theme.TextStyles.regularGrey,
marginVertical: 14,
marginHorizontal: 9,
}}>
{t('header')}
</Text>
<View style={Theme.issuersScreenStyles.issuersContainer}>
{controller.issuers.length > 0 && (
<FlatList
data={controller.issuers}
scrollEnabled={false}
renderItem={({item}) => (
<Issuer
testID={`issuer-${item.id}`}
key={item.id}
id={item.id}
displayName={item.displayName}
logoUrl={item.logoUrl}
onPress={() => onPressHandler(item.id)}
{...props}
/>
)}
numColumns={2}
keyExtractor={item => item.id}
/>
)}
</View>
</Column>
)}
{controller.errorMessage && (
<Error
testID={`${controller.errorMessage}Error`}
isVisible={controller.errorMessage !== ''}
title={t(`errors.${controller.errorMessage}.title`)}
message={t(`errors.${controller.errorMessage}.message`)}
goBack={goBack}
tryAgain={isGenericError() ? null : controller.TRY_AGAIN}
image={getImage()}
/>
)}
</React.Fragment>
);
};

View File

@@ -1,16 +1,16 @@
import React from 'react';
import { Button, Column, Text, Centered } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { useTranslation } from 'react-i18next';
import { VcItem } from '../../components/VcItem';
import { useQrLogin } from './QrLoginController';
import { QrLoginRef } from '../../machines/QrLoginMachine';
import { Icon } from 'react-native-elements';
import { Modal } from '../../components/ui/Modal';
import {Button, Column, Text, Centered} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
import {useTranslation} from 'react-i18next';
import {ExistingMosipVCItem} from '../../components/VC/ExistingMosipVCItem/ExistingMosipVCItem';
import {useQrLogin} from './QrLoginController';
import {QrLoginRef} from '../../machines/QrLoginMachine';
import {Icon} from 'react-native-elements';
import {Modal} from '../../components/ui/Modal';
export const MyBindedVcs: React.FC<MyBindedVcsProps> = (props) => {
export const MyBindedVcs: React.FC<MyBindedVcsProps> = props => {
const controller = useQrLogin(props);
const { t } = useTranslation('QrScreen');
const {t} = useTranslation('QrScreen');
return (
<Modal
@@ -22,7 +22,7 @@ export const MyBindedVcs: React.FC<MyBindedVcsProps> = (props) => {
controller.DISMISS();
}}>
<React.Fragment>
<Column fill style={{ display: props.isVisible ? 'flex' : 'none' }}>
<Column fill style={{display: props.isVisible ? 'flex' : 'none'}}>
<Column fill>
{controller.shareableVcsMetadata.length > 0 && (
<>
@@ -34,7 +34,7 @@ export const MyBindedVcs: React.FC<MyBindedVcsProps> = (props) => {
{controller.shareableVcsMetadata.length > 0 &&
controller.shareableVcsMetadata.map(
(vcMetadata, index) => (
<VcItem
<ExistingMosipVCItem
key={vcMetadata.getVcKey()}
vcMetadata={vcMetadata}
margin="0 2 8 2"
@@ -43,7 +43,7 @@ export const MyBindedVcs: React.FC<MyBindedVcsProps> = (props) => {
selectable
selected={index === controller.selectedIndex}
/>
)
),
)}
</Column>
</Column>

View File

@@ -1,6 +1,6 @@
import { useSelector } from '@xstate/react';
import { useContext, useState } from 'react';
import { ActorRefFrom } from 'xstate';
import {useSelector} from '@xstate/react';
import {useContext, useState} from 'react';
import {ActorRefFrom} from 'xstate';
import {
QrLoginEvents,
selectClientName,
@@ -24,14 +24,14 @@ import {
selectIsSendingAuthenticate,
selectEssentialClaims,
} from '../../machines/QrLoginMachine';
import { selectBindedVcsMetadata } from '../../machines/vc';
import { vcItemMachine } from '../../machines/vcItem';
import { GlobalContext } from '../../shared/GlobalContext';
import { VC } from '../../types/vc';
import { QrLoginProps } from './QrLogin';
import {selectBindedVcsMetadata} from '../../machines/vc';
import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {GlobalContext} from '../../shared/GlobalContext';
import {VC} from '../../types/vc';
import {QrLoginProps} from './QrLogin';
export function useQrLogin({ service }: QrLoginProps) {
const { appService } = useContext(GlobalContext);
export function useQrLogin({service}: QrLoginProps) {
const {appService} = useContext(GlobalContext);
const vcService = appService.children.get('vc');
const [selectedIndex, setSelectedIndex] = useState<number>(null);
@@ -45,7 +45,8 @@ export function useQrLogin({ service }: QrLoginProps) {
return {
SELECT_VC_ITEM:
(index: number) => (vcRef: ActorRefFrom<typeof vcItemMachine>) => {
(index: number) =>
(vcRef: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => {
setSelectedIndex(index);
const vcData = vcRef.getSnapshot().context;
SELECT_VC(vcData);
@@ -55,7 +56,7 @@ export function useQrLogin({ service }: QrLoginProps) {
selectedVc: useSelector(service, selectSelectedVc),
linkTransactionResponse: useSelector(
service,
selectLinkTransactionResponse
selectLinkTransactionResponse,
),
domainName: useSelector(service, selectDomainName),
logoUrl: useSelector(service, selectLogoUrl),

View File

@@ -1,23 +1,23 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import {useTranslation} from 'react-i18next';
import { DeviceInfoList } from '../../components/DeviceInfoList';
import { Button, Column, Row, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { VcDetails } from '../../components/VcDetails';
import { useReceiveVcScreen } from './ReceiveVcScreenController';
import { VerifyIdentityOverlay } from '../VerifyIdentityOverlay';
import {DeviceInfoList} from '../../components/DeviceInfoList';
import {Button, Column, Row, Text} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
import {ExistingMosipVCItemDetails} from '../../components/VC/ExistingMosipVCItem/ExistingMosipVCItemDetails';
import {useReceiveVcScreen} from './ReceiveVcScreenController';
import {VerifyIdentityOverlay} from '../VerifyIdentityOverlay';
import {
MessageOverlay,
ErrorMessageOverlay,
} from '../../components/MessageOverlay';
import { useOverlayVisibleAfterTimeout } from '../../shared/hooks/useOverlayVisibleAfterTimeout';
import {useOverlayVisibleAfterTimeout} from '../../shared/hooks/useOverlayVisibleAfterTimeout';
export const ReceiveVcScreen: React.FC = () => {
const { t } = useTranslation('ReceiveVcScreen');
const {t} = useTranslation('ReceiveVcScreen');
const controller = useReceiveVcScreen();
const savingOverlayVisible = useOverlayVisibleAfterTimeout(
controller.isAccepting
controller.isAccepting,
);
const storeErrorTranslationPath = 'errors.savingFailed';
@@ -32,7 +32,7 @@ export const ReceiveVcScreen: React.FC = () => {
<Text weight="semibold" margin="24 24 0 24">
{t('header')}
</Text>
<VcDetails
<ExistingMosipVCItemDetails
vc={controller.incomingVc}
isBindingPending={false}
activeTab={1}
@@ -59,7 +59,7 @@ export const ReceiveVcScreen: React.FC = () => {
isVisible={controller.isInvalidIdentity}
title={t('VerifyIdentityOverlay:errors.invalidIdentity.title')}
message={t(
'VerifyIdentityOverlay:errors.invalidIdentity.messageNoRetry'
'VerifyIdentityOverlay:errors.invalidIdentity.messageNoRetry',
)}
onBackdropPress={controller.DISMISS}>
<Row>

View File

@@ -1,17 +1,17 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Dimensions } from 'react-native';
import { Overlay } from 'react-native-elements/dist/overlay/Overlay';
import { Button, Column, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { VcItem } from '../../components/VcItem';
import {useTranslation} from 'react-i18next';
import {Dimensions} from 'react-native';
import {Overlay} from 'react-native-elements/dist/overlay/Overlay';
import {Button, Column, Text} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
import {ExistingMosipVCItem} from '../../components/VC/ExistingMosipVCItem/ExistingMosipVCItem';
import {
SelectVcOverlayProps,
useSelectVcOverlay,
} from './SelectVcOverlayController';
export const SelectVcOverlay: React.FC<SelectVcOverlayProps> = (props) => {
const { t } = useTranslation('SelectVcOverlay');
export const SelectVcOverlay: React.FC<SelectVcOverlayProps> = props => {
const {t} = useTranslation('SelectVcOverlay');
const controller = useSelectVcOverlay(props);
return (
@@ -21,7 +21,7 @@ export const SelectVcOverlay: React.FC<SelectVcOverlayProps> = (props) => {
<Column
padding="24"
width={Dimensions.get('screen').width * 0.9}
style={{ maxHeight: Dimensions.get('screen').height * 0.9 }}>
style={{maxHeight: Dimensions.get('screen').height * 0.9}}>
<Text weight="semibold" margin="0 0 16 0">
{t('header')}
</Text>
@@ -30,7 +30,7 @@ export const SelectVcOverlay: React.FC<SelectVcOverlayProps> = (props) => {
</Text>
<Column margin="0 0 32 0" scroll>
{props.vcMetadatas.map((vcMetadata, index) => (
<VcItem
<ExistingMosipVCItem
key={`${vcMetadata.getVcKey()}-${index}`}
vcMetadata={vcMetadata}
margin="0 2 8 2"

View File

@@ -1,31 +1,31 @@
import { useState } from 'react';
import { ActorRefFrom } from 'xstate';
import { vcItemMachine } from '../../machines/vcItem';
import { VC } from '../../types/vc';
import { VCMetadata } from '../../shared/VCMetadata';
import {useState} from 'react';
import {ActorRefFrom} from 'xstate';
import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {VC} from '../../types/vc';
import {VCMetadata} from '../../shared/VCMetadata';
export function useSelectVcOverlay(props: SelectVcOverlayProps) {
const [selectedIndex, setSelectedIndex] = useState<number>(null);
const [selectedVcRef, setSelectedVcRef] =
useState<ActorRefFrom<typeof vcItemMachine>>(null);
useState<ActorRefFrom<typeof ExistingMosipVCItemMachine>>(null);
return {
selectVcItem,
selectedIndex,
onSelect: () => {
const { serviceRefs, ...vc } = selectedVcRef.getSnapshot().context;
const {serviceRefs, ...vc} = selectedVcRef.getSnapshot().context;
props.onSelect(vc);
},
onVerifyAndSelect: () => {
const { serviceRefs, ...vc } = selectedVcRef.getSnapshot().context;
const {serviceRefs, ...vc} = selectedVcRef.getSnapshot().context;
props.onVerifyAndSelect(vc);
},
};
function selectVcItem(index: number) {
return (vcRef: ActorRefFrom<typeof vcItemMachine>) => {
return (vcRef: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => {
setSelectedIndex(index);
setSelectedVcRef(vcRef);
};

View File

@@ -1,30 +1,30 @@
import React, { useContext, useEffect, useRef } from 'react';
import { Input } from 'react-native-elements';
import { useTranslation } from 'react-i18next';
import { Button, Column, Row, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { MessageOverlay } from '../../components/MessageOverlay';
import { useSendVcScreen } from './SendVcScreenController';
import { VerifyIdentityOverlay } from '../VerifyIdentityOverlay';
import { VcItem } from '../../components/VcItem';
import { I18nManager, BackHandler } from 'react-native';
import { useInterpret } from '@xstate/react';
import { createVcItemMachine } from '../../machines/vcItem';
import { GlobalContext } from '../../shared/GlobalContext';
import { useFocusEffect } from '@react-navigation/native';
import React, {useContext, useEffect, useRef} from 'react';
import {Input} from 'react-native-elements';
import {useTranslation} from 'react-i18next';
import {Button, Column, Row, Text} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
import {MessageOverlay} from '../../components/MessageOverlay';
import {useSendVcScreen} from './SendVcScreenController';
import {VerifyIdentityOverlay} from '../VerifyIdentityOverlay';
import {ExistingMosipVCItem} from '../../components/VC/ExistingMosipVCItem/ExistingMosipVCItem';
import {I18nManager, BackHandler} from 'react-native';
import {useInterpret} from '@xstate/react';
import {createExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {GlobalContext} from '../../shared/GlobalContext';
import {useFocusEffect} from '@react-navigation/native';
export const SendVcScreen: React.FC = () => {
const { t } = useTranslation('SendVcScreen');
const { appService } = useContext(GlobalContext);
const {t} = useTranslation('SendVcScreen');
const {appService} = useContext(GlobalContext);
const controller = useSendVcScreen();
let service;
if (controller.shareableVcsMetadata?.length > 0) {
const firstVCMachine = useRef(
createVcItemMachine(
createExistingMosipVCItemMachine(
appService.getSnapshot().context.serviceRefs,
controller.shareableVcsMetadata[0]
)
controller.shareableVcsMetadata[0],
),
);
service = useInterpret(firstVCMachine.current);
@@ -42,11 +42,11 @@ export const SendVcScreen: React.FC = () => {
const disableBackHandler = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress
onBackPress,
);
return () => disableBackHandler.remove();
}, [])
}, []),
);
const reasonLabel = t('reasonForSharing');
@@ -58,28 +58,28 @@ export const SendVcScreen: React.FC = () => {
<Column
padding="24 19 14 19"
backgroundColor={Theme.Colors.whiteBackgroundColor}
style={{ position: 'relative' }}>
style={{position: 'relative'}}>
<Input
value={controller.reason ? controller.reason : ''}
placeholder={!controller.reason ? reasonLabel : ''}
label={controller.reason ? reasonLabel : ''}
labelStyle={{ textAlign: 'left' }}
labelStyle={{textAlign: 'left'}}
onChangeText={controller.UPDATE_REASON}
containerStyle={{ marginBottom: 6 }}
inputStyle={{ textAlign: I18nManager.isRTL ? 'right' : 'left' }}
containerStyle={{marginBottom: 6}}
inputStyle={{textAlign: I18nManager.isRTL ? 'right' : 'left'}}
/>
</Column>
<Text
margin="15 0 13 24"
weight="bold"
color={Theme.Colors.textValue}
style={{ position: 'relative' }}>
style={{position: 'relative'}}>
{t('pleaseSelectAnId')}
</Text>
</Column>
<Column scroll>
{controller.shareableVcsMetadata.map((vcMetadata, index) => (
<VcItem
<ExistingMosipVCItem
key={vcMetadata.getVcKey()}
vcMetadata={vcMetadata}
margin="0 2 8 2"
@@ -99,7 +99,7 @@ export const SendVcScreen: React.FC = () => {
<Button
type="gradient"
title={t('acceptRequestAndVerify')}
styles={{ marginTop: 12 }}
styles={{marginTop: 12}}
disabled={controller.selectedIndex == null}
onPress={controller.VERIFY_AND_ACCEPT_REQUEST}
/>

View File

@@ -1,9 +1,9 @@
import { useSelector } from '@xstate/react';
import { useContext, useState } from 'react';
import { ActorRefFrom } from 'xstate';
import { selectShareableVcsMetadata } from '../../machines/vc';
import { vcItemMachine } from '../../machines/vcItem';
import { GlobalContext } from '../../shared/GlobalContext';
import {useSelector} from '@xstate/react';
import {useContext, useState} from 'react';
import {ActorRefFrom} from 'xstate';
import {selectShareableVcsMetadata} from '../../machines/vc';
import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {GlobalContext} from '../../shared/GlobalContext';
import {
selectIsSelectingVc,
selectReason,
@@ -16,10 +16,10 @@ import {
selectIsInvalidIdentity,
selectIsVerifyingIdentity,
} from '../../machines/bleShare/commonSelectors';
import { ScanEvents } from '../../machines/bleShare/scan/scanMachine';
import {ScanEvents} from '../../machines/bleShare/scan/scanMachine';
export function useSendVcScreen() {
const { appService } = useContext(GlobalContext);
const {appService} = useContext(GlobalContext);
const scanService = appService.children.get('scan');
const vcService = appService.children.get('vc');
@@ -32,9 +32,10 @@ export function useSendVcScreen() {
TOGGLE_USER_CONSENT: () =>
scanService.send(ScanEvents.TOGGLE_USER_CONSENT()),
SELECT_VC_ITEM:
(index: number) => (vcRef: ActorRefFrom<typeof vcItemMachine>) => {
(index: number) =>
(vcRef: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => {
setSelectedIndex(index);
const { serviceRefs, ...vcData } = vcRef.getSnapshot().context;
const {serviceRefs, ...vcData} = vcRef.getSnapshot().context;
scanService.send(ScanEvents.SELECT_VC(vcData));
},

View File

@@ -5,7 +5,7 @@ import {Centered, Column, Text} from '../../components/ui';
import {Icon} from 'react-native-elements';
import {Theme} from '../../components/ui/styleUtils';
import {Modal} from '../../components/ui/Modal';
import {VcItem} from '../../components/VcItem';
import {ExistingMosipVCItem} from '../../components/VC/ExistingMosipVCItem/ExistingMosipVCItem';
import {ViewVcModal} from '../Home/ViewVcModal';
export const ReceivedCardsModal: React.FC<ReceivedCardsProps> = ({
@@ -31,7 +31,7 @@ export const ReceivedCardsModal: React.FC<ReceivedCardsProps> = ({
/>
}>
{controller.receivedVcsMetadata.map(vcMetadata => (
<VcItem
<ExistingMosipVCItem
key={vcMetadata.getVcKey()}
vcMetadata={vcMetadata}
margin="0 2 8 2"

View File

@@ -1,14 +1,14 @@
import { useSelector } from '@xstate/react';
import { useContext, useEffect, useState } from 'react';
import {useSelector} from '@xstate/react';
import {useContext, useEffect, useState} from 'react';
import NetInfo from '@react-native-community/netinfo';
import { GlobalContext } from '../../shared/GlobalContext';
import {GlobalContext} from '../../shared/GlobalContext';
import {
selectIsRefreshingMyVcs,
selectMyVcsMetadata,
VcEvents,
} from '../../machines/vc';
import { vcItemMachine } from '../../machines/vcItem';
import { useTranslation } from 'react-i18next';
import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {useTranslation} from 'react-i18next';
import {
RevokeVidsEvents,
@@ -17,11 +17,11 @@ import {
selectIsLoggingRevoke,
} from '../../machines/revoke';
import { ActorRefFrom } from 'xstate';
import {ActorRefFrom} from 'xstate';
export function useRevoke() {
const { t } = useTranslation('ProfileScreen');
const { appService } = useContext(GlobalContext);
const {t} = useTranslation('ProfileScreen');
const {appService} = useContext(GlobalContext);
const vcService = appService.children.get('vc');
const revokeService = appService.children.get('RevokeVids');
const vcsMetadata = useSelector(vcService, selectMyVcsMetadata);
@@ -29,7 +29,7 @@ export function useRevoke() {
const isLoggingRevoke = useSelector(revokeService, selectIsLoggingRevoke);
const isAcceptingOtpInput = useSelector(
revokeService,
selectIsAcceptingOtpInput
selectIsAcceptingOtpInput,
);
const [isRevoking, setRevoking] = useState(false);
@@ -39,11 +39,11 @@ export function useRevoke() {
const [message, setMessage] = useState('');
const [selectedIndex, setSelectedIndex] = useState<number>(null);
const [selectedVidUniqueIds, setSelectedVidUniqueIds] = useState<string[]>(
[]
[],
);
const vidsMetadata = vcsMetadata.filter(
(vcMetadata) => vcMetadata.idType === 'VID'
vcMetadata => vcMetadata.idType === 'VID',
);
const selectVcItem = (index: number, vcUniqueId: string) => {
@@ -51,10 +51,10 @@ export function useRevoke() {
setSelectedIndex(index);
if (selectedVidUniqueIds.includes(vcUniqueId)) {
setSelectedVidUniqueIds(
selectedVidUniqueIds.filter((item) => item !== vcUniqueId)
selectedVidUniqueIds.filter(item => item !== vcUniqueId),
);
} else {
setSelectedVidUniqueIds((prevArray) => [...prevArray, vcUniqueId]);
setSelectedVidUniqueIds(prevArray => [...prevArray, vcUniqueId]);
}
};
};
@@ -91,7 +91,7 @@ export function useRevoke() {
selectedVidUniqueIds,
toastVisible,
uniqueVidsMetadata: vidsMetadata.filter(
(vcMetadata, index, vid) => vid.indexOf(vcMetadata) === index
(vcMetadata, index, vid) => vid.indexOf(vcMetadata) === index,
),
CONFIRM_REVOKE_VC: () => {
@@ -110,7 +110,7 @@ export function useRevoke() {
setIsViewing(false);
},
revokeVc: (otp: string) => {
NetInfo.fetch().then((state) => {
NetInfo.fetch().then(state => {
if (state.isConnected) {
revokeService.send(RevokeVidsEvents.INPUT_OTP(otp));
} else {
@@ -127,5 +127,5 @@ export function useRevoke() {
}
export interface RevokeProps {
service: ActorRefFrom<typeof vcItemMachine>;
service: ActorRefFrom<typeof ExistingMosipVCItemMachine>;
}

View File

@@ -8,26 +8,47 @@ export class VCMetadata {
requestId = '';
isPinned = false;
id: string = '';
issuer?: string = '';
protocol?: string = '';
static vcKeyRegExp = new RegExp(VC_ITEM_STORE_KEY_REGEX);
constructor({idType = '', requestId = '', isPinned = false, id = ''} = {}) {
constructor({
idType = '',
requestId = '',
isPinned = false,
id = '',
issuer = '',
protocol = '',
} = {}) {
this.idType = idType;
this.requestId = requestId;
this.isPinned = isPinned;
this.id = id;
this.protocol = protocol;
this.issuer = issuer;
}
static fromVC(vc: Partial<VC>) {
//TODO: Remove any typing and use appropriate typing
static fromVC(vc: Partial<VC> | VCMetadata | any) {
return new VCMetadata({
idType: vc.idType,
requestId: vc.requestId,
isPinned: vc.isPinned || false,
id: vc.id,
protocol: vc.protocol,
issuer: vc.issuer,
});
}
static fromVcMetadataString(vcMetadataStr: string) {
try {
if (typeof vcMetadataStr === 'object')
return new VCMetadata(vcMetadataStr);
return new VCMetadata(JSON.parse(vcMetadataStr));
} catch (e) {
console.error('Failed to parse VC Metadata', e);
@@ -36,12 +57,24 @@ export class VCMetadata {
}
static isVCKey(key: string): boolean {
return VCMetadata.vcKeyRegExp.exec(key) != null;
//TODO: Check for VC downloaded via esignet as well
const [issuer, protocol, id] = key.split(':');
return (
key.startsWith('ESignet') || VCMetadata.vcKeyRegExp.exec(key) != null
);
}
isFromOpenId4VCI() {
return this.protocol !== '' && this.issuer !== '';
}
// Used for mmkv storage purposes and as a key for components and vc maps
// Update VC_ITEM_STORE_KEY_REGEX in case of changes in vckey
getVcKey(): string {
// openid for vc -> issuer:protocol:vcID
//TODO: Separators for VC key to be maintained consistently
if (this.protocol) return `${this.issuer}:${this.protocol}:${this.id}`;
return `${VC_KEY_PREFIX}_${this.requestId}`;
}

View File

@@ -1,7 +1,7 @@
import { KeyPair, RSA } from 'react-native-rsa-native';
import {KeyPair, RSA} from 'react-native-rsa-native';
import forge from 'node-forge';
import getAllConfigurations from '../commonprops/commonProps';
import { isIOS } from '../constants';
import {isIOS} from '../constants';
import SecureKeystore from 'react-native-secure-keystore';
import Storage from '../storage';
import CryptoJS from 'crypto-js';
@@ -21,7 +21,7 @@ export function generateKeys(): Promise<KeyPair> {
export async function getJwt(
privateKey: string,
individualId: string,
thumbprint: string
thumbprint: string,
) {
try {
var iat = Math.floor(new Date().getTime() / 1000);
@@ -30,7 +30,7 @@ export async function getJwt(
var config = await getAllConfigurations();
const header = {
'alg': 'RS256',
alg: 'RS256',
//'kid': keyId,
'x5t#S256': thumbprint,
};
@@ -53,7 +53,7 @@ export async function getJwt(
const signature64 = await createSignature(
privateKey,
preHash,
individualId
individualId,
);
return header64 + '.' + payload64 + '.' + signature64;
@@ -63,10 +63,10 @@ export async function getJwt(
}
}
async function createSignature(
export async function createSignature(
privateKey: string,
preHash: string,
individualId: string
individualId: string,
) {
let signature64;
@@ -93,7 +93,7 @@ function replaceCharactersInB64(encodedB64) {
return encodedB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function encodeB64(str: string) {
export function encodeB64(str: string) {
const encodedB64 = forge.util.encode64(str);
return replaceCharactersInB64(encodedB64);
}
@@ -124,7 +124,7 @@ export async function clear() {
export async function encryptJson(
encryptionKey: string,
data: string
data: string,
): Promise<string> {
if (!isCustomSecureKeystore()) {
return CryptoJS.AES.encrypt(data, encryptionKey).toString();
@@ -134,12 +134,12 @@ export async function encryptJson(
export async function decryptJson(
encryptionKey: string,
encryptedData: string
encryptedData: string,
): Promise<string> {
try {
if (!isCustomSecureKeystore()) {
return CryptoJS.AES.decrypt(encryptedData, encryptionKey).toString(
CryptoJS.enc.Utf8
CryptoJS.enc.Utf8,
);
}
return await SecureKeystore.decryptData(ENCRYPTION_ID, encryptedData);

View File

@@ -0,0 +1,86 @@
import {ENABLE_OPENID_FOR_VC} from 'react-native-dotenv';
import {createSignature, encodeB64} from '../cryptoutil/cryptoUtil';
import jwtDecode from 'jwt-decode';
import jose from 'node-jose';
import {VCMetadata} from '../VCMetadata';
export const OpenId4VCIProtocol = 'OpenId4VCI';
export const isVCFromOpenId4VCI = (vcMetadata: VCMetadata) => {
return vcMetadata.isFromOpenId4VCI();
};
export const isOpenId4VCIEnabled = () => {
return ENABLE_OPENID_FOR_VC === 'true';
};
export const getIdentifier = (context, credential) => {
const credId = credential.credential.id.split('/');
return (
context.selectedIssuer.id +
':' +
context.selectedIssuer.protocol +
':' +
credId[credId.length - 1]
);
};
export const getBody = async context => {
const proofJWT = await getJWT(context);
return {
format: 'ldp_vc',
credential_definition: {
'@context': ['https://www.w3.org/2018/credentials/v1'],
type: ['VerifiableCredential', 'MOSIPVerifiableCredential'],
},
proof: {
proof_type: 'jwt',
jwt: proofJWT,
},
};
};
export const getJWK = async publicKey => {
try {
const publicKeyJWKString = await jose.JWK.asKey(publicKey, 'pem');
const publicKeyJWK = publicKeyJWKString.toJSON();
return {
...publicKeyJWK,
alg: 'RS256',
use: 'sig',
};
} catch (e) {
console.log(
'Exception occured while constructing JWK from PEM : ' +
publicKey +
' Exception is ',
e,
);
}
};
export const getJWT = async context => {
try {
const header64 = encodeB64(
JSON.stringify({
alg: 'RS256',
jwk: await getJWK(context.publicKey),
typ: 'openid4vci-proof+jwt',
}),
);
const decodedToken = jwtDecode(context.tokenResponse.accessToken);
const payload64 = encodeB64(
JSON.stringify({
iss: context.selectedIssuer.clientId,
nonce: decodedToken.c_nonce,
aud: 'https://esignet.dev1.mosip.net/v1/esignet',
iat: Math.floor(new Date().getTime() / 1000),
exp: Math.floor(new Date().getTime() / 1000) + 18000,
}),
);
const preHash = header64 + '.' + payload64;
const signature64 = await createSignature(context.privateKey, preHash, '');
return header64 + '.' + payload64 + '.' + signature64;
} catch (e) {
console.log(e);
throw e;
}
};

View File

@@ -1,10 +1,11 @@
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';
import {Credential} from '../../components/VC copy/EsignetMosipVCItem/vc';
// FIXME: Ed25519Signature2018 not fully supported yet.
const ProofType = {
@@ -18,7 +19,7 @@ const ProofPurpose = {
};
export async function verifyCredential(
verifiableCredential: VerifiableCredential
verifiableCredential: VerifiableCredential | Credential,
): Promise<boolean> {
let purpose: PublicKeyProofPurpose | AssertionProofPurpose;
switch (verifiableCredential.proof.proofPurpose) {

View File

@@ -29,6 +29,11 @@ declare module 'react-native-dotenv' {
*/
export const CREDENTIAL_REGISTRY_EDIT: string;
/**
* Flag for Toggling Download via UIN/VID
*/
export const ENABLE_OPENID_FOR_VC: string;
/**
* LANGUAGE for the unsupported device languages
*/