[INJI-609]: Dynamic VC Render (#1140)

* [INJI-609]: render vc claims dynamically using feature toggle

Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com>

* [INJI-609]: remove unwanted snippet

Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com>

* [INJI-609]: add tooltip and ellipsis for longer text

Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com>

* [INJI-609]: add tooltip and ellipsis for longer text

Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com>

---------

Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com>
This commit is contained in:
vijay151096
2024-01-08 10:54:46 +05:30
committed by GitHub
parent 074ae549bc
commit a2adcabc88
24 changed files with 1069 additions and 157 deletions

7
.env
View File

@@ -2,9 +2,9 @@
# eg . npm build android:newlogic --reset-cache
#MIMOTO_HOST=http://mock.mimoto.newlogic.dev
MIMOTO_HOST=https://api.qa-inji.mosip.net
MIMOTO_HOST=https://api.qa-inji1.mosip.net
ESIGNET_HOST=https://api.qa-inji.mosip.net
ESIGNET_HOST=https://api.qa-inji1.mosip.net
OBSRV_HOST = https://dataset-api.obsrv.mosip.net
@@ -17,3 +17,6 @@ DEBUG_MODE=false
#supported languages( en, fil, ar, hi, kn, ta)
APPLICATION_LANGUAGE=en
#Card Templatization - Render Claims Dynamically.
CARD_TEMPLATIZATION=false

View File

@@ -1,10 +1,10 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Dimensions} from 'react-native';
import {Icon} from 'react-native-elements';
import {VerifiableCredential} from '../../../types/VC/ExistingMosipVC/vc';
import {Row, Text} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import {View} from 'react-native';
const WalletUnverifiedIcon: React.FC = () => {
return (
@@ -41,17 +41,21 @@ const WalletUnverifiedActivationDetails: React.FC<
const {t} = useTranslation('VcDetails');
return (
<Row style={Theme.Styles.vcActivationDetailsWrapper}>
{props.verifiableCredential && <WalletUnverifiedIcon />}
<Text
color={Theme.Colors.Details}
testID="activationPending"
weight="regular"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('offlineAuthDisabledHeader')}></Text>
<View style={{flex: 1, alignSelf: 'center', justifyContent: 'center'}}>
{props.verifiableCredential && <WalletUnverifiedIcon />}
</View>
<View style={{flex: 4}}>
<Text
color={Theme.Colors.Details}
testID="activationPending"
weight="regular"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('offlineAuthDisabledHeader')}></Text>
</View>
</Row>
);
};
@@ -62,19 +66,23 @@ const WalletVerifiedActivationDetails: React.FC<
const {t} = useTranslation('WalletBinding');
return (
<Row style={Theme.Styles.vcActivationDetailsWrapper}>
<WalletVerifiedIcon />
<Text
color={Theme.Colors.statusLabel}
testID="activated"
weight="regular"
size="smaller"
margin="0 0 0 5"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('profileAuthenticated')}></Text>
<View style={{flex: 1, alignSelf: 'center', justifyContent: 'center'}}>
<WalletVerifiedIcon />
</View>
<View style={{flex: 4}}>
<Text
color={Theme.Colors.statusLabel}
testID="activated"
weight="regular"
size="smaller"
margin="0 0 0 5"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('profileAuthenticated')}></Text>
</View>
</Row>
);
};

View File

@@ -1,12 +1,16 @@
import React from 'react';
import {
MosipVCItemDetails,
ExistingMosipVCItemDetailsProps,
EsignetMosipVCItemDetailsProps,
ExistingMosipVCItemDetailsProps,
MosipVCItemDetails,
} from './MosipVCItem/MosipVCItemDetails';
import {CARD_TEMPLATIZATION} from 'react-native-dotenv';
import {VCDetailView} from './Views/VCDetailView';
export const VcDetailsContainer: React.FC<
ExistingMosipVCItemDetailsProps | EsignetMosipVCItemDetailsProps
> = props => {
if (CARD_TEMPLATIZATION === 'true') return <VCDetailView {...props} />;
return <MosipVCItemDetails {...props} />;
};

View File

@@ -1,12 +1,15 @@
import React from 'react';
import {
EsignetMosipVCItemProps,
MosipVCItem,
ExistingMosipVCItemProps,
MosipVCItem,
} from './MosipVCItem/MosipVCItem';
import {CARD_TEMPLATIZATION} from 'react-native-dotenv';
import {VCCardView} from './Views/VCCardView';
export const VcItemContainer: React.FC<
ExistingMosipVCItemProps | EsignetMosipVCItemProps
> = props => {
if (CARD_TEMPLATIZATION === 'true') return <VCCardView {...props} />;
return <MosipVCItem {...props} />;
};

View File

@@ -0,0 +1,166 @@
import React, {useEffect, useState} from 'react';
import {Pressable, View} from 'react-native';
import {ActorRefFrom} from 'xstate';
import {
ExistingMosipVCItemEvents,
ExistingMosipVCItemMachine,
} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {ErrorMessageOverlay} from '../../MessageOverlay';
import {Theme} from '../../ui/styleUtils';
import {Row} from '../../ui';
import {KebabPopUp} from '../../KebabPopUp';
import {VCMetadata} from '../../../shared/VCMetadata';
import {format} from 'date-fns';
import {EsignetMosipVCItemMachine} from '../../../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
import {VCCardSkeleton} from '../common/VCCardSkeleton';
import {VCCardViewContent} from './VCCardViewContent';
import {MosipVCItemActivationStatus} from '../MosipVCItem/MosipVCItemActivationStatus';
import {useVcItemController} from '../MosipVCItem/VcItemController';
import {getCredentialIssuersWellKnownConfig} from '../../../shared/openId4VCI/Utils';
import {
CARD_VIEW_ADD_ON_FIELDS,
CARD_VIEW_DEFAULT_FIELDS,
} from '../../../shared/constants';
import {isVCLoaded} from '../common/VCUtils';
export const VCCardView: React.FC<
ExistingMosipVCItemProps | EsignetMosipVCItemProps
> = props => {
let {
service,
context,
verifiableCredential,
emptyWalletBindingId,
isKebabPopUp,
isSavingFailedInIdle,
storeErrorTranslationPath,
generatedOn,
DISMISS,
KEBAB_POPUP,
} = useVcItemController(props);
let formattedDate =
generatedOn && format(new Date(generatedOn), 'MM/dd/yyyy');
useEffect(() => {
service.send(
ExistingMosipVCItemEvents.UPDATE_VC_METADATA(props.vcMetadata),
);
}, [props.vcMetadata]);
const credential = props.isDownloading
? null
: props.vcMetadata.isFromOpenId4VCI()
? verifiableCredential?.credential
: verifiableCredential;
const [fields, setFields] = useState([]);
const [wellknown, setWellknown] = useState(null);
useEffect(() => {
getCredentialIssuersWellKnownConfig(
props?.vcMetadata.issuer,
verifiableCredential?.wellKnown,
CARD_VIEW_DEFAULT_FIELDS,
).then(response => {
setWellknown(response.wellknown);
setFields(response.fields.slice(0, 1).concat(CARD_VIEW_ADD_ON_FIELDS));
});
}, [verifiableCredential?.wellKnown]);
if (!isVCLoaded(verifiableCredential, fields)) {
return <VCCardSkeleton />;
}
return (
<React.Fragment>
<Pressable
accessible={false}
onPress={() => props.onPress(service)}
disabled={!verifiableCredential}
style={
props.selected
? Theme.Styles.selectedBindedVc
: Theme.Styles.closeCardBgContainer
}>
<VCCardViewContent
vcMetadata={props.vcMetadata}
context={context}
verifiableCredential={verifiableCredential}
credential={credential}
fields={fields}
wellknown={wellknown}
generatedOn={formattedDate}
selectable={props.selectable}
selected={props.selected}
service={service}
isPinned={props.isPinned}
onPress={() => props.onPress(service)}
isDownloading={props.isDownloading}
/>
<View style={Theme.Styles.horizontalLine} />
{props.isSharingVc ? null : (
<Row style={Theme.Styles.activationTab}>
<MosipVCItemActivationStatus
vcMetadata={props.vcMetadata}
verifiableCredential={verifiableCredential}
emptyWalletBindingId={emptyWalletBindingId}
showOnlyBindedVc={props.showOnlyBindedVc}
/>
<Row style={Theme.Styles.verticalLineWrapper}>
<View style={Theme.Styles.verticalLine} />
</Row>
<Row style={Theme.Styles.kebabIcon}>
<Pressable
onPress={KEBAB_POPUP}
accessible={false}
style={Theme.Styles.kebabPressableContainer}>
<KebabPopUp
vcMetadata={props.vcMetadata}
iconName="dots-three-horizontal"
iconType="entypo"
isVisible={isKebabPopUp}
onDismiss={DISMISS}
service={service}
/>
</Pressable>
</Row>
</Row>
)}
</Pressable>
<ErrorMessageOverlay
isVisible={isSavingFailedInIdle}
error={storeErrorTranslationPath}
onDismiss={DISMISS}
translationPath={'VcDetails'}
/>
</React.Fragment>
);
};
export interface ExistingMosipVCItemProps {
vcMetadata: VCMetadata;
margin?: string;
selectable?: boolean;
selected?: boolean;
showOnlyBindedVc?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
isSharingVc?: boolean;
isDownloading?: boolean;
isPinned?: boolean;
}
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;
isSharingVc?: boolean;
isDownloading?: boolean;
isPinned?: boolean;
}

View File

@@ -0,0 +1,132 @@
import React from 'react';
import {ImageBackground} from 'react-native';
import {VerifiableCredential} from '../../../types/VC/ExistingMosipVC/vc';
import {Column, Row} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import {CheckBox, Icon} from 'react-native-elements';
import {SvgImage} from '../../ui/svg';
import {
fieldItemIterator,
getIssuerLogo,
isVCLoaded,
setBackgroundColour,
} from '../common/VCUtils';
import VerifiedIcon from '../../VerifiedIcon';
import {VCItemField} from '../common/VCItemField';
import {useTranslation} from 'react-i18next';
export const VCCardViewContent: React.FC<
ExistingMosipVCItemContentProps | EsignetMosipVCItemContentProps
> = props => {
const {t} = useTranslation('VcDetails');
const selectableOrCheck = props.selectable ? (
<CheckBox
checked={props.selected}
checkedIcon={
<Icon name="check-circle" type="material" color={Theme.Colors.Icon} />
}
uncheckedIcon={
<Icon
name="radio-button-unchecked"
color={Theme.Colors.uncheckedIcon}
/>
}
onPress={() => props.onPress()}
/>
) : null;
return (
<ImageBackground
source={!props.credential ? null : Theme.CloseCard}
resizeMode="stretch"
style={[
!props.credential
? Theme.Styles.vertloadingContainer
: Theme.Styles.backgroundImageContainer,
setBackgroundColour(props.wellknown),
]}>
<Column>
<Row align="space-between">
<Row margin="5 0 0 5">
{SvgImage.VcItemContainerProfileImage(props, props.credential)}
<Column margin={'0 0 0 20'}>
{fieldItemIterator(
props.fields.slice(0, 2),
props.credential,
props.wellknown,
props,
)}
</Column>
</Row>
<Column>{props.credential ? selectableOrCheck : null}</Column>
</Row>
{fieldItemIterator(
props.fields.slice(2),
props.credential,
props.wellknown,
props,
)}
<Row align={'space-between'} margin="0 8 5 8">
<VCItemField
key={'status'}
fieldName={t('status')}
fieldValue={
!isVCLoaded(props.credential, props.fields) ? null : (
<VerifiedIcon />
)
}
wellknown={props.wellknown}
verifiableCredential={props.credential}
/>
<Column
testID="logo"
style={{
display: props.credential ? 'flex' : 'none',
justifyContent: 'center',
alignItems: 'center',
}}>
{!isVCLoaded(props.credential, props.fields)
? null
: getIssuerLogo(
props.vcMetadata.isFromOpenId4VCI(),
props.verifiableCredential?.issuerLogo,
)}
</Column>
</Row>
</Column>
</ImageBackground>
);
};
export interface ExistingMosipVCItemContentProps {
context: any;
verifiableCredential: VerifiableCredential;
credential: VerifiableCredential;
fields: [];
wellknown: {};
generatedOn: string;
selectable: boolean;
selected: boolean;
isPinned?: boolean;
service: any;
onPress?: () => void;
isDownloading?: boolean;
}
export interface EsignetMosipVCItemContentProps {
context: any;
credential: VerifiableCredential;
fields: [];
wellknown: {};
generatedOn: string;
selectable: boolean;
selected: boolean;
isPinned?: boolean;
service: any;
onPress?: () => void;
isDownloading?: boolean;
}
VCCardViewContent.defaultProps = {
isPinned: false,
};

View File

@@ -0,0 +1,248 @@
import {formatDistanceToNow} from 'date-fns';
import React, {useEffect, useState} from 'react';
import * as DateFnsLocale from 'date-fns/locale';
import {useTranslation} from 'react-i18next';
import {Image, ImageBackground} from 'react-native';
import {Icon} from 'react-native-elements';
import {VC} from '../../../types/VC/ExistingMosipVC/vc';
import {Button, Column, Row, Text} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import {TextItem} from '../../ui/TextItem';
import {QrCodeOverlay} from '../../QrCodeOverlay';
import {VCMetadata} from '../../../shared/VCMetadata';
import {
VcIdType,
VCSharingReason,
VerifiableCredential,
VerifiablePresentation,
} from '../../../types/VC/EsignetMosipVC/vc';
import {WalletBindingResponse} from '../../../shared/cryptoutil/cryptoUtil';
import {logoType} from '../../../machines/issuersMachine';
import {SvgImage} from '../../ui/svg';
import {getCredentialIssuersWellKnownConfig} from '../../../shared/openId4VCI/Utils';
import {
DETAIL_VIEW_ADD_ON_FIELDS,
DETAIL_VIEW_DEFAULT_FIELDS,
} from '../../../shared/constants';
import {
fieldItemIterator,
isVCLoaded,
setBackgroundColour,
} from '../common/VCUtils';
import {ActivityIndicator} from '../../ui/ActivityIndicator';
const getIssuerLogo = (isOpenId4VCI: boolean, issuerLogo: logoType) => {
if (isOpenId4VCI) {
return (
<Image
src={issuerLogo?.url}
alt={issuerLogo?.alt_text}
style={Theme.Styles.issuerLogo}
/>
);
}
return SvgImage.MosipLogo(Theme.Styles.vcDetailsLogo);
};
const getProfileImage = (
props: ExistingMosipVCItemDetailsProps | EsignetMosipVCItemDetailsProps,
verifiableCredential,
isOpenId4VCI,
) => {
if (isOpenId4VCI) {
if (verifiableCredential?.credentialSubject.face) {
return {uri: verifiableCredential?.credentialSubject.face};
}
} else {
if (props.vc?.credential?.biometrics?.face) {
return {uri: props.vc?.credential.biometrics.face};
}
}
return <Icon name="person" color={Theme.Colors.Icon} size={88} />;
};
export const VCDetailView: React.FC<
ExistingMosipVCItemDetailsProps | EsignetMosipVCItemDetailsProps
> = props => {
const {t, i18n} = useTranslation('VcDetails');
let isOpenId4VCI = VCMetadata.fromVC(props.vc.vcMetadata).isFromOpenId4VCI();
const issuerLogo = getIssuerLogo(
isOpenId4VCI,
props.vc?.verifiableCredential?.issuerLogo,
);
const verifiableCredential = isOpenId4VCI
? props.vc?.verifiableCredential.credential
: props.vc?.verifiableCredential;
let [fields, setFields] = useState([]);
const [wellknown, setWellknown] = useState(null);
useEffect(() => {
getCredentialIssuersWellKnownConfig(
VCMetadata.fromVC(props.vc.vcMetadata).issuer,
props.vc?.verifiableCredential?.wellKnown,
DETAIL_VIEW_DEFAULT_FIELDS,
).then(response => {
setWellknown(response.wellknown);
setFields(response.fields.concat(DETAIL_VIEW_ADD_ON_FIELDS));
});
}, [props.verifiableCredential?.wellKnown]);
if (!isVCLoaded(verifiableCredential, fields)) {
return <ActivityIndicator />;
}
return (
<Column margin="10 4 10 4">
<ImageBackground
imageStyle={{width: '100%'}}
resizeMethod="scale"
resizeMode="stretch"
style={[
Theme.Styles.openCardBgContainer,
setBackgroundColour(wellknown),
]}
source={Theme.OpenCard}>
<Row padding="10" margin="0 10 0 8">
<Column crossAlign="center">
<Image
source={getProfileImage(
props,
verifiableCredential,
isOpenId4VCI,
)}
style={Theme.Styles.openCardImage}
/>
<QrCodeOverlay qrCodeDetails={String(verifiableCredential)} />
<Column margin="20 0 0 0">{issuerLogo}</Column>
</Column>
<Column align="space-evenly" margin={'0 0 0 10'} style={{flex: 1}}>
{fieldItemIterator(fields, verifiableCredential, wellknown, props)}
</Column>
</Row>
</ImageBackground>
{props.vc?.reason?.length > 0 && (
<Text
testID="reasonForSharingTitle"
margin="24 24 16 24"
weight="semibold">
{t('reasonForSharing')}
</Text>
)}
{props.vc?.reason?.map((reason, index) => (
<TextItem
testID="reason"
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} padding="10">
<Column margin={'0 0 4 0'} crossAlign={'flex-start'}>
<Icon
name="shield-alert"
color={Theme.Colors.Icon}
size={Theme.ICON_LARGE_SIZE}
type="material-community"
containerStyle={{
marginEnd: 5,
bottom: 1,
}}
/>
<Text
testID="offlineAuthDisabledHeader"
style={{flex: 1}}
weight="semibold"
size="small"
margin={'5 0 0 0'}
color={Theme.Colors.statusLabel}>
{t('offlineAuthDisabledHeader')}
</Text>
</Column>
<Text
testID="offlineAuthDisabledMessage"
style={{flex: 1, lineHeight: 17}}
weight="regular"
size="small"
margin={'3 0 10 0'}
color={Theme.Colors.statusMessage}>
{t('offlineAuthDisabledMessage')}
</Text>
<Button
testID="enableVerification"
title={t('enableVerification')}
onPress={props.onBinding}
type="gradient"
styles={{width: '100%'}}
/>
</Column>
) : (
<Column style={Theme.Styles.openCardBgContainer} padding="10">
<Row crossAlign="center">
<Icon
name="verified-user"
color={Theme.Colors.VerifiedIcon}
size={28}
containerStyle={{marginStart: 4, bottom: 1}}
/>
<Text
testID="profileAuthenticated"
numLines={1}
color={Theme.Colors.statusLabel}
weight="bold"
size="smaller"
margin="10 10 10 10"
children={t('profileAuthenticated')}></Text>
</Row>
</Column>
)
) : (
<></>
)}
</Column>
);
};
export interface ExistingMosipVCItemDetailsProps {
vc: VC;
isBindingPending: boolean;
onBinding?: () => void;
activeTab?: Number;
}
export interface EsignetMosipVCItemDetailsProps {
vc: EsignetVC;
isBindingPending: boolean;
onBinding?: () => void;
activeTab?: number;
}
export interface EsignetVC {
id: string;
idType: VcIdType;
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;
}

View File

@@ -0,0 +1,69 @@
import {Theme} from '../../ui/styleUtils';
import {Column, Row} from '../../ui';
import {ImageBackground} from 'react-native';
import {VCItemField} from './VCItemField';
import React from 'react';
import {SvgImage} from '../../ui/svg';
import LinearGradient from 'react-native-linear-gradient';
import ShimmerPlaceHolder from 'react-native-shimmer-placeholder';
export const VCCardInnerSkeleton = () => {
return (
<ImageBackground
source={Theme.CloseCard}
resizeMode="stretch"
style={Theme.Styles.vertloadingContainer}>
<Column>
<Row margin={'0 20 10 10'}>
<Column style={{marginTop: 10}}>
{SvgImage.VcItemContainerProfileImage(undefined, null)}
</Column>
<Column margin={'0 0 0 20'}>
{
<>
<VCItemField
key={'empty1'}
fieldName={'empty'}
fieldValue={'empty'}
verifiableCredential={null}
wellknown={null}
/>
<VCItemField
key={'empty2'}
fieldName={'empty'}
fieldValue={'empty'}
verifiableCredential={null}
wellknown={null}
/>
</>
}
</Column>
</Row>
<Column margin="0 8 5 8">
<VCItemField
key={'empty3'}
fieldName={'empty'}
fieldValue={'empty'}
verifiableCredential={null}
wellknown={null}
/>
</Column>
<Row align={'space-between'} margin="0 8 5 8">
<VCItemField
key={'empty4'}
fieldName={'empty'}
fieldValue={'empty'}
verifiableCredential={null}
wellknown={null}
/>
<ShimmerPlaceHolder
LinearGradient={LinearGradient}
width={60}
height={60}
style={{borderRadius: 5}}
/>
</Row>
</Column>
</ImageBackground>
);
};

View File

@@ -0,0 +1,27 @@
import {View} from 'react-native';
import {Theme} from '../../ui/styleUtils';
import {Row} from '../../ui';
import React from 'react';
import {VCCardInnerSkeleton} from './VCCardInnerSkeleton';
import LinearGradient from 'react-native-linear-gradient';
import ShimmerPlaceHolder from 'react-native-shimmer-placeholder';
export const VCCardSkeleton = () => {
return (
<View style={Theme.Styles.closeCardBgContainer}>
<VCCardInnerSkeleton />
<View style={Theme.Styles.horizontalLine} />
<Row style={[Theme.Styles.activationTab, {height: 30, borderRadius: 20}]}>
<Row style={Theme.Styles.vcActivationStatusContainer}>
<Row style={Theme.Styles.vcActivationDetailsWrapper}>
<ShimmerPlaceHolder
LinearGradient={LinearGradient}
width={300}
style={{borderRadius: 5}}
/>
</Row>
</Row>
</Row>
</View>
);
};

View File

@@ -0,0 +1,63 @@
import {Column, Text} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import React from 'react';
import {setTextColor} from './VCUtils';
import LinearGradient from 'react-native-linear-gradient';
import ShimmerPlaceHolder from 'react-native-shimmer-placeholder';
import {Tooltip} from 'react-native-elements';
export const VCItemField = ({
verifiableCredential,
fieldName,
fieldValue,
wellknown,
}) => {
if (!verifiableCredential) {
return (
<Column margin="9 0 0 0">
<ShimmerPlaceHolder
LinearGradient={LinearGradient}
width={150}
style={{marginBottom: 5, borderRadius: 5}}
/>
<ShimmerPlaceHolder
LinearGradient={LinearGradient}
width={180}
style={{borderRadius: 5}}
/>
</Column>
);
}
return (
<Column margin="9 0 0 0">
<Text
testID={`${fieldName}Title`}
weight="regular"
size="smaller"
{...setTextColor(wellknown)}
style={[Theme.Styles.subtitle]}>
{fieldName}
</Text>
<Tooltip
toggleOnPress={fieldValue.length > 20}
containerStyle={{
width: 200,
height: null,
overflow: 'hidden',
}}
skipAndroidStatusBar={true}
backgroundColor={Theme.Colors.Icon}
popover={<Text>{fieldValue}</Text>}>
<Text
testID={`${fieldName}Value`}
weight="semibold"
numLines={1}
ellipsizeMode={'tail'}
style={[Theme.Styles.subtitle, setTextColor(wellknown)]}>
{fieldValue}
</Text>
</Tooltip>
</Column>
);
};

View File

@@ -0,0 +1,159 @@
import {
CredentialSubject,
VerifiableCredential,
} from '../../../types/VC/ExistingMosipVC/vc';
import VerifiedIcon from '../../VerifiedIcon';
import i18n, {getLocalizedField} from '../../../i18n';
import {Row} from '../../ui';
import {VCItemField} from './VCItemField';
import React from 'react';
import {format, parse} from 'date-fns';
import {logoType} from '../../../machines/issuersMachine';
import {Image} from 'react-native';
import {Theme} from '../../ui/styleUtils';
import {SvgImage} from '../../ui/svg';
import {CREDENTIAL_REGISTRY_EDIT} from 'react-native-dotenv';
export const getFieldValue = (
verifiableCredential: VerifiableCredential,
field: string,
props: any,
) => {
switch (field) {
case 'status':
return <VerifiedIcon />;
case 'idType':
return i18n.t('VcDetails:nationalCard');
case 'dateOfBirth':
return formattedDateOfBirth(verifiableCredential);
case 'credentialRegistry':
return props?.vc?.credentialRegistry;
case 'address':
return getLocalizedField(
getFullAddress(verifiableCredential?.credentialSubject),
);
default:
return getLocalizedField(verifiableCredential?.credentialSubject[field]);
}
};
export const getFieldName = (field: string, wellknown: any) => {
if (wellknown && wellknown.credentials_supported) {
const fieldObj =
wellknown.credentials_supported[0].credential_definition
.credentialSubject[field];
if (fieldObj) {
const newFieldObj = fieldObj.display.map(obj => {
return {language: obj.locale, value: obj.name};
});
return getLocalizedField(newFieldObj);
}
}
return i18n.t(`VcDetails:${field}`);
};
export const setBackgroundColour = (wellknown: any) => {
if (wellknown && wellknown.credentials_supported[0]?.display) {
return {
backgroundColor: wellknown.credentials_supported[0].display[0]
?.background_color
? wellknown.credentials_supported[0].display[0].background_color
: Theme.Colors.textValue,
};
}
};
export const setTextColor = (wellknown: any) => {
if (wellknown && wellknown.credentials_supported[0]?.display) {
return {
color: wellknown.credentials_supported[0].display[0]?.text_color
? wellknown.credentials_supported[0].display[0].text_color
: Theme.Colors.textValue,
};
}
};
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(', ');
}
function formattedDateOfBirth(verifiableCredential: any) {
const dateOfBirth = verifiableCredential?.credentialSubject.dateOfBirth;
if (dateOfBirth) {
const formatString =
dateOfBirth.split('/').length === 1 ? 'yyyy' : 'yyyy/MM/dd';
const parsedDate = parse(dateOfBirth, formatString, new Date());
return format(parsedDate, 'MM/dd/yyyy');
}
return dateOfBirth;
}
export const fieldItemIterator = (
fields: any[],
verifiableCredential: any,
wellknown: any,
props: any,
) => {
return fields.map(field => {
const fieldName = getFieldName(field, wellknown);
const fieldValue = getFieldValue(verifiableCredential, field, props);
if (
(field === 'credentialRegistry' &&
CREDENTIAL_REGISTRY_EDIT === 'false') ||
!fieldValue
)
return;
return (
<Row
key={field}
style={{flexDirection: 'row', flex: 1}}
align="space-between"
let
margin="0 8 5 8">
<VCItemField
key={field}
fieldName={fieldName}
fieldValue={fieldValue}
verifiableCredential={verifiableCredential}
wellknown={wellknown}
/>
</Row>
);
});
};
export const isVCLoaded = (verifiableCredential: any, fields: string[]) => {
return verifiableCredential != null && fields.length > 0;
};
export const getIssuerLogo = (isOpenId4VCI: boolean, issuerLogo: logoType) => {
if (isOpenId4VCI) {
return (
<Image
src={issuerLogo?.url}
alt={issuerLogo?.alt_text}
style={Theme.Styles.issuerLogo}
resizeMethod="scale"
resizeMode="contain"
/>
);
}
return SvgImage.MosipLogo(Theme.Styles.logo);
};

View File

@@ -0,0 +1,36 @@
import {Centered, Column} from './Layout';
import {View} from 'react-native';
import {Theme} from './styleUtils';
import testIDProps from '../../shared/commonUtil';
import Spinner from 'react-native-spinkit';
import React from 'react';
import {SvgImage} from './svg';
export const ActivityIndicator = () => {
return (
<Centered
style={{backgroundColor: Theme.Colors.whiteBackgroundColor}}
crossAlign="center"
fill>
<Column
style={{
flex: 1,
justifyContent: 'center',
alignSelf: 'center',
alignItems: 'center',
}}
margin="24 0"
align="center"
crossAlign="center">
{SvgImage.ProgressIcon()}
<View {...testIDProps('threeDotsLoader')}>
<Spinner
type="ThreeBounce"
color={Theme.Colors.Loading}
style={{marginLeft: 6}}
/>
</View>
</Column>
</Centered>
);
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import {StyleProp, TextStyle, Text as RNText} from 'react-native';
import {Theme, Spacing} from './styleUtils';
import {StyleProp, Text as RNText, TextStyle} from 'react-native';
import {Spacing, Theme} from './styleUtils';
import testIDProps from '../../shared/commonUtil';
export const Text: React.FC<TextProps> = (props: TextProps) => {
@@ -21,6 +21,7 @@ export const Text: React.FC<TextProps> = (props: TextProps) => {
{...testIDProps(props.testID)}
style={textStyles}
numberOfLines={props.numLines}
ellipsizeMode={props.ellipsizeMode}
accessible={props.accessible}>
{props.children}
</RNText>
@@ -37,6 +38,7 @@ interface TextProps {
size?: 'small' | 'extraSmall' | 'smaller' | 'regular' | 'large';
lineHeight?: number;
numLines?: number;
ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip' | undefined;
style?: StyleProp<TextStyle>;
accessible?: boolean | true;
}

View File

@@ -338,6 +338,7 @@ export const DefaultTheme = {
flex: 1,
padding: 10,
overflow: 'hidden',
borderRadius: 10,
},
successTag: {
backgroundColor: Colors.Green,
@@ -372,8 +373,8 @@ export const DefaultTheme = {
height: 60,
},
vcDetailsLogo: {
height: 35,
width: 90,
height: 50,
width: 50,
},
homeCloseCardDetailsHeader: {
flex: 1,
@@ -756,6 +757,7 @@ export const DefaultTheme = {
color: 'transparent',
backgroundColor: Colors.Grey5,
borderRadius: 4,
marginBottom: 2,
},
subtitle: {
backgroundColor: 'transparent',

View File

@@ -342,6 +342,7 @@ export const PurpleTheme = {
flex: 1,
padding: 10,
overflow: 'hidden',
borderRadius: 10,
},
successTag: {
backgroundColor: Colors.Green,
@@ -376,8 +377,8 @@ export const PurpleTheme = {
height: 60,
},
vcDetailsLogo: {
width: 90,
height: 35,
width: 50,
height: 50,
},
homeCloseCardDetailsHeader: {
flex: 1,
@@ -759,6 +760,7 @@ export const PurpleTheme = {
color: 'transparent',
backgroundColor: Colors.Grey5,
borderRadius: 4,
marginBottom: 2,
},
subtitle: {
backgroundColor: 'transparent',

View File

@@ -576,7 +576,16 @@ export const IssuersMachine = model.createMachine(
},
checkInternet: async () => await NetInfo.fetch(),
downloadIssuerConfig: async (context, _) => {
return await CACHED_API.fetchIssuerConfig(context.selectedIssuerId);
let issuersConfig = await CACHED_API.fetchIssuerConfig(
context.selectedIssuerId,
);
if (context.selectedIssuer['.well-known']) {
await CACHED_API.fetchIssuerWellknownConfig(
context.selectedIssuerId,
context.selectedIssuer['.well-known'],
);
}
return issuersConfig;
},
downloadCredential: async context => {
const body = await getBody(context);

134
package-lock.json generated
View File

@@ -75,6 +75,7 @@
"react-native-screens": "~3.20.0",
"react-native-secure-key-store": "^2.0.10",
"react-native-securerandom": "^1.0.1",
"react-native-shimmer-placeholder": "^2.0.9",
"react-native-spinkit": "^1.5.1",
"react-native-svg": "13.4.0",
"react-native-vector-icons": "^10.0.0",
@@ -8664,21 +8665,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@svgr/core/node_modules/typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true,
"optional": true,
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/@svgr/hast-util-to-babel-ast": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
@@ -8783,21 +8769,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@svgr/plugin-svgo/node_modules/typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true,
"optional": true,
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -12529,29 +12500,6 @@
"node": ">= 0.8"
}
},
"node_modules/encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"optional": true,
"peer": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
},
"node_modules/encoding/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"optional": true,
"peer": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -13856,7 +13804,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.5.1.tgz",
"integrity": "sha512-yt5a1VCp2BF9CrsO689PCD5oXKP14MMhnOanQMvDn4BDpURYfzAlDVGC5fZrNQKtwn/eq3bcrxIwZ7D9QjVVRg==",
"peer": true,
"dependencies": {
"@expo/config": "~8.1.0",
"chalk": "^4.1.0",
@@ -13873,7 +13820,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"peer": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -13888,7 +13834,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"peer": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -13904,7 +13849,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"peer": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -13921,7 +13865,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"peer": true,
"engines": {
"node": ">= 10"
}
@@ -13930,7 +13873,6 @@
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"peer": true,
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
@@ -13945,7 +13887,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"peer": true,
"engines": {
"node": ">=8"
}
@@ -13954,7 +13895,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -13966,7 +13906,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"peer": true,
"engines": {
"node": ">= 10.0.0"
}
@@ -24738,9 +24677,9 @@
"integrity": "sha512-c7Cs+YQN26UaQsRG1dmlXL7VL2ctnXwH/dl0IOMEQ7ZaL2NdN313YSAI8ZEZZjrVhNmPsyWEuvTFqWrdpItqQg=="
},
"node_modules/react-native-linear-gradient": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.2.tgz",
"integrity": "sha512-hgmCsgzd58WNcDCyPtKrvxsaoETjb/jLGxis/dmU3Aqm2u4ICIduj4ECjbil7B7pm9OnuTkmpwXu08XV2mpg8g==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz",
"integrity": "sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==",
"peerDependencies": {
"react": "*",
"react-native": "*"
@@ -24898,6 +24837,15 @@
"react-native": "*"
}
},
"node_modules/react-native-shimmer-placeholder": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/react-native-shimmer-placeholder/-/react-native-shimmer-placeholder-2.0.9.tgz",
"integrity": "sha512-s2pfAAO6uaybREYM2KcaxP3c2XlZvLfHfzmhMlGEoOhSKaoM92KpA1Hx0BeC75yilkliJuowqO/hFzvLB0h7hg==",
"peerDependencies": {
"prop-types": ">=15.6.0",
"react-native-linear-gradient": ">=2.4.0"
}
},
"node_modules/react-native-size-matters": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/react-native-size-matters/-/react-native-size-matters-0.3.1.tgz",
@@ -35406,14 +35354,6 @@
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
}
},
"typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true,
"optional": true,
"peer": true
}
}
},
@@ -35473,14 +35413,6 @@
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
}
},
"typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true,
"optional": true,
"peer": true
}
}
},
@@ -38349,28 +38281,6 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"optional": true,
"peer": true,
"requires": {
"iconv-lite": "^0.6.2"
},
"dependencies": {
"iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"optional": true,
"peer": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
}
}
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -39390,7 +39300,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.5.1.tgz",
"integrity": "sha512-yt5a1VCp2BF9CrsO689PCD5oXKP14MMhnOanQMvDn4BDpURYfzAlDVGC5fZrNQKtwn/eq3bcrxIwZ7D9QjVVRg==",
"peer": true,
"requires": {
"@expo/config": "~8.1.0",
"chalk": "^4.1.0",
@@ -39404,7 +39313,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"peer": true,
"requires": {
"color-convert": "^2.0.1"
}
@@ -39413,7 +39321,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"peer": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -39423,7 +39330,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"peer": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -39442,7 +39348,6 @@
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"peer": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
@@ -39459,7 +39364,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"peer": true,
"requires": {
"has-flag": "^4.0.0"
}
@@ -47566,9 +47470,9 @@
"integrity": "sha512-c7Cs+YQN26UaQsRG1dmlXL7VL2ctnXwH/dl0IOMEQ7ZaL2NdN313YSAI8ZEZZjrVhNmPsyWEuvTFqWrdpItqQg=="
},
"react-native-linear-gradient": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.2.tgz",
"integrity": "sha512-hgmCsgzd58WNcDCyPtKrvxsaoETjb/jLGxis/dmU3Aqm2u4ICIduj4ECjbil7B7pm9OnuTkmpwXu08XV2mpg8g==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz",
"integrity": "sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==",
"requires": {}
},
"react-native-localize": {
@@ -47670,6 +47574,12 @@
"base64-js": "*"
}
},
"react-native-shimmer-placeholder": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/react-native-shimmer-placeholder/-/react-native-shimmer-placeholder-2.0.9.tgz",
"integrity": "sha512-s2pfAAO6uaybREYM2KcaxP3c2XlZvLfHfzmhMlGEoOhSKaoM92KpA1Hx0BeC75yilkliJuowqO/hFzvLB0h7hg==",
"requires": {}
},
"react-native-size-matters": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/react-native-size-matters/-/react-native-size-matters-0.3.1.tgz",

View File

@@ -78,6 +78,7 @@
"react-native-screens": "~3.20.0",
"react-native-secure-key-store": "^2.0.10",
"react-native-securerandom": "^1.0.1",
"react-native-shimmer-placeholder": "^2.0.9",
"react-native-spinkit": "^1.5.1",
"react-native-svg": "13.4.0",
"react-native-vector-icons": "^10.0.0",

View File

@@ -15,6 +15,10 @@ export const API_URLS: ApiUrls = {
buildURL: (issuerId: string): `/${string}` =>
`/residentmobileapp/issuers/${issuerId}`,
},
issuerWellknownConfig: {
method: 'GET',
buildURL: (requestUrl: `/${string}`): `/${string}` => requestUrl,
},
allProperties: {
method: 'GET',
buildURL: (): `/${string}` => '/residentmobileapp/allProperties',
@@ -96,7 +100,13 @@ export const API = {
);
return response.response;
},
fetchIssuerWellknownConfig: async (requestUrl: string) => {
const response = await request(
API_URLS.issuerWellknownConfig.method,
API_URLS.issuerWellknownConfig.buildURL(requestUrl),
);
return response;
},
fetchAllProperties: async () => {
const response = await request(
API_URLS.allProperties.method,
@@ -118,6 +128,11 @@ export const CACHED_API = {
cacheKey: API_CACHED_STORAGE_KEYS.fetchIssuerConfig(issuerId),
fetchCall: API.fetchIssuerConfig.bind(null, issuerId),
}),
fetchIssuerWellknownConfig: (issuerId: string, requestUrl: string) =>
generateCacheAPIFunction({
cacheKey: API_CACHED_STORAGE_KEYS.fetchIssuerWellknownConfig(issuerId),
fetchCall: API.fetchIssuerWellknownConfig.bind(null, requestUrl),
}),
getAllProperties: (isCachePreferred: boolean) =>
generateCacheAPIFunction({
@@ -246,6 +261,7 @@ type Api_Params = {
type ApiUrls = {
issuersList: Api_Params;
issuerConfig: Api_Params;
issuerWellknownConfig: Api_Params;
allProperties: Api_Params;
getIndividualId: Api_Params;
reqIndividualOTP: Api_Params;

View File

@@ -1,5 +1,5 @@
import {Platform} from 'react-native';
import {MIMOTO_HOST, ESIGNET_HOST, DEBUG_MODE} from 'react-native-dotenv';
import {DEBUG_MODE, ESIGNET_HOST, MIMOTO_HOST} from 'react-native-dotenv';
import {Argon2iConfig} from './commonUtil';
import {VcIdType} from '../types/VC/ExistingMosipVC/vc';
@@ -31,9 +31,9 @@ export const APP_ID_LENGTH = 12;
// Numbers and Upper case Alphabets without confusing characters like 0, 1, 2, I, O, Z
// prettier-ignore
export const APP_ID_DICTIONARY = [
'3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L',
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L',
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
];
export function isIOS(): boolean {
@@ -74,3 +74,23 @@ export type IndividualId = {
export const NETWORK_REQUEST_FAILED = 'Network request failed';
export const REQUEST_TIMEOUT = 'request timedout';
export const BIOMETRIC_CANCELLED = 'User has cancelled biometric';
export const CARD_VIEW_DEFAULT_FIELDS = ['fullName'];
export const DETAIL_VIEW_DEFAULT_FIELDS = [
'fullName',
'gender',
'phone',
'dateOfBirth',
'email',
'address',
];
//todo UIN & VID to be removed once we get the fields in the wellknown endpoint
export const CARD_VIEW_ADD_ON_FIELDS = ['idType', 'UIN', 'VID'];
export const DETAIL_VIEW_ADD_ON_FIELDS = [
'UIN',
'VID',
'status',
'credentialRegistry',
];

View File

@@ -8,6 +8,7 @@ import {CredentialWrapper} from '../../types/VC/EsignetMosipVC/vc';
import {VCMetadata} from '../VCMetadata';
import i18next from 'i18next';
import {getJWT} from '../cryptoutil/cryptoUtil';
import {CACHED_API} from '../api';
export const Protocols = {
OpenId4VCI: 'OpenId4VCI',
@@ -66,6 +67,10 @@ export const updateCredentialInformation = (context, credential) => {
credentialWrapper.verifiableCredential = credential;
credentialWrapper.identifier = getIdentifier(context, credential);
credentialWrapper.generatedOn = new Date();
credentialWrapper.verifiableCredential.wellKnown =
context.selectedIssuer['.well-known'];
// credentialWrapper.verifiableCredential.wellKnown =
// 'https://esignet.collab.mosip.net/.well-known/openid-credential-issuer';
credentialWrapper.verifiableCredential.issuerLogo =
getDisplayObjectForCurrentLanguage(context.selectedIssuer.display)?.logo;
return credentialWrapper;
@@ -136,6 +141,28 @@ export const getJWK = async publicKey => {
}
};
export const getCredentialIssuersWellKnownConfig = async (
issuer: string,
wellknown: string,
defaultFields: string[],
) => {
let fields: string[] = defaultFields;
let response = null;
if (wellknown) {
response = await CACHED_API.fetchIssuerWellknownConfig(issuer, wellknown);
fields = !response
? []
: Object.keys(
response?.credentials_supported[0].credential_definition
.credentialSubject,
);
}
return {
wellknown: response,
fields: fields,
};
};
export const vcDownloadTimeout = async (): Promise<number> => {
const response = await getAllConfigurations();
@@ -150,6 +177,7 @@ export enum OIDCErrors {
INVALID_TOKEN_SPECIFIED = 'Invalid token specified',
OIDC_CONFIG_ERROR_PREFIX = 'Config error',
}
// ErrorMessage is the type of error message shown in the UI
export enum ErrorMessage {
NO_INTERNET = 'noInternetConnection',

View File

@@ -27,20 +27,21 @@ export async function request(
if (path.includes('residentmobileapp'))
headers['X-AppId'] = __AppId.getValue();
let response;
const requestUrl = path.indexOf('https://') != -1 ? path : host + path;
if (timeoutMillis === undefined) {
response = await fetch(host + path, {
response = await fetch(requestUrl, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
} else {
console.log(`making a web request to ${host + path}`);
console.log(`making a web request to ${requestUrl}`);
let controller = new AbortController();
setTimeout(() => {
controller.abort();
}, timeoutMillis);
try {
response = await fetch(host + path, {
response = await fetch(requestUrl, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,

View File

@@ -42,6 +42,8 @@ export const API_CACHED_STORAGE_KEYS = {
fetchIssuers: 'CACHE_FETCH_ISSUERS',
fetchIssuerConfig: (issuerId: string) =>
`CACHE_FETCH_ISSUER_CONFIG_${issuerId}`,
fetchIssuerWellknownConfig: (issuerId: string) =>
`CACHE_FETCH_ISSUER_WELLKNOWN_CONFIG_${issuerId}`,
};
async function generateHmac(

View File

@@ -72,6 +72,7 @@ export interface VerifiableCredential {
issuerLogo: logoType;
format: string;
credential: Credential;
wellKnown: string;
}
export interface CredentialWrapper {