[INJIMOB-3581] add revocation and reverification logic (#2117)

* [INJIMOB-3581] add revocation and reverification logic

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3581] refactor readable array conversion to a shared utility

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

---------

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>
This commit is contained in:
abhip2565
2025-11-05 19:03:12 +05:30
committed by GitHub
parent a0d25feac1
commit 52c7ed1357
73 changed files with 2083 additions and 517 deletions

View File

@@ -60,6 +60,7 @@ import io.mosip.openID4VP.constants.ResponseType;
import io.mosip.openID4VP.constants.VPFormatType;
import io.mosip.openID4VP.exceptions.OpenID4VPExceptions;
import io.mosip.openID4VP.networkManager.NetworkResponse;
import io.mosip.residentapp.Utils.FormatConverter;
public class InjiOpenID4VPModule extends ReactContextBaseJavaModule {
private static final String TAG = "InjiOpenID4VPModule";
@@ -232,7 +233,7 @@ public class InjiOpenID4VPModule extends ReactContextBaseJavaModule {
ReadableMap formatMap = vpFormatsMap.getMap(key);
if (formatMap != null && formatMap.hasKey("alg_values_supported")) {
ReadableArray algArray = formatMap.getArray("alg_values_supported");
List<String> algValuesList = algArray != null ? convertReadableArrayToList(algArray) : null;
List<String> algValuesList = algArray != null ? FormatConverter.convertReadableArrayToList(algArray) : null;
vpFormatsSupportedMap.put(VPFormatType.Companion.fromValue(key), new VPFormatSupported(algValuesList));
}
}
@@ -245,7 +246,7 @@ public class InjiOpenID4VPModule extends ReactContextBaseJavaModule {
ReadableMap verifierMap = verifiersArray.getMap(i);
String clientId = verifierMap.getString("client_id");
ReadableArray responseUris = verifierMap.getArray("response_uris");
List<String> responseUriList = convertReadableArrayToList(responseUris);
List<String> responseUriList = FormatConverter.convertReadableArrayToList(responseUris);
String jwksUri = null;
if (verifierMap.hasKey("jwks_uri") && !verifierMap.isNull("jwks_uri")) {
try {
@@ -487,15 +488,7 @@ public class InjiOpenID4VPModule extends ReactContextBaseJavaModule {
throw new UnsupportedOperationException("Credential format '" + formatStr + "' is not supported");
}
private List<String> convertReadableArrayToList(ReadableArray readableArray) {
List<String> list = new ArrayList<>();
for (int i = 0; i < readableArray.size(); i++) {
list.add(readableArray.getString(i));
}
return list;
}
private String requireNonNullString(ReadableMap map, String key) {
String value = map.getString(key);

View File

@@ -1,14 +1,22 @@
package io.mosip.residentapp;
import java.util.*;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import io.mosip.residentapp.Utils.FormatConverter;
import io.mosip.vercred.vcverifier.CredentialsVerifier;
import io.mosip.vercred.vcverifier.constants.CredentialFormat;
import io.mosip.vercred.vcverifier.data.VerificationResult;
import io.mosip.vercred.vcverifier.data.CredentialVerificationSummary;
import io.mosip.vercred.vcverifier.data.CredentialStatusResult;
import io.mosip.vercred.vcverifier.exception.StatusCheckException;
public class RNVCVerifierModule extends ReactContextBaseJavaModule {
@@ -34,4 +42,51 @@ public class RNVCVerifierModule extends ReactContextBaseJavaModule {
promise.resolve(response);
}
@ReactMethod
public void getVerificationSummary(String vc, String format, ReadableArray statusPurposes, Promise promise) {
try {
// Convert ReadableArray to List<String>
List<String> statusPurposeList = FormatConverter.convertReadableArrayToList(statusPurposes);
CredentialVerificationSummary summary = credentialsVerifier.verifyAndGetCredentialStatus(
vc,
CredentialFormat.Companion.fromValue(format),
statusPurposeList
);
WritableMap resultMap = Arguments.createMap();
VerificationResult verificationResult = summary.getVerificationResult();
resultMap.putBoolean("verificationStatus", verificationResult.getVerificationStatus());
resultMap.putString("verificationMessage", verificationResult.getVerificationMessage());
resultMap.putString("verificationErrorCode", verificationResult.getVerificationErrorCode());
WritableArray statusArray = Arguments.createArray();
for (CredentialStatusResult statusResult : summary.getCredentialStatus()) {
WritableMap statusMap = Arguments.createMap();
statusMap.putString("purpose", statusResult.getPurpose());
statusMap.putInt("status", statusResult.getStatus());
statusMap.putBoolean("valid", statusResult.getValid());
StatusCheckException error = statusResult.getError();
if (error != null) {
WritableMap errorMap = Arguments.createMap();
errorMap.putString("message", error.getMessage());
errorMap.putString("code", error.getErrorCode().name());
statusMap.putMap("error", errorMap);
} else {
statusMap.putNull("error");
}
statusArray.pushMap(statusMap);
}
resultMap.putArray("credentialStatus", statusArray);
promise.resolve(resultMap);
} catch (Exception e) {
promise.reject("VERIFY_AND_GET_STATUS_ERROR", e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,18 @@
package io.mosip.residentapp.Utils;
import java.util.List;
import java.util.ArrayList;
import com.facebook.react.bridge.ReadableArray;
public class FormatConverter {
public static List<String> convertReadableArrayToList(ReadableArray readableArray) {
List<String> list = new ArrayList<>();
for (int i = 0; i < readableArray.size(); i++) {
list.add(readableArray.getString(i));
}
return list;
}
}

3
assets/Reverify.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 15C5.40617 15 3.63275 14.2736 2.17975 12.8207C0.726584 11.3679 0 9.59483 0 7.5015C0 5.40817 0.726584 3.63458 2.17975 2.18075C3.63275 0.726916 5.40617 0 7.5 0C8.66917 0 9.77558 0.259917 10.8193 0.77975C11.8628 1.29975 12.7307 2.03342 13.423 2.98075V0.75C13.423 0.5375 13.4949 0.359417 13.6388 0.21575C13.7826 0.0719168 13.9607 0 14.1733 0C14.3859 0 14.564 0.0719168 14.7075 0.21575C14.8512 0.359417 14.923 0.5375 14.923 0.75V5.2115C14.923 5.46767 14.8364 5.68233 14.6633 5.8555C14.4899 6.02867 14.2753 6.11525 14.0193 6.11525H9.55775C9.34525 6.11525 9.16708 6.04342 9.02325 5.89975C8.87958 5.75592 8.80775 5.57767 8.80775 5.365C8.80775 5.1525 8.87958 4.97442 9.02325 4.83075C9.16708 4.68725 9.34525 4.6155 9.55775 4.6155H12.7578C12.2308 3.65 11.4999 2.88942 10.5653 2.33375C9.63075 1.77792 8.609 1.5 7.5 1.5C5.83333 1.5 4.41667 2.08333 3.25 3.25C2.08333 4.41667 1.5 5.83333 1.5 7.5C1.5 9.16667 2.08333 10.5833 3.25 11.75C4.41667 12.9167 5.83333 13.5 7.5 13.5C8.66217 13.5 9.72608 13.1948 10.6918 12.5845C11.6574 11.9743 12.389 11.1603 12.8865 10.1423C12.9813 9.96025 13.1208 9.833 13.3048 9.7605C13.4888 9.68817 13.6763 9.684 13.8673 9.748C14.0711 9.81217 14.2137 9.9455 14.2952 10.148C14.3766 10.3507 14.3698 10.543 14.275 10.725C13.6557 12.0198 12.7415 13.056 11.5325 13.8335C10.3235 14.6112 8.97933 15 7.5 15Z" fill="#1A1818"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -20,6 +20,7 @@ export type ActivityLogType =
| 'WALLET_BINDING_SUCCESSFULL'
| 'WALLET_BINDING_FAILURE'
| 'VC_REMOVED'
| 'VC_STATUS_CHANGED'
| 'TAMPERED_VC_REMOVED';
export interface ActivityLog {
@@ -35,6 +36,7 @@ export class VCActivityLog implements ActivityLog {
type: ActivityLogType;
issuer: string;
flow: string;
vcStatus?: string;
constructor({
id = '',
@@ -46,6 +48,7 @@ export class VCActivityLog implements ActivityLog {
issuer = '',
credentialConfigurationId = '',
flow = VCItemContainerFlowType.VC_SHARE,
vcStatus = '',
} = {}) {
this.id = id;
this.idType = idType;
@@ -56,17 +59,23 @@ export class VCActivityLog implements ActivityLog {
this.issuer = issuer;
this.credentialConfigurationId = credentialConfigurationId;
this.flow = flow;
this.vcStatus = vcStatus;
}
getActionText(t: TFunction, wellknown: Object | undefined) {
const formattedVcStatus = this.vcStatus ? `.${this.vcStatus}` : '';
if (!!this.credentialConfigurationId && wellknown) {
const cardType = getCredentialTypeFromWellKnown(
wellknown,
this.credentialConfigurationId,
);
return `${t(this.type, {idType: cardType})}`;
return `${t(this.type + formattedVcStatus, {
idType: cardType,
vcStatus: this.vcStatus,
})}`;
}
return `${t(this.type, {idType: ''})}`;
return `${t(this.type + formattedVcStatus, {idType: '', vcStatus: this.vcStatus})}`;
}
static getLogFromObject(data: Object): VCActivityLog {

View File

@@ -15,7 +15,7 @@ import {useSettingsScreen} from '../screens/Settings/SettingScreenController';
export const BannerNotificationContainer: React.FC<
BannerNotificationContainerProps
> = props => {
const {showVerificationStatusBanner = true} = props;
const { showVerificationStatusBanner = true } = props;
const scanScreenController = useScanScreen();
const settingsScreenController = useSettingsScreen(props);
const showQuickShareSuccessBanner =
@@ -23,9 +23,11 @@ export const BannerNotificationContainer: React.FC<
const bannerNotificationController = UseBannerNotification();
const WalletBindingSuccess = bannerNotificationController.isBindingSuccess;
const {t} = useTranslation('BannerNotification');
const reverificationSuccessObject = bannerNotificationController.isReverificationSuccess;
const reverificationFailureObject = bannerNotificationController.isReverificationFailed;
const { t } = useTranslation('BannerNotification');
const rt = useTranslation('RequestScreen').t;
const verificationStatus = bannerNotificationController.verificationStatus;
const verificationStatus = bannerNotificationController.verificationStatus || null;
return (
<>
@@ -69,6 +71,20 @@ export const BannerNotificationContainer: React.FC<
</View>
)}
{reverificationSuccessObject.status && (
<View style={Theme.BannerStyles.topBanner}>
<BannerNotification
type={BannerStatusType.SUCCESS}
message={t(`reverifiedSuccessfully.${reverificationSuccessObject.statusValue}`, { vcType: reverificationSuccessObject.vcType })}
onClosePress={
bannerNotificationController.RESET_REVIRIFICATION_SUCCESS
}
key={'reverifiedSuccessfullyPopup'}
testId={'reverifiedSuccessfullyPopup'}
/>
</View>
)}
{showQuickShareSuccessBanner && (
<View style={Theme.BannerStyles.topBanner}>
<BannerNotification
@@ -101,18 +117,6 @@ export const BannerNotificationContainer: React.FC<
/>
)}
{verificationStatus !== null && showVerificationStatusBanner && (
<BannerNotification
type={verificationStatus.statusType}
message={t(`VcVerificationBanner:${verificationStatus?.statusType}`, {
vcDetails: `${verificationStatus.vcType} ${verificationStatus.vcNumber}`,
})}
onClosePress={bannerNotificationController.RESET_VERIFICATION_STATUS}
key={'reVerificationInProgress'}
testId={'reVerificationInProgress'}
/>
)}
{bannerNotificationController.isDownloadingFailed && (
<BannerNotification
type={BannerStatusType.ERROR}
@@ -122,6 +126,16 @@ export const BannerNotificationContainer: React.FC<
testId={'downloadingVcFailedPopup'}
/>
)}
{reverificationFailureObject.status && (
<BannerNotification
type={BannerStatusType.ERROR}
message={t(`reverificationFailed.${reverificationFailureObject.statusValue}`, { vcType: reverificationFailureObject.vcType })}
onClosePress={bannerNotificationController.RESET_REVERIFICATION_FAILURE}
key={'reverificationFailedPopup'}
testId={'reverificationFailedPopup'}
/>
)}
{bannerNotificationController.isDownloadingSuccess && (
<BannerNotification
type={BannerStatusType.SUCCESS}
@@ -137,6 +151,8 @@ export const BannerNotificationContainer: React.FC<
export type vcVerificationBannerDetails = {
statusType: BannerStatus;
isRevoked: boolean;
isExpired: boolean;
vcType: string;
};

View File

@@ -10,6 +10,8 @@ import {VcMetaEvents} from '../machines/VerifiableCredential/VCMetaMachine/VCMet
import {
selectIsDownloadingFailed,
selectIsDownloadingSuccess,
selectIsReverificationFailure,
selectIsReverificationSuccess,
selectWalletBindingSuccess,
} from '../machines/VerifiableCredential/VCMetaMachine/VCMetaSelectors';
import {selectVerificationStatus} from '../machines/VerifiableCredential/VCItemMachine/VCItemSelectors';
@@ -27,6 +29,8 @@ export const UseBannerNotification = () => {
isBiometricUnlock: useSelector(settingsService, selectIsBiometricUnlock),
isDownloadingSuccess: useSelector(vcMetaService, selectIsDownloadingSuccess),
isDownloadingFailed: useSelector(vcMetaService, selectIsDownloadingFailed),
isReverificationSuccess: useSelector(vcMetaService,selectIsReverificationSuccess),
isReverificationFailed: useSelector(vcMetaService, selectIsReverificationFailure),
DISMISS: () => {
settingsService.send(SettingsEvents.DISMISS());
},
@@ -40,5 +44,11 @@ export const UseBannerNotification = () => {
RESET_DOWNLOADING_SUCCESS: () => {
vcMetaService.send(VcMetaEvents.RESET_DOWNLOADING_SUCCESS());
},
RESET_REVIRIFICATION_SUCCESS: () => {
vcMetaService.send(VcMetaEvents.RESET_REVERIFY_VC_SUCCESS());
},
RESET_REVERIFICATION_FAILURE: () => {
vcMetaService.send(VcMetaEvents.RESET_REVERIFY_VC_FAILED());
},
};
};

View File

@@ -1,20 +1,20 @@
import React from 'react';
import {Icon, ListItem, Overlay} from 'react-native-elements';
import {Theme} from '../components/ui/styleUtils';
import {Column, Row, Text} from '../components/ui';
import {View} from 'react-native';
import {useKebabPopUp} from './KebabPopUpController';
import {ActorRefFrom} from 'xstate';
import {useTranslation} from 'react-i18next';
import {FlatList} from 'react-native-gesture-handler';
import {VCMetadata} from '../shared/VCMetadata';
import { Icon, ListItem, Overlay } from 'react-native-elements';
import { Theme } from '../components/ui/styleUtils';
import { Column, Row, Text } from '../components/ui';
import { View } from 'react-native';
import { useKebabPopUp } from './KebabPopUpController';
import { ActorRefFrom } from 'xstate';
import { useTranslation } from 'react-i18next';
import { FlatList } from 'react-native-gesture-handler';
import { VCMetadata } from '../shared/VCMetadata';
import testIDProps from '../shared/commonUtil';
import {getKebabMenuOptions} from './kebabMenuUtils';
import {VCItemMachine} from '../machines/VerifiableCredential/VCItemMachine/VCItemMachine';
import { getKebabMenuOptions } from './kebabMenuUtils';
import { VCItemMachine } from '../machines/VerifiableCredential/VCItemMachine/VCItemMachine';
export const KebabPopUp: React.FC<KebabPopUpProps> = props => {
const controller = useKebabPopUp(props);
const {t} = useTranslation('HomeScreenKebabPopUp');
const { t } = useTranslation('HomeScreenKebabPopUp');
return (
<Column>
@@ -52,14 +52,14 @@ export const KebabPopUp: React.FC<KebabPopUpProps> = props => {
<FlatList
data={getKebabMenuOptions(props)}
renderItem={({item}) => (
renderItem={({ item }) => (
<ListItem topDivider onPress={item.onPress}>
<Row crossAlign="center" style={{flex: 1}}>
<View style={{width: 25, alignItems: 'center'}}>
<Row crossAlign="center" style={{ flex: 1 }}>
<View style={{ width: 25, alignItems: 'center' }}>
{item.icon}
</View>
<Text
style={{fontFamily: 'Inter_600SemiBold'}}
style={{ fontFamily: 'Inter_600SemiBold' }}
color={
item.testID === 'removeFromWallet'
? Theme.Colors.warningText
@@ -69,6 +69,7 @@ export const KebabPopUp: React.FC<KebabPopUpProps> = props => {
margin="0 0 0 10">
{item.label}
</Text>
{item.label === t('reverify') && (<View style={Theme.KebabPopUpStyles.new}><Text color='white' weight='bold' style={{ fontSize: 10 }}>{t('new')}</Text></View>)}
</Row>
</ListItem>
)}

View File

@@ -71,6 +71,7 @@ export function useKebabPopUp(props) {
DISMISS: () => service.send(VCItemEvents.DISMISS()),
CANCEL: () => service.send(VCItemEvents.CANCEL()),
SHOW_ACTIVITY: () => service.send(VCItemEvents.SHOW_ACTIVITY()),
REVERIFY_VC: () => service.send(VCItemEvents.REVERIFY_VC()),
INPUT_OTP: (otp: string) => service.send(VCItemEvents.INPUT_OTP(otp)),
RESEND_OTP: () => service.send(VCItemEvents.RESEND_OTP()),
GOTO_SCANSCREEN: () => {

View File

@@ -1,73 +1,123 @@
import React, {ReactElement} from 'react';
import {useTranslation} from 'react-i18next';
import {Dimensions, StyleSheet} from 'react-native';
import {LinearProgress, Overlay} from 'react-native-elements';
import {Button, Column, Text} from './ui';
import {Theme} from './ui/styleUtils';
import React, { ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import { Dimensions, StyleSheet, View } from 'react-native';
import { LinearProgress, Overlay } from 'react-native-elements';
import { Button, Column, Text } from './ui';
import { Theme } from './ui/styleUtils';
import Svg, { Mask, Rect } from 'react-native-svg';
export const MessageOverlay: React.FC<MessageOverlayProps> = props => {
const {t} = useTranslation('common');
const { t } = useTranslation('common');
const style = StyleSheet.create({
customHeight: {
minHeight: props.minHeight ? props.minHeight : props.progress ? 100 : 170,
},
});
const isHighlightMode = props.overlayMode === 'highlight';
return (
<Overlay
isVisible={props.isVisible}
overlayStyle={Theme.MessageOverlayStyles.overlay}
fullScreen={isHighlightMode}
backdropStyle={{ backgroundColor: isHighlightMode ? 'transparent' : 'rgba(0,0,0,0.6)' }}
overlayStyle={
isHighlightMode
? { backgroundColor: 'transparent', padding: 0, margin: 0 }
: Theme.MessageOverlayStyles.overlay
}
onShow={props.onShow}
onBackdropPress={props.onBackdropPress}>
<Column
testID={props.testID}
width={Dimensions.get('screen').width * 0.8}
style={[Theme.MessageOverlayStyles.popupOverLay, style.customHeight]}>
<Column padding="21" crossAlign="center">
{props.title && (
<Text
testID={props.testID && props.testID + 'Title'}
style={{paddingTop: 3}}
align="center"
weight="bold"
margin="0 0 10 0"
color={Theme.Colors.Details}>
{props.title}
</Text>
{isHighlightMode ? (
<View
style={{ flex: 1 }}
onStartShouldSetResponder={() => true}
onResponderRelease={props.onBackdropPress}
>
{props.cardLayout && (
<>
<Svg
style={StyleSheet.absoluteFill}
pointerEvents="none"
>
<Mask id="hole">
<Rect width="100%" height="100%" fill="white" />
<Rect
x={props.cardLayout.x - 10}
y={props.cardLayout.y - 10}
width={props.cardLayout.width + 20}
height={props.cardLayout.height + 20}
rx={12}
fill="black"
/>
</Mask>
<Rect
width="100%"
height="100%"
fill="rgba(0,0,0,0.65)"
mask="url(#hole)"
/>
</Svg>
</>
)}
{props.message && (
<Text
testID={props.testID && props.testID + 'Message'}
align="center"
weight="semibold"
size="small"
margin="10 0 12 0"
color={Theme.Colors.Details}>
{props.message}
</Text>
)}
{props.progress && <Progress progress={props.progress} />}
{props.hint && (
<Text
testID={props.testID && props.testID + 'Hint'}
size="smaller"
color={Theme.Colors.textLabel}
margin={[4, 0, 0, 0]}>
{props.hint}
</Text>
)}
{props.children}
</Column>
{!props.children && props.onButtonPress ? (
<Button
testID="cancel"
type="gradient"
title={props.buttonText ? t(props.buttonText) : t('cancel')}
onPress={props.onButtonPress}
styles={Theme.MessageOverlayStyles.button}
/>
) : null}
</Column>
</View>
)
: (
(props.title || props.message || props.children) && (
<Column
testID={props.testID}
width={Dimensions.get('screen').width * 0.8}
style={[
Theme.MessageOverlayStyles.popupOverLay,
style.customHeight,
]}>
<Column padding="21" crossAlign="center">
{props.title && (
<Text
testID={props.testID && props.testID + 'Title'}
style={{ paddingTop: 3 }}
align="center"
weight="bold"
margin="0 0 10 0"
color={Theme.Colors.Details}>
{props.title}
</Text>
)}
{props.message && (
<Text
testID={props.testID && props.testID + 'Message'}
align="center"
weight="semibold"
size="small"
margin="10 0 12 0"
color={Theme.Colors.Details}>
{props.message}
</Text>
)}
{props.progress && <Progress progress={props.progress} />}
{props.hint && (
<Text
testID={props.testID && props.testID + 'Hint'}
size="smaller"
color={Theme.Colors.textLabel}
margin={[4, 0, 0, 0]}>
{props.hint}
</Text>
)}
{props.children}
</Column>
{!props.children && props.onButtonPress ? (
<Button
testID="cancel"
type="gradient"
title={props.buttonText ? t(props.buttonText) : t('cancel')}
onPress={props.onButtonPress}
styles={Theme.MessageOverlayStyles.button}
/>
) : null}
</Column>
)
)}
</Overlay>
);
};
@@ -79,7 +129,7 @@ export const ErrorMessageOverlay: React.FC<ErrorMessageOverlayProps> = ({
onDismiss,
translationPath,
}) => {
const {t} = useTranslation(translationPath);
const { t } = useTranslation(translationPath);
return (
<MessageOverlay
@@ -100,6 +150,7 @@ export interface ErrorMessageOverlayProps {
testID?: string;
}
const Progress: React.FC<MessageOverlayProps> = props => {
return typeof props.progress === 'boolean' ? (
props.progress && (
@@ -113,6 +164,7 @@ const Progress: React.FC<MessageOverlayProps> = props => {
export interface MessageOverlayProps {
testID?: string;
isVisible: boolean;
overlayMode?: 'popup' | 'highlight';
title?: string;
buttonText?: string;
message?: string;
@@ -126,6 +178,13 @@ export interface MessageOverlayProps {
onShow?: () => void;
minHeight?: number | string | undefined;
children?: ReactElement<any, any>;
cardLayout?: {
x: number;
y: number;
width: number;
height: number;
type: 'success' | 'failure';
};
}
export interface VCSharingErrorStatusProps {

View File

@@ -3,14 +3,18 @@ import {View} from 'react-native';
import {Icon} from 'react-native-elements';
import {Theme} from './ui/styleUtils';
const PendingIcon: React.FC = () => {
interface PendingIconProps {
color?: string;
}
const PendingIcon: React.FC<PendingIconProps> = (props) => {
return (
<View style={Theme.Styles.verificationStatusIconContainer}>
<View style={Theme.Styles.verificationStatusIconInner}>
<Icon
name="alert-circle"
type="material-community"
color={Theme.Colors.PendingIcon}
color={props.color}
size={12}
/>
</View>

View File

@@ -7,6 +7,7 @@ import {
selectWalletBindingResponse,
selectVerifiableCredentialData,
selectCredential,
isReverifyingVc,
} from '../../machines/VerifiableCredential/VCItemMachine/VCItemSelectors';
import {useInterpret, useSelector} from '@xstate/react';
import {
@@ -49,6 +50,7 @@ export function useVcItemController(vcMetadata: VCMetadata) {
VCItemService,
selectIsSavingFailedInIdle,
),
isReverifyingVc: useSelector(VCItemService, isReverifyingVc),
storeErrorTranslationPath: 'errors.savingFailed',
generatedOn: useSelector(VCItemService, selectGeneratedOn),
isTourGuide: useSelector(authService, selectIsTourGuide),

View File

@@ -1,21 +1,21 @@
import * as React from 'react';
import {useEffect, useState} from 'react';
import {Pressable} from 'react-native';
import {ActorRefFrom} from 'xstate';
import {ErrorMessageOverlay} from '../../MessageOverlay';
import {Theme} from '../../ui/styleUtils';
import {VCMetadata} from '../../../shared/VCMetadata';
import {format} from 'date-fns';
import { useEffect, useRef, useState } from 'react';
import { Pressable, View } from 'react-native';
import { ActorRefFrom } from 'xstate';
import { ErrorMessageOverlay, MessageOverlay } from '../../MessageOverlay';
import { Theme } from '../../ui/styleUtils';
import { VCMetadata } from '../../../shared/VCMetadata';
import { format } from 'date-fns';
import {VCCardSkeleton} from '../common/VCCardSkeleton';
import {VCCardViewContent} from './VCCardViewContent';
import {useVcItemController} from '../VCItemController';
import {getCredentialIssuersWellKnownConfig} from '../../../shared/openId4VCI/Utils';
import {CARD_VIEW_DEFAULT_FIELDS, isVCLoaded} from '../common/VCUtils';
import {VCItemMachine} from '../../../machines/VerifiableCredential/VCItemMachine/VCItemMachine';
import {useTranslation} from 'react-i18next';
import {Copilot} from '../../ui/Copilot';
import {VCProcessor} from '../common/VCProcessor';
import { VCCardSkeleton } from '../common/VCCardSkeleton';
import { VCCardViewContent } from './VCCardViewContent';
import { useVcItemController } from '../VCItemController';
import { getCredentialIssuersWellKnownConfig } from '../../../shared/openId4VCI/Utils';
import { CARD_VIEW_DEFAULT_FIELDS, isVCLoaded } from '../common/VCUtils';
import { VCItemMachine } from '../../../machines/VerifiableCredential/VCItemMachine/VCItemMachine';
import { useTranslation } from 'react-i18next';
import { Copilot } from '../../ui/Copilot';
import { VCProcessor } from '../common/VCProcessor';
export const VCCardView: React.FC<VCItemProps> = ({
vcMetadata,
@@ -28,15 +28,17 @@ export const VCCardView: React.FC<VCItemProps> = ({
isInitialLaunch = false,
isTopCard = false,
onDisclosuresChange,
onMeasured,
}) => {
const controller = useVcItemController(vcMetadata);
const {t} = useTranslation();
const { t } = useTranslation();
const cardRef = useRef<View>(null);
const service = controller.VCItemService;
const verifiableCredentialData = controller.verifiableCredentialData;
const generatedOn = -controller.generatedOn;
let formattedDate =
const formattedDate =
generatedOn && format(new Date(generatedOn), 'MM/dd/yyyy');
useEffect(() => {
@@ -47,6 +49,21 @@ export const VCCardView: React.FC<VCItemProps> = ({
const [wellknown, setWellknown] = useState(null);
const [vc, setVc] = useState(null);
useEffect(() => {
if (onMeasured && cardRef.current) {
const handle = requestAnimationFrame(() => {
cardRef.current?.measureInWindow((x, y, width, height) => {
if (width > 0 && height > 0) {
onMeasured({ x, y, width, height });
}
});
});
return () => cancelAnimationFrame(handle);
}
}, [onMeasured]);
useEffect(() => {
async function loadVc() {
if (!isDownloading) {
@@ -82,7 +99,7 @@ export const VCCardView: React.FC<VCItemProps> = ({
setFields(response.fields);
})
.catch(error => {
setWellknown({fallback: 'true'});
setWellknown({ fallback: 'true' });
console.error(
'Error occurred while fetching wellknown for viewing VC ',
error,
@@ -126,8 +143,15 @@ export const VCCardView: React.FC<VCItemProps> = ({
);
return (
<React.Fragment>
<>
<MessageOverlay
progress={true}
title={t('In Progress')}
isVisible={controller.isReverifyingVc}
/>
<Pressable
ref={cardRef}
accessible={false}
onPress={() => onPress(service)}
style={
@@ -145,7 +169,7 @@ export const VCCardView: React.FC<VCItemProps> = ({
onDismiss={controller.DISMISS}
translationPath={'VcDetails'}
/>
</React.Fragment>
</>
);
};
@@ -162,4 +186,5 @@ export interface VCItemProps {
isInitialLaunch?: boolean;
isTopCard?: boolean;
onDisclosuresChange?: (paths: string[]) => void;
onMeasured?: (rect: { x: number; y: number; width: number; height: number }) => void;
}

View File

@@ -244,6 +244,7 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = ({
<VCVerification
vcMetadata={verifiableCredentialData?.vcMetadata}
display={wellknownDisplayProperty}
showLastChecked={false}
/>
</Row>
</Column>
@@ -301,7 +302,7 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = ({
<Column padding="8 0">
<View style={{ paddingHorizontal: 6, marginTop: 8 }}>
<View
style={{...Theme.Styles.horizontalSeparator, marginBottom: 12 }}
style={{ ...Theme.Styles.horizontalSeparator, marginBottom: 12 }}
/>
<Column>
<Text

View File

@@ -1,11 +1,13 @@
import {Dimensions, View} from 'react-native';
import {Column, Row, Text} from '../../ui';
import {CustomTooltip} from '../../ui/ToolTip';
import {Theme} from '../../ui/styleUtils';
import { Dimensions, View } from 'react-native';
import { Column, Row, Text } from '../../ui';
import { CustomTooltip } from '../../ui/ToolTip';
import { Theme } from '../../ui/styleUtils';
import React from 'react';
import {SvgImage} from '../../ui/svg';
import {useTranslation} from 'react-i18next';
import { SvgImage } from '../../ui/svg';
import { useTranslation } from 'react-i18next';
import Icon from 'react-native-vector-icons/FontAwesome';
import { STATUS_FIELD_NAME } from './VCUtils';
import { StatusTooltipContent } from './VcStatustooTip';
export const VCItemFieldName = ({
fieldName,
@@ -18,7 +20,7 @@ export const VCItemFieldName = ({
fieldNameColor?: string;
isDisclosed?: boolean;
}) => {
const {t} = useTranslation('ViewVcModal');
const { t } = useTranslation('ViewVcModal');
return (
<Row>
{fieldName && (
@@ -26,64 +28,24 @@ export const VCItemFieldName = ({
testID={`${testID}Title`}
color={textColor}
style={Theme.Styles.fieldItemTitle}>
{fieldName}
{fieldName === STATUS_FIELD_NAME ? t('VcDetails:status') : fieldName}
</Text>
)}
{fieldName == t('VcDetails:status') && (
{fieldName == STATUS_FIELD_NAME && (
<CustomTooltip
testID="statusToolTip"
width={Dimensions.get('screen').width * 0.8}
height={Dimensions.get('screen').height * 0.28}
triggerComponent={SvgImage.info()}
triggerComponentStyles={{marginLeft: 2, marginTop: 2}}
triggerComponentStyles={{ marginLeft: 2, marginTop: 2 }}
toolTipContent={
<Column align="flex-start">
<View style={{marginBottom: 20}}>
<Text weight="semibold">
{t('statusToolTipContent.valid.title')}
</Text>
<Text
weight="regular"
style={[
Theme.Styles.tooltipContentDescription,
{marginTop: 3},
]}>
{t('statusToolTipContent.valid.description')}
</Text>
</View>
<View style={{marginBottom: 20}}>
<Text weight="semibold">
{t('statusToolTipContent.pending.title')}
</Text>
<Text
weight="regular"
style={[
Theme.Styles.tooltipContentDescription,
{marginTop: 3},
]}>
{t('statusToolTipContent.pending.description')}
</Text>
</View>
<View>
<Text weight="semibold">
{t('statusToolTipContent.expired.title')}
</Text>
<Text
weight="regular"
style={[
Theme.Styles.tooltipContentDescription,
{marginTop: 3},
]}>
{t('statusToolTipContent.expired.description')}
</Text>
</View>
</Column>
<StatusTooltipContent />
}
/>
)}
{isDisclosed && (
<Icon name="share-square-o" size={10} color="#666" style={{marginLeft:5, marginTop:3}} />
<Icon name="share-square-o" size={10} color="#666" style={{ marginLeft: 5, marginTop: 3 }} />
)}
</Row>
);
@@ -94,19 +56,28 @@ export const VCItemFieldValue = ({
testID,
fieldValueColor: textColor = Theme.Colors.Details,
}: {
fieldValue: string;
fieldValue: any;
testID: string;
fieldValueColor?: string;
}) => {
return (
<>
<Text
if (React.isValidElement(fieldValue)) {
return (
<View
testID={`${testID}Value`}
color={textColor}
style={Theme.Styles.fieldItemValue}>
>
{fieldValue}
</Text>
</>
</View>
);
}
return (
<Text
testID={`${testID}Value`}
color={textColor}
style={Theme.Styles.fieldItemValue}>
{fieldValue}
</Text>
);
};

View File

@@ -33,6 +33,9 @@ export const DETAIL_VIEW_DEFAULT_FIELDS = [
'address',
];
export const STATUS_FIELD_NAME = 'Status';
export const VC_STATUS_KEYS = ['valid', 'pending', 'expired', 'revoked'];
//todo UIN & VID to be removed once we get the fields in the wellknown endpoint
export const CARD_VIEW_ADD_ON_FIELDS = ['UIN', 'VID'];
export const DETAIL_VIEW_ADD_ON_FIELDS = [

View File

@@ -0,0 +1,30 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { Column } from "../../ui";
import { Theme } from "../../ui/styleUtils";
import { Text } from "../../ui";
import { VC_STATUS_KEYS } from "./VCUtils";
export const StatusTooltipContent = () => {
const { t } = useTranslation('ViewVcModal');
return (
<Column align="center" style={{marginTop:20}}>
{VC_STATUS_KEYS.map(key => (
<View key={key} style={{ marginBottom: 20 }}>
<Text weight="semibold">{t(`statusToolTipContent.${key}.title`)}</Text>
<Text
weight="regular"
style={[
Theme.Styles.tooltipContentDescription,
{ marginTop: 3 },
]}>
{t(`statusToolTipContent.${key}.description`)}
</Text>
</View>
))}
</Column>
);
};

View File

@@ -1,53 +1,83 @@
import testIDProps from '../shared/commonUtil';
import {Display} from './VC/common/VCUtils';
import VerifiedIcon from './VerifiedIcon';
import {Row, Text} from './ui';
import {Theme} from './ui/styleUtils';
import React from 'react';
import {useTranslation} from 'react-i18next';
import { View } from 'react-native';
import testIDProps from '../shared/commonUtil';
import { Display } from './VC/common/VCUtils';
import VerifiedIcon from './VerifiedIcon';
import PendingIcon from './PendingIcon';
import {VCMetadata} from '../shared/VCMetadata';
import { Row, Text } from './ui';
import { Theme } from './ui/styleUtils';
import { useTranslation } from 'react-i18next';
import { VCMetadata } from '../shared/VCMetadata';
export const VCVerification: React.FC<VCVerificationProps> = ({
vcMetadata,
display,
showLastChecked = true,
}) => {
const {t} = useTranslation('VcDetails');
const statusText = vcMetadata.isVerified
? vcMetadata.isExpired
? t('expired')
: t('valid')
: t('pending');
const { t } = useTranslation('VcDetails');
let statusText: string;
let statusIcon: JSX.Element;
if (vcMetadata.isVerified) {
if (vcMetadata.isRevoked) {
statusText = t('revoked');
statusIcon = <PendingIcon color="brown" />;
} else if (vcMetadata.isExpired) {
statusText = t('expired');
statusIcon = <PendingIcon color="red" />;
} else {
statusText = t('valid');
statusIcon = <VerifiedIcon />;
}
} else {
statusText = t('pending');
statusIcon = <PendingIcon color="orange" />;
}
const statusIcon = vcMetadata.isVerified ? (
vcMetadata.isExpired ? (
<PendingIcon />
) : (
<VerifiedIcon />
)
) : (
<PendingIcon />
);
return (
<Row
{...testIDProps('verified')}
style={{
alignItems: 'center',
}}>
<React.Fragment>
{statusIcon}
<View
{...testIDProps('verified')}
style={{
flexDirection: 'column',
alignItems: 'flex-start',
paddingVertical: 6,
}}>
{/* First Row: Status Icon + Text */}
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{statusIcon}
<Text
testID="verificationStatus"
color={display.getTextColor(Theme.Colors.Details)}
style={Theme.Styles.verificationStatus}>
{statusText}
</Text>
</View>
{showLastChecked && vcMetadata.lastKnownStatusTimestamp && (
<View style={{ marginTop: 4 }}>
<Text
testID="verificationStatus"
testID='lastCheckedLabel'
color={display.getTextColor(Theme.Colors.Details)}
style={Theme.Styles.verificationStatus}>
{statusText}
style={[Theme.Styles.verificationStatus, { fontFamily: 'Inter_400' }]}>
{t('lastChecked')}
</Text>
</React.Fragment>
</Row>
);
<Text
testID="lastKnownStatusTimestamp"
color={display.getTextColor(Theme.Colors.Details)}
style={[Theme.Styles.verificationStatus,{ fontFamily: 'Inter_400' }]}>
{new Date(vcMetadata.lastKnownStatusTimestamp).toLocaleString()}
</Text>
</View>
)}
</View>
);
};
export interface VCVerificationProps {
vcMetadata: VCMetadata;
display: Display;
showLastChecked?: boolean;
}

View File

@@ -31,6 +31,12 @@ export const getKebabMenuOptions = props => {
onPress: controller.SHOW_ACTIVITY,
testID: 'viewActivityLog',
},
{
label: t('reverify'),
icon: SvgImage.ReverifyIcon(),
onPress: controller.REVERIFY_VC,
testID: 'reverify',
},
{
label: t('removeFromWallet'),
icon: SvgImage.outlinedDeleteIcon(),

View File

@@ -60,6 +60,7 @@ import QuestionIcon from '../../assets/questionIcon.svg';
import CopyIcon from '../../assets/file_copy.svg';
import StarIcon from '../../assets/credentialRegestryStar.svg';
import SelectedCheckBox from '../../assets/Selected_Check_Box.svg';
import ReverifyIcon from '../../assets/Reverify.svg';
export class SvgImage {
static selectedCheckBox() {
return <SelectedCheckBox />;
@@ -248,6 +249,12 @@ export class SvgImage {
);
}
static ReverifyIcon() {
return (
<ReverifyIcon/>
)
}
static OutlinedPinIcon() {
return <OutlinedPinIcon {...testIDProps('outlinedPinIcon')} />;
}

View File

@@ -769,24 +769,24 @@ export const DefaultTheme = {
flex: 1,
justifyContent: 'space-around',
},
horizontalSeparator:{
horizontalSeparator: {
height: 1,
backgroundColor: '#DADADA',
},
disclosureTitle:{
disclosureTitle: {
fontFamily: 'Inter_700Bold',
fontSize: 15,
color: Colors.Black,
},
disclosureSubtitle:{
disclosureSubtitle: {
fontSize: 13,
color: '#747474',
marginTop: 4,
},
disclosureSelectButton:{
disclosureSelectButton: {
fontSize: 14,
fontFamily: 'Inter_700Bold',
}
},
}),
BannerStyles: StyleSheet.create({
container: {
@@ -1191,7 +1191,7 @@ export const DefaultTheme = {
borderColor: Colors.Orange,
borderRadius: 30,
},
sharedSuccessfullyVerifierInfo:{
sharedSuccessfullyVerifierInfo: {
alignSelf: 'center',
backgroundColor: '#F5F5F5',
borderRadius: 16,
@@ -1205,7 +1205,7 @@ export const DefaultTheme = {
height: 40,
borderRadius: 8,
marginRight: 12,
}
},
}),
AppMetaDataStyles: StyleSheet.create({
buttonContainer: {
@@ -1366,6 +1366,15 @@ export const DefaultTheme = {
position: 'absolute',
bottom: 0,
},
new: {
height: 20,
width: 'auto',
backgroundColor: '#FF5300',
alignItems: 'center',
marginLeft: 10,
borderRadius: 5,
paddingHorizontal: 5,
},
kebabHeaderStyle: {
justifyContent: 'space-between',
fontFamily: 'Inter_700Bold',
@@ -2168,14 +2177,14 @@ export const DefaultTheme = {
color: '#973C00',
marginBottom: 5,
},
noteDescriptionText:{
noteDescriptionText: {
fontSize: 13,
color: '#973C00',
fontFamily: 'Inter_400Regular',
lineHeight: 18,
textAlign: 'left',
marginLeft: -25
}
marginLeft: -25,
},
}),
DisclosureInfo: StyleSheet.create({
view: {

View File

@@ -1372,6 +1372,15 @@ export const PurpleTheme = {
position: 'absolute',
bottom: 0,
},
new: {
height: 20,
width: 'auto',
backgroundColor: '#FF5300',
alignItems: 'center',
marginLeft: 10,
borderRadius: 5,
paddingHorizontal: 5,
},
kebabHeaderStyle: {
justifyContent: 'space-between',
fontFamily: 'Inter_700Bold',

View File

@@ -22,7 +22,20 @@
96905EF65AED1B983A6B3ABC /* libPods-Inji.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Inji.a */; };
9C0E86B52BEE357A00E9F9F6 /* RNPixelpassModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0E86B42BEE357A00E9F9F6 /* RNPixelpassModule.swift */; };
9C0E86BB2BEE36C300E9F9F6 /* RNPixelpassModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C0E86BA2BEE36C300E9F9F6 /* RNPixelpassModule.m */; };
9C4850432C3E5873002ECBD5 /* ios-tuvali-library in Frameworks */ = {isa = PBXBuildFile; productRef = 9C4850422C3E5873002ECBD5 /* ios-tuvali-library */; };
9C3BD80E2EA8C8DE00101656 /* RNVCVerifierModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD80D2EA8C8D600101656 /* RNVCVerifierModule.swift */; };
9C3BD8102EA8C8F800101656 /* RNVCVerifierModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD80F2EA8C8EE00101656 /* RNVCVerifierModule.m */; };
9C3BD83C2EA8D63A00101656 /* CredentialsVerifierFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD82E2EA8D63A00101656 /* CredentialsVerifierFactory.swift */; };
9C3BD83D2EA8D63A00101656 /* VerifiableCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD82F2EA8D63A00101656 /* VerifiableCredential.swift */; };
9C3BD83E2EA8D63A00101656 /* NetworkManagerClientExceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD8332EA8D63A00101656 /* NetworkManagerClientExceptions.swift */; };
9C3BD83F2EA8D63A00101656 /* RevocationCheckExceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD8342EA8D63A00101656 /* RevocationCheckExceptions.swift */; };
9C3BD8402EA8D63A00101656 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD8312EA8D63A00101656 /* Data.swift */; };
9C3BD8412EA8D63A00101656 /* CredentialsVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD83A2EA8D63A00101656 /* CredentialsVerifier.swift */; };
9C3BD8422EA8D63A00101656 /* LdpVerifiableCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD82C2EA8D63A00101656 /* LdpVerifiableCredential.swift */; };
9C3BD8432EA8D63A00101656 /* CredentialsVerifierConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD8282EA8D63A00101656 /* CredentialsVerifierConstant.swift */; };
9C3BD8442EA8D63A00101656 /* LdpStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD82A2EA8D63A00101656 /* LdpStatusChecker.swift */; };
9C3BD8452EA8D63A00101656 /* base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD8382EA8D63A00101656 /* base64.swift */; };
9C3BD8462EA8D63A00101656 /* NetworkManagerClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD8362EA8D63A00101656 /* NetworkManagerClient.swift */; };
9C3BD8472EA8D63A00101656 /* CredentialFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3BD8272EA8D63A00101656 /* CredentialFormat.swift */; };
9C48504B2C3E59B5002ECBD5 /* RNWalletModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4850442C3E59B5002ECBD5 /* RNWalletModule.swift */; };
9C48504D2C3E59B5002ECBD5 /* RNWalletModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C4850462C3E59B5002ECBD5 /* RNWalletModule.m */; };
9C48504E2C3E59B5002ECBD5 /* RNEventEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4850472C3E59B5002ECBD5 /* RNEventEmitter.swift */; };
@@ -30,10 +43,11 @@
9C4850502C3E59B5002ECBD5 /* RNEventMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4850492C3E59B5002ECBD5 /* RNEventMapper.swift */; };
9C4850512C3E59B5002ECBD5 /* RNVersionModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C48504A2C3E59B5002ECBD5 /* RNVersionModule.m */; };
9C4850532C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4850522C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift */; };
9C7CDF3E2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C7CDF3D2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift */; };
9C7CDF432C7CC13500243A9A /* RNSecureKeystoreModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C7CDF422C7CC13500243A9A /* RNSecureKeystoreModule.m */; };
9CAE74EE2E2E38F800C2532C /* pixelpass in Frameworks */ = {isa = PBXBuildFile; productRef = 9CAE74ED2E2E38F800C2532C /* pixelpass */; };
9CCC57772E9D7C7000669DB7 /* ios-tuvali-library in Frameworks */ = {isa = PBXBuildFile; productRef = 9CCC57762E9D7C7000669DB7 /* ios-tuvali-library */; };
9CCCA19E2CF87A8400D5A461 /* securekeystore in Frameworks */ = {isa = PBXBuildFile; productRef = 9CCCA19D2CF87A8400D5A461 /* securekeystore */; };
9CCDB2E02EAB63D9009E8E2B /* RNSecureKeystoreModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CCDB2DF2EAB63C9009E8E2B /* RNSecureKeystoreModule.swift */; };
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
C339223B2E79A536004A01EC /* InjiVcRenderer in Frameworks */ = {isa = PBXBuildFile; productRef = C339223A2E79A536004A01EC /* InjiVcRenderer */; };
@@ -84,6 +98,20 @@
7A4D352CD337FB3A3BF06240 /* Pods-Inji.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Inji.release.xcconfig"; path = "Target Support Files/Pods-Inji/Pods-Inji.release.xcconfig"; sourceTree = "<group>"; };
9C0E86B42BEE357A00E9F9F6 /* RNPixelpassModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNPixelpassModule.swift; sourceTree = "<group>"; };
9C0E86BA2BEE36C300E9F9F6 /* RNPixelpassModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNPixelpassModule.m; sourceTree = "<group>"; };
9C3BD80D2EA8C8D600101656 /* RNVCVerifierModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNVCVerifierModule.swift; sourceTree = "<group>"; };
9C3BD80F2EA8C8EE00101656 /* RNVCVerifierModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNVCVerifierModule.m; sourceTree = "<group>"; };
9C3BD8272EA8D63A00101656 /* CredentialFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialFormat.swift; sourceTree = "<group>"; };
9C3BD8282EA8D63A00101656 /* CredentialsVerifierConstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialsVerifierConstant.swift; sourceTree = "<group>"; };
9C3BD82A2EA8D63A00101656 /* LdpStatusChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LdpStatusChecker.swift; sourceTree = "<group>"; };
9C3BD82C2EA8D63A00101656 /* LdpVerifiableCredential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LdpVerifiableCredential.swift; sourceTree = "<group>"; };
9C3BD82E2EA8D63A00101656 /* CredentialsVerifierFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialsVerifierFactory.swift; sourceTree = "<group>"; };
9C3BD82F2EA8D63A00101656 /* VerifiableCredential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifiableCredential.swift; sourceTree = "<group>"; };
9C3BD8312EA8D63A00101656 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
9C3BD8332EA8D63A00101656 /* NetworkManagerClientExceptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManagerClientExceptions.swift; sourceTree = "<group>"; };
9C3BD8342EA8D63A00101656 /* RevocationCheckExceptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevocationCheckExceptions.swift; sourceTree = "<group>"; };
9C3BD8362EA8D63A00101656 /* NetworkManagerClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManagerClient.swift; sourceTree = "<group>"; };
9C3BD8382EA8D63A00101656 /* base64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = base64.swift; sourceTree = "<group>"; };
9C3BD83A2EA8D63A00101656 /* CredentialsVerifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialsVerifier.swift; sourceTree = "<group>"; };
9C4850442C3E59B5002ECBD5 /* RNWalletModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RNWalletModule.swift; path = Inji/RNWalletModule.swift; sourceTree = "<group>"; };
9C4850462C3E59B5002ECBD5 /* RNWalletModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNWalletModule.m; path = Inji/RNWalletModule.m; sourceTree = "<group>"; };
9C4850472C3E59B5002ECBD5 /* RNEventEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RNEventEmitter.swift; path = Inji/RNEventEmitter.swift; sourceTree = "<group>"; };
@@ -91,8 +119,8 @@
9C4850492C3E59B5002ECBD5 /* RNEventMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RNEventMapper.swift; path = Inji/RNEventMapper.swift; sourceTree = "<group>"; };
9C48504A2C3E59B5002ECBD5 /* RNVersionModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNVersionModule.m; path = Inji/RNVersionModule.m; sourceTree = "<group>"; };
9C4850522C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RNEventEmitterProtocol.swift; path = Inji/RNEventEmitterProtocol.swift; sourceTree = "<group>"; };
9C7CDF3D2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSecureKeystoreModule.swift; sourceTree = "<group>"; };
9C7CDF422C7CC13500243A9A /* RNSecureKeystoreModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSecureKeystoreModule.m; sourceTree = "<group>"; };
9CCDB2DF2EAB63C9009E8E2B /* RNSecureKeystoreModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSecureKeystoreModule.swift; sourceTree = "<group>"; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Inji/SplashScreen.storyboard; sourceTree = "<group>"; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
C3F6A9DC2E66188D006C9904 /* RNInjiVcRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNInjiVcRenderer.swift; sourceTree = "<group>"; };
@@ -129,8 +157,8 @@
buildActionMask = 2147483647;
files = (
C3F18B1D2E320C9A007DBE73 /* VCIClient in Frameworks */,
9CCC57772E9D7C7000669DB7 /* ios-tuvali-library in Frameworks */,
C339223B2E79A536004A01EC /* InjiVcRenderer in Frameworks */,
9C4850432C3E5873002ECBD5 /* ios-tuvali-library in Frameworks */,
9CAE74EE2E2E38F800C2532C /* pixelpass in Frameworks */,
9CCCA19E2CF87A8400D5A461 /* securekeystore in Frameworks */,
C3F18B1A2E320C85007DBE73 /* OpenID4VP in Frameworks */,
@@ -144,6 +172,7 @@
13B07FAE1A68108700A75B9A /* Inji */ = {
isa = PBXGroup;
children = (
9C3BD83B2EA8D63A00101656 /* vcverifier */,
C3F6A9DE2E6618F6006C9904 /* RNInjiVcRenderer.m */,
C3F6A9DC2E66188D006C9904 /* RNInjiVcRenderer.swift */,
BB2F792B24A3F905000567C9 /* Supporting */,
@@ -151,10 +180,11 @@
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.mm */,
9C0E86B42BEE357A00E9F9F6 /* RNPixelpassModule.swift */,
9C3BD80D2EA8C8D600101656 /* RNVCVerifierModule.swift */,
9C3BD80F2EA8C8EE00101656 /* RNVCVerifierModule.m */,
9C4850472C3E59B5002ECBD5 /* RNEventEmitter.swift */,
9C4850522C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift */,
9C4850492C3E59B5002ECBD5 /* RNEventMapper.swift */,
9C7CDF3D2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift */,
9C48504A2C3E59B5002ECBD5 /* RNVersionModule.m */,
9C4850482C3E59B5002ECBD5 /* RNVersionModule.swift */,
9C4850462C3E59B5002ECBD5 /* RNWalletModule.m */,
@@ -169,6 +199,7 @@
E86208142C0335C5007C3E24 /* RNVCIClientModule.swift */,
E86208162C0335EC007C3E24 /* RNVCIClientModule.m */,
1E6875EA2CA554FD0086D870 /* RNOpenID4VPModule.m */,
9CCDB2DF2EAB63C9009E8E2B /* RNSecureKeystoreModule.swift */,
1E6875EC2CA5550F0086D870 /* RNOpenID4VPModule.swift */,
1D23B9CD47CFD7F3C87D202F /* PrivacyInfo.xcprivacy */,
1EED69F82DA913D30042EAFC /* IntentData.swift */,
@@ -235,6 +266,89 @@
name = Inji;
sourceTree = "<group>";
};
9C3BD8292EA8D63A00101656 /* constants */ = {
isa = PBXGroup;
children = (
9C3BD8272EA8D63A00101656 /* CredentialFormat.swift */,
9C3BD8282EA8D63A00101656 /* CredentialsVerifierConstant.swift */,
);
path = constants;
sourceTree = "<group>";
};
9C3BD82B2EA8D63A00101656 /* statusChecker */ = {
isa = PBXGroup;
children = (
9C3BD82A2EA8D63A00101656 /* LdpStatusChecker.swift */,
);
path = statusChecker;
sourceTree = "<group>";
};
9C3BD82D2EA8D63A00101656 /* types */ = {
isa = PBXGroup;
children = (
9C3BD82C2EA8D63A00101656 /* LdpVerifiableCredential.swift */,
);
path = types;
sourceTree = "<group>";
};
9C3BD8302EA8D63A00101656 /* credentialVerifier */ = {
isa = PBXGroup;
children = (
9C3BD82B2EA8D63A00101656 /* statusChecker */,
9C3BD82D2EA8D63A00101656 /* types */,
9C3BD82E2EA8D63A00101656 /* CredentialsVerifierFactory.swift */,
9C3BD82F2EA8D63A00101656 /* VerifiableCredential.swift */,
);
path = credentialVerifier;
sourceTree = "<group>";
};
9C3BD8322EA8D63A00101656 /* data */ = {
isa = PBXGroup;
children = (
9C3BD8312EA8D63A00101656 /* Data.swift */,
);
path = data;
sourceTree = "<group>";
};
9C3BD8352EA8D63A00101656 /* exception */ = {
isa = PBXGroup;
children = (
9C3BD8332EA8D63A00101656 /* NetworkManagerClientExceptions.swift */,
9C3BD8342EA8D63A00101656 /* RevocationCheckExceptions.swift */,
);
path = exception;
sourceTree = "<group>";
};
9C3BD8372EA8D63A00101656 /* networkManager */ = {
isa = PBXGroup;
children = (
9C3BD8362EA8D63A00101656 /* NetworkManagerClient.swift */,
);
path = networkManager;
sourceTree = "<group>";
};
9C3BD8392EA8D63A00101656 /* utils */ = {
isa = PBXGroup;
children = (
9C3BD8382EA8D63A00101656 /* base64.swift */,
);
path = utils;
sourceTree = "<group>";
};
9C3BD83B2EA8D63A00101656 /* vcverifier */ = {
isa = PBXGroup;
children = (
9C3BD8292EA8D63A00101656 /* constants */,
9C3BD8302EA8D63A00101656 /* credentialVerifier */,
9C3BD8322EA8D63A00101656 /* data */,
9C3BD8352EA8D63A00101656 /* exception */,
9C3BD8372EA8D63A00101656 /* networkManager */,
9C3BD8392EA8D63A00101656 /* utils */,
9C3BD83A2EA8D63A00101656 /* CredentialsVerifier.swift */,
);
path = vcverifier;
sourceTree = "<group>";
};
9CFB37462DDDC83900C199A8 /* Recovered References */ = {
isa = PBXGroup;
children = (
@@ -320,12 +434,12 @@
);
name = Inji;
packageProductDependencies = (
9C4850422C3E5873002ECBD5 /* ios-tuvali-library */,
9CCCA19D2CF87A8400D5A461 /* securekeystore */,
9CAE74ED2E2E38F800C2532C /* pixelpass */,
C3F18B192E320C85007DBE73 /* OpenID4VP */,
C3F18B1C2E320C9A007DBE73 /* VCIClient */,
C339223A2E79A536004A01EC /* InjiVcRenderer */,
9CCC57762E9D7C7000669DB7 /* ios-tuvali-library */,
);
productName = Inji;
productReference = 13B07F961A680F5B00A75B9A /* Inji.app */;
@@ -354,12 +468,12 @@
);
mainGroup = 83CBB9F61A601CBA00E9B192;
packageReferences = (
9C4850412C3E5873002ECBD5 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */,
9CCCA19C2CF87A8400D5A461 /* XCRemoteSwiftPackageReference "secure-keystore-ios-swift" */,
9CAE74EC2E2E38F800C2532C /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */,
C3F18B182E320C85007DBE73 /* XCRemoteSwiftPackageReference "inji-openid4vp-ios-swift" */,
C3F18B1B2E320C9A007DBE73 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */,
C33922392E79A536004A01EC /* XCRemoteSwiftPackageReference "inji-vc-renderer-ios-swift" */,
9CCC57752E9D7C7000669DB7 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */,
);
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
@@ -575,7 +689,9 @@
1E6875ED2CA5550F0086D870 /* RNOpenID4VPModule.swift in Sources */,
1E55C20B2DB120C2009DF38B /* RNDeepLinkIntentModule.swift in Sources */,
C3F6A9DD2E661896006C9904 /* RNInjiVcRenderer.swift in Sources */,
9C3BD8102EA8C8F800101656 /* RNVCVerifierModule.m in Sources */,
9C48504F2C3E59B5002ECBD5 /* RNVersionModule.swift in Sources */,
9C3BD80E2EA8C8DE00101656 /* RNVCVerifierModule.swift in Sources */,
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
9C0E86BB2BEE36C300E9F9F6 /* RNPixelpassModule.m in Sources */,
E86208152C0335C5007C3E24 /* RNVCIClientModule.swift in Sources */,
@@ -588,10 +704,22 @@
9C7CDF432C7CC13500243A9A /* RNSecureKeystoreModule.m in Sources */,
E86208172C0335EC007C3E24 /* RNVCIClientModule.m in Sources */,
9C48504E2C3E59B5002ECBD5 /* RNEventEmitter.swift in Sources */,
9C7CDF3E2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift in Sources */,
9C48504B2C3E59B5002ECBD5 /* RNWalletModule.swift in Sources */,
1EED69F92DA913D30042EAFC /* IntentData.swift in Sources */,
9C48504D2C3E59B5002ECBD5 /* RNWalletModule.m in Sources */,
9C3BD83C2EA8D63A00101656 /* CredentialsVerifierFactory.swift in Sources */,
9C3BD83D2EA8D63A00101656 /* VerifiableCredential.swift in Sources */,
9C3BD83E2EA8D63A00101656 /* NetworkManagerClientExceptions.swift in Sources */,
9C3BD83F2EA8D63A00101656 /* RevocationCheckExceptions.swift in Sources */,
9C3BD8402EA8D63A00101656 /* Data.swift in Sources */,
9CCDB2E02EAB63D9009E8E2B /* RNSecureKeystoreModule.swift in Sources */,
9C3BD8412EA8D63A00101656 /* CredentialsVerifier.swift in Sources */,
9C3BD8422EA8D63A00101656 /* LdpVerifiableCredential.swift in Sources */,
9C3BD8432EA8D63A00101656 /* CredentialsVerifierConstant.swift in Sources */,
9C3BD8442EA8D63A00101656 /* LdpStatusChecker.swift in Sources */,
9C3BD8452EA8D63A00101656 /* base64.swift in Sources */,
9C3BD8462EA8D63A00101656 /* NetworkManagerClient.swift in Sources */,
9C3BD8472EA8D63A00101656 /* CredentialFormat.swift in Sources */,
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
9C4850512C3E59B5002ECBD5 /* RNVersionModule.m in Sources */,
C3F6A9DF2E661903006C9904 /* RNInjiVcRenderer.m in Sources */,
@@ -837,14 +965,6 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
9C4850412C3E5873002ECBD5 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mosip/tuvali-ios-swift/";
requirement = {
kind = exactVersion;
version = 0.5.0;
};
};
9CAE74EC2E2E38F800C2532C /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mosip/pixelpass-ios-swift/";
@@ -853,6 +973,14 @@
version = 0.6.3;
};
};
9CCC57752E9D7C7000669DB7 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mosip/tuvali-ios-swift/";
requirement = {
kind = exactVersion;
version = 0.5.0;
};
};
9CCCA19C2CF87A8400D5A461 /* XCRemoteSwiftPackageReference "secure-keystore-ios-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mosip/secure-keystore-ios-swift";
@@ -888,16 +1016,16 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
9C4850422C3E5873002ECBD5 /* ios-tuvali-library */ = {
isa = XCSwiftPackageProductDependency;
package = 9C4850412C3E5873002ECBD5 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */;
productName = "ios-tuvali-library";
};
9CAE74ED2E2E38F800C2532C /* pixelpass */ = {
isa = XCSwiftPackageProductDependency;
package = 9CAE74EC2E2E38F800C2532C /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */;
productName = pixelpass;
};
9CCC57762E9D7C7000669DB7 /* ios-tuvali-library */ = {
isa = XCSwiftPackageProductDependency;
package = 9CCC57752E9D7C7000669DB7 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */;
productName = "ios-tuvali-library";
};
9CCCA19D2CF87A8400D5A461 /* securekeystore */ = {
isa = XCSwiftPackageProductDependency;
package = 9CCCA19C2CF87A8400D5A461 /* XCRemoteSwiftPackageReference "secure-keystore-ios-swift" */;

View File

@@ -67,7 +67,7 @@
{
"identity" : "gzipswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/1024jp/GzipSwift",
"location" : "https://github.com/1024jp/GzipSwift.git",
"state" : {
"revision" : "7a7f17761c76a932662ab77028a4329f67d645a4",
"version" : "5.2.0"

13
ios/RNVCVerifierModule.m Normal file
View File

@@ -0,0 +1,13 @@
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(VCVerifierModule, NSObject)
RCT_EXTERN_METHOD(
getCredentialStatus:
(NSString *)credential
format:(NSString *)format
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject
)
@end

View File

@@ -0,0 +1,47 @@
import Foundation
import React
@objc(VCVerifierModule)
class VCVerifierModule: NSObject, RCTBridgeModule {
static func moduleName() -> String {
return "VCVerifier"
}
static func requiresMainQueueSetup() -> Bool {
return false
}
@objc
func getCredentialStatus(
_ credential: String,
format: String,
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) {
Task {
do {
guard let credentialFormat = StatusCheckCredentialFormat(rawValue: format) else {
reject("INVALID_FORMAT", "Unsupported credential format: \(format)", nil)
return
}
let verifier = CredentialsVerifier()
let results = try await verifier.getCredentialStatus(credential: credential, format: credentialFormat)
let responseArray = results.map { result in
return [
"status": result.status,
"purpose": result.purpose,
"errorCode": result.error?.errorCode.rawValue,
"errorMessage": result.error?.message,
"statusListVC": result.statusListVC
]
}
resolve(responseArray)
} catch {
reject("VERIFICATION_FAILED", "Verification threw an error: \(error.localizedDescription)", error)
}
}
}
}

View File

@@ -0,0 +1,16 @@
import Foundation
public struct CredentialsVerifier {
public init() {}
public func getCredentialStatus(credential: String, format: StatusCheckCredentialFormat, statusPurposeList: [String] = []) async throws-> [CredentialStatusResult] {
do {
let verifier = CredentialVerifierFactory().get(format: format)
let credentialStatusArray = try await verifier.checkStatus(credential: credential, statusPurposes: statusPurposeList)
return credentialStatusArray ?? []
} catch{
throw error
}
}
}

View File

@@ -0,0 +1,7 @@
public enum StatusCheckCredentialFormat: String {
case ldpVc = "ldp_vc"
static func from(_ value: String) -> StatusCheckCredentialFormat? {
return StatusCheckCredentialFormat(rawValue: value)
}
}

View File

@@ -0,0 +1,3 @@
let ERROR_CODE_VC_REVOKED = "REVOKED"
let ERROR_CODE_VERIFICATION_FAILED = "ERR_VERIFICATION_FAILED"
let ERROR_VC_REVOKED = "Credential is revoked"

View File

@@ -0,0 +1,10 @@
import Foundation
struct CredentialVerifierFactory {
func get(format: StatusCheckCredentialFormat) -> VerifiableCredential {
switch format {
case .ldpVc:
return LdpVerifiableCredential()
}
}
}

View File

@@ -0,0 +1,5 @@
import Foundation
protocol VerifiableCredential {
func checkStatus(credential: String, statusPurposes: [String]?) async throws-> [CredentialStatusResult]?
}

View File

@@ -0,0 +1,219 @@
import Foundation
import Gzip
// MARK: - Credential Status Result
public struct CredentialStatusResult {
let purpose: String
let status: Int
let valid: Bool
let error: StatusCheckException?
let statusListVC: [String: Any]?
init(purpose: String, status: Int, valid: Bool, error: StatusCheckException?, statusListVC: [String: Any]? = nil) {
self.purpose = purpose
self.status = status
self.valid = valid
self.error = error
self.statusListVC = statusListVC
}
}
// MARK: - Error Types
enum StatusCheckErrorCode: String {
case rangeError = "RANGE_ERROR"
case statusVerificationError = "STATUS_VERIFICATION_ERROR"
case statusRetrievalError = "STATUS_RETRIEVAL_ERROR"
case invalidPurpose = "INVALID_PURPOSE"
case invalidIndex = "INVALID_INDEX"
case encodedListMissing = "ENCODED_LIST_MISSING"
case base64DecodeFailed = "BASE64_DECODE_FAILED"
case gzipDecompressFailed = "GZIP_DECOMPRESS_FAILED"
case unknownError = "UNKNOWN_ERROR"
}
struct StatusCheckException: Error {
let message: String
let errorCode: StatusCheckErrorCode
}
// MARK: - LDP Status Checker
final class LdpStatusChecker {
private let networkManager: NetworkManaging
private let minimumNumberOfEntries = 131072
private let defaultStatusSize = 1
init(networkManager: NetworkManaging = NetworkManagerClient.shared) {
self.networkManager = networkManager
}
func getStatuses(credential: String, statusPurposes: [String]? = nil) async throws -> [CredentialStatusResult]? {
guard
let data = credential.data(using: .utf8),
let vc = try JSONSerialization.jsonObject(with: data) as? [String: Any]
else {
throw StatusCheckException(message: "Invalid credential JSON", errorCode: .statusVerificationError)
}
let statusField = vc["credentialStatus"]
guard let statusEntries = normalizeStatusField(statusField) else { return nil }
let filteredEntries = filterEntries(statusEntries, statusPurposes)
guard !filteredEntries.isEmpty else { return nil }
var results: [CredentialStatusResult] = []
for entry in filteredEntries {
let purpose = (entry["statusPurpose"] as? String)?.lowercased() ?? ""
do {
let result = try await checkStatusEntry(entry: entry, purpose: purpose)
results.append(result)
} catch let error as StatusCheckException {
results.append(.init(purpose: purpose, status: -1, valid: false, error: error))
} catch {
let genericError = StatusCheckException(message: error.localizedDescription, errorCode: .unknownError)
results.append(.init(purpose: purpose, status: -1, valid: false, error: genericError))
}
}
return results
}
private func normalizeStatusField(_ statusField: Any?) -> [[String: Any]]? {
if let entry = statusField as? [String: Any] {
return [entry]
} else if let array = statusField as? [[String: Any]] {
return array
}
return nil
}
private func filterEntries(_ entries: [[String: Any]], _ purposes: [String]?) -> [[String: Any]] {
guard let purposes = purposes, !purposes.isEmpty else { return entries }
let lowerPurposes = purposes.map { $0.lowercased() }
return entries.filter { entry in
if let purpose = entry["statusPurpose"] as? String {
return lowerPurposes.contains(purpose.lowercased())
}
return false
}
}
private func checkStatusEntry(entry: [String: Any], purpose: String) async throws -> CredentialStatusResult {
try validateCredentialStatusEntry(entry: entry)
let statusListVC = try await fetchAndValidateStatusListVC(entry: entry, purpose: purpose)
return try computeStatusResult(entry: entry, statusListVCCredentialSubject: statusListVC[0], purpose: purpose, statusListVC: statusListVC[1])
}
private func validateCredentialStatusEntry(entry: [String: Any]) throws {
guard let type = entry["type"] as? String, type == "BitstringStatusListEntry" else {
throw StatusCheckException(message: "Invalid credentialStatus.type", errorCode: .statusVerificationError)
}
guard let index = entry["statusListIndex"] as? String, Int(index) != nil else {
throw StatusCheckException(message: "Invalid or missing statusListIndex", errorCode: .invalidIndex)
}
guard let url = entry["statusListCredential"] as? String, URL(string: url) != nil else {
throw StatusCheckException(message: "statusListCredential must be a valid URL", errorCode: .invalidIndex)
}
}
private func fetchAndValidateStatusListVC(entry: [String: Any], purpose: String) async throws -> [[String: Any]] {
let url = entry["statusListCredential"] as! String
let vc: [String: Any]
do {
vc = try await networkManager.sendHTTPRequest(url: url, method: .get, bodyParams: nil, headers: nil)
} catch {
throw StatusCheckException(message: "Retrieval of the status list failed: \(error.localizedDescription)", errorCode: .statusRetrievalError)
}
guard let subject = vc["credentialSubject"] as? [String: Any] else {
throw StatusCheckException(message: "Missing credentialSubject", errorCode: .statusVerificationError)
}
guard (subject["type"] as? String) == "BitstringStatusList" else {
throw StatusCheckException(message: "Invalid credentialSubject.type", errorCode: .statusVerificationError)
}
guard (subject["statusPurpose"] as? String)?.lowercased() == purpose else {
throw StatusCheckException(message: "Status list VC purpose mismatch", errorCode: .invalidPurpose)
}
let now = Date().timeIntervalSince1970 * 1000
if let validFromStr = subject["validFrom"] as? String,
let validFromMillis = ISO8601DateFormatter().date(from: validFromStr)?.timeIntervalSince1970, now < validFromMillis * 1000 {
throw StatusCheckException(message: "Status list VC is not yet valid (validFrom=\(validFromStr))", errorCode: .statusVerificationError)
}
if let validUntilStr = subject["validUntil"] as? String,
let validUntilMillis = ISO8601DateFormatter().date(from: validUntilStr)?.timeIntervalSince1970, now > validUntilMillis * 1000 {
throw StatusCheckException(message: "Status list VC has expired (validUntil=\(validUntilStr))", errorCode: .statusVerificationError)
}
return [subject, vc]
}
private func computeStatusResult(entry: [String: Any], statusListVCCredentialSubject: [String: Any], purpose: String, statusListVC: [String: Any]) throws -> CredentialStatusResult {
guard
let encodedList = statusListVCCredentialSubject["encodedList"] as? String,
let indexStr = entry["statusListIndex"] as? String,
let index = Int(indexStr)
else {
throw StatusCheckException(message: "Missing encodedList or statusListIndex", errorCode: .encodedListMissing)
}
let statusSize = (statusListVCCredentialSubject["statusSize"] as? Int) ?? defaultStatusSize
guard statusSize > 0 else {
throw StatusCheckException(message: "Invalid statusSize", errorCode: .statusVerificationError)
}
if statusSize > 1 {
guard
let statusMessage = entry["statusMessage"] as? [String: Any],
statusMessage.count == (1 << statusSize)
else {
throw StatusCheckException(message: "statusMessage count mismatch", errorCode: .statusVerificationError)
}
}
let bitSet = try decodeEncodedList(encodedList)
let bitPosition = index * statusSize
let totalBits = bitSet.count * 8
guard bitPosition < totalBits else {
throw StatusCheckException(message: "Bit position out of range", errorCode: .rangeError)
}
let statusValue = readBits(from: bitSet, start: bitPosition, count: statusSize)
return .init(purpose: purpose, status: statusValue, valid: statusValue == 0, error: nil, statusListVC: statusListVC)
}
private func readBits(from bitSet: [UInt8], start: Int, count: Int) -> Int {
var value = 0
for i in 0 ..< count {
if readBit(bitSet, position: start + i) {
value |= (1 << (count - i - 1))
}
}
return value
}
private func readBit(_ bitSet: [UInt8], position: Int) -> Bool {
let byteIndex = position / 8
let bitIndex = position % 8
guard byteIndex < bitSet.count else { return false }
let byte = bitSet[byteIndex]
return ((byte >> (7 - bitIndex)) & 1) == 1
}
private func decodeEncodedList(_ encodedList: String) throws -> [UInt8] {
let base64url = encodedList.hasPrefix("u") ? String(encodedList.dropFirst()) : encodedList
guard let compressed = try? decodeBase64URL(base64url) else {
throw StatusCheckException(message: "Base64url decoding failed", errorCode: .base64DecodeFailed)
}
return try decompressGzip(data: compressed)
}
private func decompressGzip(data: Data) throws -> [UInt8] {
guard let decompressed = try? data.gunzipped() else {
throw StatusCheckException(message: "GZIP decompression failed", errorCode: .gzipDecompressFailed)
}
return [UInt8](decompressed)
}
}

View File

@@ -0,0 +1,8 @@
import Foundation
struct LdpVerifiableCredential: VerifiableCredential {
func checkStatus(credential: String, statusPurposes: [String]?) async throws-> [CredentialStatusResult]? {
try await LdpStatusChecker().getStatuses(credential: credential)
}
}

View File

@@ -0,0 +1,18 @@
public struct VerificationResult {
let verificationStatus: Bool
let verificationMessage: String
let verificationErrorCode: String
let statusListVC: String?
init(
verificationStatus: Bool,
verificationMessage: String = "",
verificationErrorCode: String,
statusListVC: String? = nil
) {
self.verificationStatus = verificationStatus
self.verificationMessage = verificationMessage
self.verificationErrorCode = verificationErrorCode
self.statusListVC = statusListVC
}
}

View File

@@ -0,0 +1,15 @@
import Foundation
enum NetworkManagerClientException: Error, LocalizedError {
case timeout
case failed(String)
var errorDescription: String? {
switch self {
case .timeout:
return "Connection timed out"
case .failed(let message):
return "Network request failed with error response - \(message)"
}
}
}

View File

@@ -0,0 +1,24 @@
import Foundation
enum RevocationCheckException: Error, LocalizedError {
case invalidCredentialJSON
case missingStatusListCredential
case invalidStatusListIndex
case invalidStatusListContent
case failed(String)
var errorDescription: String? {
switch self {
case .invalidCredentialJSON:
return "Invalid or malformed credential JSON."
case .missingStatusListCredential:
return "Missing 'statusListCredential' field in credentialStatus."
case .invalidStatusListIndex:
return "Invalid or missing 'statusListIndex' in credentialStatus."
case .invalidStatusListContent:
return "Missing or malformed 'credentialSubject.encodedList' in the status list VC."
case .failed(let message):
return "Failed to check revocation: \(message)"
}
}
}

View File

@@ -0,0 +1,71 @@
import Foundation
enum HttpMethod: String {
case get = "GET"
case post = "POST"
}
protocol NetworkManaging {
func sendHTTPRequest(url: String, method: HttpMethod, bodyParams: [String: String]?,
headers: [String: String]?) async throws -> [String: Any]
}
class NetworkManagerClient:NetworkManaging {
static let shared = NetworkManagerClient()
func sendHTTPRequest(
url: String,
method: HttpMethod,
bodyParams: [String: String]? = nil,
headers: [String: String]? = nil
) async throws -> [String: Any] {
guard let url = URL(string: url) else {
throw NetworkManagerClientException.failed("Invalid Url")
}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
if method == .post, let bodyParams = bodyParams {
let formBody = bodyParams
.map { "\($0.key)=\($0.value)" }
.joined(separator: "&")
request.httpBody = formBody.data(using: .utf8)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
}
headers?.forEach { key, value in
request.setValue(value, forHTTPHeaderField: key)
}
let (data, response): (Data, URLResponse)
do {
(data, response) = try await URLSession.shared.data(for: request)
} catch {
if (error as NSError).code == NSURLErrorTimedOut {
throw NetworkManagerClientException.timeout
}
throw NetworkManagerClientException.failed(error.localizedDescription)
}
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkManagerClientException.failed("Invalid network Response")
}
if !(200...299).contains(httpResponse.statusCode) {
if let serverError = String(data: data, encoding: .utf8), !serverError.isEmpty {
throw NetworkManagerClientException.failed("HTTP \(httpResponse.statusCode): \(serverError)")
} else {
throw NetworkManagerClientException.failed("HTTP \(httpResponse.statusCode): Unknown error")
}
}
guard let jsonObject = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw NetworkManagerClientException.failed("Decoding network response failed")
}
return jsonObject
}
}

View File

@@ -0,0 +1,18 @@
import Foundation
func decodeBase64URL(_ base64url: String) throws -> Data {
var base64 = base64url
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
let padding = 4 - (base64.count % 4)
if padding < 4 {
base64 += String(repeating: "=", count: padding)
}
guard let data = Data(base64Encoded: base64) else {
throw NSError(domain: "Base64URLDecodeError", code: -1)
}
return data
}

View File

@@ -15,6 +15,12 @@
"WALLET_BINDING_FAILURE": "فشل تفعيل {{idType}}.",
"VC_REMOVED": "تم إزالة {{idType}} من المحفظة.",
"TAMPERED_VC_REMOVED": "تم إزالة {{idType}} من المحفظة بسبب العبث.",
"VC_STATUS_CHANGED": {
"VALID": "تم الانتهاء من فحص الحالة. حالة {{idType}} صالحة.",
"REVOKED": "تم الانتهاء من فحص الحالة. حالة {{idType}} تم إلغاؤها.",
"EXPIRED": "تم الانتهاء من فحص الحالة. حالة {{idType}} منتهية الصلاحية.",
"PENDING": "تم الانتهاء من فحص الحالة. حالة {{idType}} قيد التحقق."
},
"vpSharing": {
"SHARED_SUCCESSFULLY": "تمت مشاركة العرض التقديمي لبيانات الاعتماد بنجاح.",
"SHARED_WITH_FACE_VERIFIACTION": "تم التحقق من الوجه بنجاح، وتمت مشاركة العرض التقديمي لبيانات الاعتماد بنجاح.",
@@ -73,6 +79,8 @@
"VcDetails": {
"generatedOn": "تم إنشاؤه في",
"status": "الحالة",
"lastChecked": "آخر تحقق",
"revoked": "تم الإلغاء",
"valid": "صالح",
"expired": "منتهي الصلاحية",
"pending": "قيد الانتظار",
@@ -125,6 +133,8 @@
"unPinCard": "إزالة التثبيت",
"pinCard": "دبوس",
"share": "يشارك",
"reverify": "تحقق من حالة البطاقة",
"new": "جديد",
"shareWithSelfie": "شارك مع سيلفي",
"offlineAuthDisabledMessage": "انقر هنا لتمكين استخدام بيانات الاعتماد هذه للمصادقة عبر الإنترنت.",
"viewActivityLog": "عرض سجل النشاط",
@@ -191,7 +201,16 @@
"alternateBiometricSuccess": "تم النجاح! يمكنك الآن استخدام القياسات الحيوية لفتح تطبيق Inji.",
"activated": "تم تمكين بيانات الاعتماد للمصادقة عبر الإنترنت.",
"keyPreferenceSuccess": "تم حفظ تفضيلات المفاتيح الخاصة بك بنجاح!",
"keyPreferenceError": "عذرًا! حدث خطأ أثناء تعيين تفضيلات المفتاح الخاصة بك"
"keyPreferenceError": "عذرًا! حدث خطأ أثناء تعيين تفضيلات المفتاح الخاصة بك",
"reverifiedSuccessfully": {
"VALID": "تم التحقق من الحالة بنجاح. حالة {{vcType}} الخاصة بك صالحة.",
"REVOKED": "تم التحقق من الحالة بنجاح. حالة {{vcType}} الخاصة بك تم إلغاؤها.",
"PENDING": "تم التحقق من الحالة بنجاح. حالة {{vcType}} الخاصة بك قيد التحقق.",
"EXPIRED": "تم التحقق من الحالة بنجاح. حالة {{vcType}} الخاصة بك منتهية الصلاحية."
},
"reverificationFailed": {
"PENDING": "فشل التحقق من الحالة. تعذر التحقق من {{vcType}} الخاصة بك، يرجى المحاولة مرة أخرى لاحقًا."
}
},
"AboutInji": {
"aboutInji": "حول محفظة إنجي",
@@ -503,7 +522,9 @@
"VcVerificationBanner": {
"inProgress": "نحن نقوم بالتحقق من بطاقتك، قد يستغرق هذا بعض الوقت. بمجرد التحقق، ستتمكن من تفعيل بطاقتك.",
"success": "تم التحقق من {{vcDetails}} بنجاح وهو متاح الآن للتنشيط.",
"error": "عذرًا، لا يمكننا التحقق من {{vcDetails}} الآن. الرجاء معاودة المحاولة في وقت لاحق. وحتى ذلك الحين، لن تتمكن من تفعيل بطاقتك أو مشاركتها."
"error": "عذرًا، لا يمكننا التحقق من {{vcDetails}} الآن. الرجاء معاودة المحاولة في وقت لاحق. وحتى ذلك الحين، لن تتمكن من تفعيل بطاقتك أو مشاركتها.",
"revoked": "تم إلغاء {{vcDetails}} من قبل الجهة المصدرة ولم يعد صالحًا. يرجى الاتصال بالجهة المصدرة لمزيد من المعلومات.",
"expired": "{{vcDetails}} منتهي الصلاحية ولم يعد صالحًا. يرجى الاتصال بالجهة المصدرة لمزيد من المعلومات."
},
"ViewVcModal": {
"title": "تفاصيل الهوية",
@@ -516,6 +537,10 @@
"pending": {
"title": "وضع انتظار:",
"description": "التحقق معلق حاليًا بسبب مشكلات فنية."
},
"revoked": {
"title": "الحالة الملغاة:",
"description": "تم إلغاء بيانات الاعتماد."
}
}
},

View File

@@ -15,6 +15,12 @@
"WALLET_BINDING_FAILURE": "Activation of {{idType}} has failed.",
"VC_REMOVED": "{{idType}} is removed from wallet.",
"TAMPERED_VC_REMOVED": "{{idType}} is removed from from wallet due to tampering.",
"VC_STATUS_CHANGED": {
"VALID": "Status check completed. The status of {{idType}} is Valid.",
"REVOKED": "Status check completed. The status of {{idType}} is Revoked.",
"EXPIRED": "Status check completed. The status of {{idType}} is Expired.",
"PENDING": "Status check completed. The status of {{idType}} is Pending Verification."
},
"vpSharing": {
"SHARED_SUCCESSFULLY": "Credential Presentation is shared successfully.",
"SHARED_WITH_FACE_VERIFIACTION": "Face verification is successful, and the Credential Presentation is shared successfully.",
@@ -73,7 +79,9 @@
"VcDetails": {
"generatedOn": "Generated On",
"status": "Status",
"lastChecked": "Last Checked",
"valid": "Valid",
"revoked": "Revoked",
"pending": "Pending",
"expired": "Expired",
"photo": "Photo",
@@ -116,15 +124,17 @@
"shareQRCode": "Share QR Code",
"disclosureNote": "Your card includes specific details marked by the issuer as selectively shareable. These can be shared independently during credential presentation. Other fields not listed here can only be shared as part of the full credential, if requested by the verifier.",
"disclosureNoteTitle": "Please note",
"disclosedFieldsDescription":"Your card includes the following selectively shareable details listed below.",
"disclosedFieldsTitle":"Information you choose to share",
"disclosureInfoNote":"Fields next to this icon indicate that the information can be shared selectively."
"disclosedFieldsDescription": "Your card includes the following selectively shareable details listed below.",
"disclosedFieldsTitle": "Information you choose to share",
"disclosureInfoNote": "Fields next to this icon indicate that the information can be shared selectively."
},
"HomeScreenKebabPopUp": {
"title": "More Options",
"unPinCard": "Unpin",
"pinCard": "Pin",
"share": "Share",
"reverify": "Check Card Status",
"new": "NEW",
"shareWithSelfie": "Share with Selfie",
"offlineAuthDisabledMessage": "Click here to enable this credentials to be used for online authentication.",
"viewActivityLog": "View Activity Log",
@@ -192,7 +202,16 @@
"alternateBiometricSuccess": "Success! You can now use biometrics to unlock Inji app.",
"activated": "Credentials are enabled for online authentication.",
"keyPreferenceSuccess": "Your key preferences have been saved successfully!",
"keyPreferenceError": "Sorry! Error setting your key preferences"
"keyPreferenceError": "Sorry! Error setting your key preferences",
"reverifiedSuccessfully": {
"VALID": "Status check successful. The status of your {{vcType}} is Valid.",
"REVOKED": "Status check successful. The status of your {{vcType}} is Revoked.",
"PENDING": "Status check successful. The status of your {{vcType}} is Pending Verification.",
"EXPIRED": "Status check successful. The status of your {{vcType}} is Expired."
},
"reverificationFailed": {
"PENDING": "Status check failed. Unable to verify your {{vcType}}, please try again later"
}
},
"AboutInji": {
"aboutInji": "About Inji Wallet",
@@ -504,7 +523,9 @@
"VcVerificationBanner": {
"inProgress": "We are validating your card, this may take sometime. Once verified, youll be able to activate your card.",
"success": "{{vcDetails}} is verified successfully and now available for activation.",
"error": "Sorry, we are unable to verify the {{vcDetails}} right now. Please try again later. Until then, you won't be able to activate or share your card."
"error": "Sorry, we are unable to verify the {{vcDetails}} right now. Please try again later. Until then, you won't be able to activate or share your card.",
"revoked": "{{vcDetails}} has been revoked by the issuer and is no longer valid. Please contact the issuer for more information.",
"expired": "{{vcDetails}} has expired and is no longer valid. Please contact the issuer for more information."
},
"ViewVcModal": {
"title": "ID Details",
@@ -521,6 +542,10 @@
"expired": {
"title": "Expired Status:",
"description": "The credential has expired."
},
"revoked": {
"title": "Revoked Status:",
"description": "The credential has been revoked."
}
}
},
@@ -872,10 +897,10 @@
"cardSelected": "card selected",
"unCheck": "Uncheck",
"checkAll": "Check All",
"selectedFieldsTitle":"Information you choose to share",
"selectedFieldsSubtitle":"Your card includes the following selectively shareable details listed below.",
"unselectAll":"Unselect All",
"selectAll":"Select All",
"selectedFieldsTitle": "Information you choose to share",
"selectedFieldsSubtitle": "Your card includes the following selectively shareable details listed below.",
"unselectAll": "Unselect All",
"selectAll": "Select All",
"consentDialog": {
"title": "Consent Required",
"message": "We require your consent to share your verifiable credentials with {{verifierName}}. This will enable us to verify your identity and fulfil your service requests. Choose \"Yes, Proceed\" to consent or \"Decline\" if you do not wish to share your credentials with {{verifierName}}.",
@@ -1147,7 +1172,7 @@
"This issuer will be added to your trusted list.",
"You won't need to review trust again when downloading from them next time."
],
"verifierInfoPoints":[
"verifierInfoPoints": [
"The card will be securely shared with the verifier.",
"The verifier will be added to your trusted list.",
"You wont need to review trust again when sharing with them in the future."

View File

@@ -15,6 +15,12 @@
"WALLET_BINDING_FAILURE": "Nabigo ang pag-activate ng {{idType}}.",
"VC_REMOVED": "Ang {{idType}} ay inalis sa wallet.",
"TAMPERED_VC_REMOVED": "Ang {{idType}} ay inalis mula sa wallet dahil sa pakikialam.",
"VC_STATUS_CHANGED": {
"VALID": "Tapos na ang pag-check ng status. Ang status ng {{idType}} ay Valid.",
"REVOKED": "Tapos na ang pag-check ng status. Ang status ng {{idType}} ay Binawi.",
"EXPIRED": "Tapos na ang pag-check ng status. Ang status ng {{idType}} ay Expired.",
"PENDING": "Tapos na ang pag-check ng status. Ang status ng {{idType}} ay Naka-pending para sa beripikasyon."
},
"vpSharing": {
"SHARED_SUCCESSFULLY": "Matagumpay na naibahagi ang Pagtatanghal ng Kredensyal.",
"SHARED_WITH_FACE_VERIFIACTION": "Matagumpay ang pag-verify sa mukha, at matagumpay na naibahagi ang Credential Presentation.",
@@ -73,6 +79,8 @@
"VcDetails": {
"generatedOn": "Nilikha noong",
"status": "Katayuan",
"lastChecked": "Huling Suri",
"revoked": "Binawi",
"valid": "Napatunayan",
"pending": "Nakabinbin",
"expired": "Nag-expire na",
@@ -125,6 +133,7 @@
"unPinCard": "I-unpin",
"pinCard": "Pin",
"share": "Ibahagi",
"reverify": "Suriin ang Katayuan ng Card",
"shareWithSelfie": "Ibahagi sa Selfie",
"offlineAuthDisabledMessage": "Mag-click dito upang paganahin ang mga kredensyal na ito na magamit para sa online na pagpapatotoo.",
"viewActivityLog": "Tingnan ang log ng aktibidad",
@@ -191,7 +200,16 @@
"alternateBiometricSuccess": "Tagumpay! Maaari ka na ngayong gumamit ng biometrics upang i-unlock ang Inji app.",
"activated": "Ang mga kredensyal ay pinagana para sa online na pagpapatunay.",
"keyPreferenceSuccess": "Ang iyong mga kagustuhan sa key ay matagumpay na na-save!",
"keyPreferenceError": "Paumanhin! May error sa pagsasaayos ng iyong mga kagustuhan sa key"
"keyPreferenceError": "Paumanhin! May error sa pagsasaayos ng iyong mga kagustuhan sa key",
"reverifiedSuccessfully": {
"VALID": "Matagumpay ang pag-check ng status. Ang status ng iyong {{vcType}} ay Valid.",
"REVOKED": "Matagumpay ang pag-check ng status. Ang status ng iyong {{vcType}} ay Binawi.",
"PENDING": "Matagumpay ang pag-check ng status. Ang status ng iyong {{vcType}} ay Naka-pending para sa beripikasyon.",
"EXPIRED": "Matagumpay ang pag-check ng status. Ang status ng iyong {{vcType}} ay Expired."
},
"reverificationFailed": {
"PENDING": "Nabigong i-check ang status. Hindi ma-verify ang iyong {{vcType}}, pakisubukan muli mamaya."
}
},
"AboutInji": {
"aboutInji": "Tungkol kay Inji Wallet",
@@ -503,7 +521,9 @@
"VcVerificationBanner": {
"inProgress": "Pina-validate namin ang iyong card, maaaring magtagal ito. Kapag na-verify na, magagawa mong i-activate ang iyong card.",
"success": "Matagumpay na na-verify ang {{vcDetails}} at magagamit na ngayon para sa pag-activate.",
"error": "Paumanhin, hindi namin ma-verify ang {{vcDetails}} ngayon. Subukang muli mamaya. Hanggang sa panahong iyon, hindi mo maa-activate o maibabahagi ang iyong card."
"error": "Paumanhin, hindi namin ma-verify ang {{vcDetails}} ngayon. Subukang muli mamaya. Hanggang sa panahong iyon, hindi mo maa-activate o maibabahagi ang iyong card.",
"revoked": "Ang {{vcDetails}} ay binawi na ng issuer at hindi na ito valid. Mangyaring makipag-ugnayan sa issuer para sa karagdagang impormasyon.",
"expired": "Ang {{vcDetails}} ay expired na at hindi na ito valid. Mangyaring makipag-ugnayan sa issuer para sa karagdagang impormasyon."
},
"ViewVcModal": {
"title": "Mga Detalye ng ID",
@@ -520,6 +540,10 @@
"expired": {
"title": "Nag-expire na Katayuan:",
"description": "Nag-expire na ang kredensyal."
},
"revoked": {
"title": "Binawing Status:",
"description": "Ang credential ay binawi na."
}
}
},

View File

@@ -15,6 +15,12 @@
"WALLET_BINDING_FAILURE": "{{idType}} का सक्रियण विफल हो गया।",
"VC_REMOVED": "{{idType}} को वॉलेट से हटा दिया गया।",
"TAMPERED_VC_REMOVED": "{{idType}} को छेड़छाड़ के कारण वॉलेट से हटा दिया गया।",
"VC_STATUS_CHANGED": {
"VALID": "स्थिति जांच पूरी हो गई है। {{idType}} की स्थिति मान्य है।",
"REVOKED": "स्थिति जांच पूरी हो गई है। {{idType}} की स्थिति रद्द कर दी गई है।",
"EXPIRED": "स्थिति जांच पूरी हो गई है। {{idType}} की स्थिति समाप्त हो गई है।",
"PENDING": "स्थिति जांच पूरी हो गई है। {{idType}} की स्थिति सत्यापन लंबित है।"
},
"vpSharing": {
"SHARED_SUCCESSFULLY": "क्रेडेंशियल प्रेजेंटेशन सफलतापूर्वक साझा किया गया है.",
"SHARED_WITH_FACE_VERIFIACTION": "चेहरा सत्यापन सफल है, और क्रेडेंशियल प्रस्तुति सफलतापूर्वक साझा की गई है।",
@@ -73,6 +79,8 @@
"VcDetails": {
"generatedOn": "पर उत्पन्न हुआ",
"status": "दर्जा",
"lastChecked": "अंतिम जांच",
"revoked": "रद्द किया गया",
"valid": "वैध",
"expired": "खत्म हो चुका",
"pending": "लंबित",
@@ -126,6 +134,8 @@
"unPinCard": "अनपिन",
"pinCard": "पिन",
"share": "शेयर करना",
"reverify": "कार्ड की स्थिति जांचें",
"new": "नया",
"shareWithSelfie": "सेल्फी के साथ साझा करें",
"offlineAuthDisabledMessage": "ऑनलाइन प्रमाणीकरण के लिए इस क्रेडेंशियल का उपयोग सक्षम करने के लिए यहां क्लिक करें।",
"viewActivityLog": "गतिविधि लॉग देखें",
@@ -192,7 +202,16 @@
"alternateBiometricSuccess": "सफलता! अब आप Inji ऐप को अनलॉक करने के लिए बायोमेट्रिक्स का उपयोग कर सकते हैं।",
"activated": "ऑनलाइन प्रमाणीकरण के लिए क्रेडेंशियल सक्षम हैं।",
"keyPreferenceSuccess": "आपकी कुंजी प्राथमिकताएँ सफलतापूर्वक सहेजी गई हैं!",
"keyPreferenceError": "माफ़ कीजिये! आपकी कुंजी प्राथमिकताएँ सेट करने में त्रुटि हुई है"
"keyPreferenceError": "माफ़ कीजिये! आपकी कुंजी प्राथमिकताएँ सेट करने में त्रुटि हुई है",
"reverifiedSuccessfully": {
"VALID": "स्थिति जांच सफल रही। आपके {{vcType}} की स्थिति वैध है।",
"REVOKED": "स्थिति जांच सफल रही। आपके {{vcType}} की स्थिति रद्द कर दी गई है।",
"PENDING": "स्थिति जांच सफल रही। आपके {{vcType}} की स्थिति सत्यापन लंबित है।",
"EXPIRED": "स्थिति जांच सफल रही। आपके {{vcType}} की स्थिति समाप्त हो गई है।"
},
"reverificationFailed": {
"PENDING": "स्थिति जांच विफल रही। आपके {{vcType}} को सत्यापित नहीं किया जा सका, कृपया बाद में पुनः प्रयास करें।"
}
},
"AboutInji": {
"aboutInji": "इंजी वॉलेट के बारे में",
@@ -505,7 +524,9 @@
"VcVerificationBanner": {
"inProgress": "हम आपके कार्ड का सत्यापन कर रहे हैं, इसमें कुछ समय लग सकता है। एक बार सत्यापित हो जाने पर, आप अपना कार्ड सक्रिय कर सकेंगे।",
"success": "{{vcDetails}} सफलतापूर्वक सत्यापित हो गया है और अब सक्रियण के लिए उपलब्ध है।",
"error": "क्षमा करें, हम अभी {{vcDetails}} को सत्यापित करने में असमर्थ हैं। कृपया बाद में पुन: प्रयास करें। तब तक, आप अपना कार्ड सक्रिय या साझा नहीं कर पाएंगे।"
"error": "क्षमा करें, हम अभी {{vcDetails}} को सत्यापित करने में असमर्थ हैं। कृपया बाद में पुन: प्रयास करें। तब तक, आप अपना कार्ड सक्रिय या साझा नहीं कर पाएंगे।",
"revoked": "{{vcDetails}} को जारीकर्ता द्वारा रद्द कर दिया गया है और यह अब मान्य नहीं है। अधिक जानकारी के लिए कृपया जारीकर्ता से संपर्क करें।",
"expired": "{{vcDetails}} की वैधता समाप्त हो गई है और यह अब मान्य नहीं है। अधिक जानकारी के लिए कृपया जारीकर्ता से संपर्क करें।"
},
"ViewVcModal": {
"title": "आईडी विवरण",
@@ -522,6 +543,10 @@
"expired": {
"title": "समाप्त स्थिति:",
"description": "क्रेडेंशियल समाप्त हो गया है।"
},
"revoked": {
"title": "रद्द की गई स्थिति:",
"description": "साख्यपत्र को रद्द कर दिया गया है।"
}
}
},
@@ -1140,5 +1165,4 @@
"verifierConfirm": "हाँ, मुझे इस सत्यापनकर्ता पर भरोसा है",
"cancel": "नहीं, मुझे वापस ले चलें"
}
}
}

View File

@@ -15,6 +15,12 @@
"WALLET_BINDING_FAILURE": "{{idType}} ಸಕ್ರಿಯಗೊಳಿಸುವಿಕೆ ವಿಫಲವಾಗಿದೆ.",
"VC_REMOVED": "{{idType}} ವಾಲೆಟ್‌ನಿಂದ ತೆಗೆದುಹಾಕಲಾಗಿದೆ.",
"TAMPERED_VC_REMOVED": "ತಿದ್ದುವಿಕೆಯಿಂದಾಗಿ {{idType}} ಅನ್ನು ವ್ಯಾಲೆಟ್‌ನಿಂದ ತೆಗೆದುಹಾಕಲಾಗಿದೆ.",
"VC_STATUS_CHANGED": {
"VALID": "ಸ್ಥಿತಿಯ ಪರಿಶೀಲನೆ ಪೂರ್ಣಗೊಂಡಿದೆ. {{idType}} ಸ್ಥಿತಿ ಮಾನ್ಯವಾಗಿದೆ.",
"REVOKED": "ಸ್ಥಿತಿಯ ಪರಿಶೀಲನೆ ಪೂರ್ಣಗೊಂಡಿದೆ. {{idType}} ಸ್ಥಿತಿ ಹಿಂಪಡೆಯಲಾಗಿದೆ.",
"EXPIRED": "ಸ್ಥಿತಿಯ ಪರಿಶೀಲನೆ ಪೂರ್ಣಗೊಂಡಿದೆ. {{idType}} ಸ್ಥಿತಿ ಗಡಿವಾದವಾಗಿದೆ.",
"PENDING": "ಸ್ಥಿತಿಯ ಪರಿಶೀಲನೆ ಪೂರ್ಣಗೊಂಡಿದೆ. {{idType}} ಸ್ಥಿತಿ ಪರಿಶೀಲನೆಯಲ್ಲಿದೆ."
},
"vpSharing": {
"SHARED_SUCCESSFULLY": "ರುಜುವಾತು ಪ್ರಸ್ತುತಿಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಹಂಚಿಕೊಳ್ಳಲಾಗಿದೆ.",
"SHARED_WITH_FACE_VERIFIACTION": "ಮುಖ ಪರಿಶೀಲನೆ ಯಶಸ್ವಿಯಾಗಿದೆ ಮತ್ತು ರುಜುವಾತು ಪ್ರಸ್ತುತಿಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಹಂಚಿಕೊಳ್ಳಲಾಗಿದೆ.",
@@ -73,6 +79,8 @@
"VcDetails": {
"generatedOn": "ಜನರೇಟೆಡ್ ಆನ್",
"status": "ಸ್ಥಿತಿ",
"lastChecked": "ಕೊನೆಯದಾಗಿ ಪರಿಶೀಲಿಸಲಾಯಿತು",
"revoked": "ಹಿಂಪಡೆಯಲಾಗಿದೆ",
"valid": "ಮಾನ್ಯ",
"expired": "ಅವಧಿ ಮೀರಿದೆ",
"pending": "ಬಾಕಿಯಿದೆ",
@@ -125,6 +133,8 @@
"unPinCard": "ಅನ್‌ಪಿನ್",
"pinCard": "ಪಿನ್",
"share": "ಹಂಚಿಕೊಳ್ಳಿ",
"reverify": "ಕಾರ್ಡ್ ಸ್ಥಿತಿಯನ್ನು ಪರಿಶೀಲಿಸಿ",
"new": "ಹೊಸದು",
"shareWithSelfie": "ಸೆಲ್ಫಿಯೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಿ",
"offlineAuthDisabledMessage": "ಆನ್‌ಲೈನ್ ದೃಢೀಕರಣಕ್ಕಾಗಿ ಬಳಸಲು ಈ ರುಜುವಾತುಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ಇಲ್ಲಿ ಕ್ಲಿಕ್ ಮಾಡಿ.",
"viewActivityLog": "ಚಟುವಟಿಕೆ ಲಾಗ್ ಅನ್ನು ವೀಕ್ಷಿಸಿ",
@@ -191,7 +201,16 @@
"alternateBiometricSuccess": "ಯಶಸ್ವಿ! ನೀವು ಈಗ ಇಂಜಿ ಅಪ್ಲಿಕೇಶನ್ ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಬಯೋಮೆಟ್ರಿಕ್‌ಗಳನ್ನು ಬಳಸಬಹುದು.",
"activated": "ಆನ್‌ಲೈನ್ ದೃಢೀಕರಣಕ್ಕಾಗಿ ರುಜುವಾತುಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ.",
"keyPreferenceSuccess": "ನಿಮ್ಮ ಕೀ ಆಸ್ತಿಕತೆಗಳನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಉಳಿಸಲಾಗಿದೆ!",
"keyPreferenceError": "ಕ್ಷಮಿಸಿ! ನಿಮ್ಮ ಪ್ರಮುಖ ಆದ್ಯತೆಗಳನ್ನು ಹೊಂದಿಸುವಲ್ಲಿ ದೋಷ ಕಂಡುಬಂದಿದೆ."
"keyPreferenceError": "ಕ್ಷಮಿಸಿ! ನಿಮ್ಮ ಪ್ರಮುಖ ಆದ್ಯತೆಗಳನ್ನು ಹೊಂದಿಸುವಲ್ಲಿ ದೋಷ ಕಂಡುಬಂದಿದೆ.",
"reverifiedSuccessfully": {
"VALID": "ಸ್ಥಿತಿಯ ಪರಿಶೀಲನೆ ಯಶಸ್ವಿಯಾಗಿದೆ. ನಿಮ್ಮ {{vcType}} ಸ್ಥಿತಿ ಮಾನ್ಯವಾಗಿದೆ.",
"REVOKED": "ಸ್ಥಿತಿಯ ಪರಿಶೀಲನೆ ಯಶಸ್ವಿಯಾಗಿದೆ. ನಿಮ್ಮ {{vcType}} ಸ್ಥಿತಿ ಹಿಂಪಡೆಯಲಾಗಿದೆ.",
"PENDING": "ಸ್ಥಿತಿಯ ಪರಿಶೀಲನೆ ಯಶಸ್ವಿಯಾಗಿದೆ. ನಿಮ್ಮ {{vcType}} ಸ್ಥಿತಿ ಪರಿಶೀಲನೆಯಲ್ಲಿದೆ.",
"EXPIRED": "ಸ್ಥಿತಿಯ ಪರಿಶೀಲನೆ ಯಶಸ್ವಿಯಾಗಿದೆ. ನಿಮ್ಮ {{vcType}} ಸ್ಥಿತಿ ಅವಧಿ ಮೀರಿದೆ."
},
"reverificationFailed": {
"PENDING": "ಸ್ಥಿತಿಯ ಪರಿಶೀಲನೆ ವಿಫಲವಾಗಿದೆ. ನಿಮ್ಮ {{vcType}} ಪರಿಶೀಲಿಸಲಾಗಲಿಲ್ಲ, ದಯವಿಟ್ಟು ನಂತರ ಪ್ರಯತ್ನಿಸಿ."
}
},
"AboutInji": {
"aboutInji": "ಇಂಜಿ ವಾಲೆಟ್ ಬಗ್ಗೆ",
@@ -503,7 +522,9 @@
"VcVerificationBanner": {
"inProgress": "ನಾವು ನಿಮ್ಮ ಕಾರ್ಡ್ ಅನ್ನು ಮೌಲ್ಯೀಕರಿಸುತ್ತಿದ್ದೇವೆ, ಇದಕ್ಕೆ ಸ್ವಲ್ಪ ಸಮಯ ತೆಗೆದುಕೊಳ್ಳಬಹುದು. ಒಮ್ಮೆ ಪರಿಶೀಲಿಸಿದ ನಂತರ, ನಿಮ್ಮ ಕಾರ್ಡ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ನಿಮಗೆ ಸಾಧ್ಯವಾಗುತ್ತದೆ.",
"success": "{{vcDetails}} ಅನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಪರಿಶೀಲಿಸಲಾಗಿದೆ ಮತ್ತು ಇದೀಗ ಸಕ್ರಿಯಗೊಳಿಸುವಿಕೆಗೆ ಲಭ್ಯವಿದೆ.",
"error": "ಕ್ಷಮಿಸಿ, ಇದೀಗ {{vcDetails}} ಅನ್ನು ಪರಿಶೀಲಿಸಲು ನಮಗೆ ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ದಯವಿಟ್ಟು ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. ಅಲ್ಲಿಯವರೆಗೆ, ನಿಮ್ಮ ಕಾರ್ಡ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ಅಥವಾ ಹಂಚಿಕೊಳ್ಳಲು ನಿಮಗೆ ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ."
"error": "ಕ್ಷಮಿಸಿ, ಇದೀಗ {{vcDetails}} ಅನ್ನು ಪರಿಶೀಲಿಸಲು ನಮಗೆ ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ದಯವಿಟ್ಟು ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. ಅಲ್ಲಿಯವರೆಗೆ, ನಿಮ್ಮ ಕಾರ್ಡ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ಅಥವಾ ಹಂಚಿಕೊಳ್ಳಲು ನಿಮಗೆ ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ.",
"revoked": "{{vcDetails}} ಅನ್ನು ಹೊರತಂದವರಿಂದ ಹಿಂಪಡೆಯಲಾಗಿದೆ ಮತ್ತು ಈಗ ಮಾನ್ಯವಿಲ್ಲ. ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗೆ ದಯವಿಟ್ಟು ಹೊರತಂದವರನ್ನು ಸಂಪರ್ಕಿಸಿ.",
"expired": "{{vcDetails}} ಅವಧಿ ಮೀರಿದೆ ಮತ್ತು ಈಗ ಮಾನ್ಯವಿಲ್ಲ. ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗೆ ದಯವಿಟ್ಟು ಹೊರತಂದವರನ್ನು ಸಂಪರ್ಕಿಸಿ."
},
"ViewVcModal": {
"title": "ಐಡಿ ವಿವರಗಳು",
@@ -520,6 +541,10 @@
"expired": {
"title": "ಅವಧಿ ಮುಗಿದ ಸ್ಥಿತಿ:",
"description": "ರುಜುವಾತು ಅವಧಿ ಮುಗಿದಿದೆ."
},
"revoked": {
"title": "ಹಿಂಪಡೆಯಲಾದ ಸ್ಥಿತಿ:",
"description": "ಪ್ರಮಾಣಪತ್ರವನ್ನು ಹಿಂಪಡೆಯಲಾಗಿದೆ."
}
}
},
@@ -918,8 +943,8 @@
"message": "ಪರಿಶೀಲಕರಿಂದ ಬಂದ ಪ್ರತಿಕ್ರಿಯೆ ಅಮಾನ್ಯವಾಗಿದೆ. ದಯವಿಟ್ಟು ಅವರನ್ನು ಪರಿಶೀಲಿಸಿ ಸರಿಯಾದ ವಿವರಗಳನ್ನು ನೀಡಲು ಕೇಳಿ."
},
"invalidTransactionData": {
"title": "ಪರಿಶೀಲನಾ ವಿನಂತಿಯನ್ನು ಬೆಂಬಲಿಸಲಾಗಿಲ್ಲ",
"message": "ಈ ಪರಿಶೀಲನಾ ವಿನಂತಿಯಲ್ಲಿ ನಿಮ್ಮ ವಾಲೆಟ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲವಾದ ಡೇಟಾ ಸೇರಿದೆ."
"title": "ಪರಿಶೀಲನಾ ವಿನಂತಿಯನ್ನು ಬೆಂಬಲಿಸಲಾಗಿಲ್ಲ",
"message": "ಈ ಪರಿಶೀಲನಾ ವಿನಂತಿಯಲ್ಲಿ ನಿಮ್ಮ ವಾಲೆಟ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲವಾದ ಡೇಟಾ ಸೇರಿದೆ."
},
"invalidQrCode": {
"title": "ನಿಮ್ಮ ವಿನಂತಿಯನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲಾಗಲಿಲ್ಲ",

View File

@@ -15,6 +15,12 @@
"WALLET_BINDING_FAILURE": "{{idType}} செயல்படுத்தல் தோல்வியடைந்தது.",
"VC_REMOVED": "{{idType}} பணப்பையிலிருந்து நீக்கப்பட்டது.",
"TAMPERED_VC_REMOVED": "{{idType}} த篡றுத்தல் காரணமாக பணப்பையிலிருந்து நீக்கப்பட்டது.",
"VC_STATUS_CHANGED": {
"VALID": "நிலைச் சரிபார்ப்பு முடிந்தது. {{idType}} நிலைச் சான்றிதழ் செல்லுபடியாகும்.",
"REVOKED": "நிலைச் சரிபார்ப்பு முடிந்தது. {{idType}} நிலைச் சான்றிதழ் இரத்து செய்யப்பட்டது.",
"EXPIRED": "நிலைச் சரிபார்ப்பு முடிந்தது. {{idType}} நிலைச் சான்றிதழ் காலாவதி ஆகிவிட்டது.",
"PENDING": "நிலைச் சரிபார்ப்பு முடிந்தது. {{idType}} நிலைச் சான்றிதழ் சரிபார்ப்புக்காக காத்திருக்கிறது."
},
"vpSharing": {
"SHARED_SUCCESSFULLY": "நற்சான்றிதழ் வழங்கல் வெற்றிகரமாகப் பகிரப்பட்டது.",
"SHARED_WITH_FACE_VERIFIACTION": "முக சரிபார்ப்பு வெற்றிகரமாக உள்ளது, மேலும் நற்சான்றிதழ் விளக்கக்காட்சி வெற்றிகரமாக பகிரப்பட்டது.",
@@ -73,6 +79,8 @@
"VcDetails": {
"generatedOn": "உருவாக்கப்பட்டது",
"status": "நிலை",
"lastChecked": "கடைசியாக சரிபார்க்கப்பட்டது",
"revoked": "இரத்து செய்யப்பட்டது",
"valid": "செல்லுபடியாகும்",
"expired": "காலாவதியானது",
"pending": "நிலுவையில் உள்ளது",
@@ -125,6 +133,8 @@
"unPinCard": "அன்பின்",
"pinCard": "பின்",
"share": "பகிர்",
"reverify": "அட்டையின் நிலையைச் சரிபார்க்கவும்",
"new": "புதியது",
"shareWithSelfie": "செல்ஃபியுடன் பகிரவும்",
"offlineAuthDisabledMessage": "இந்த நற்சான்றிதழ்களை ஆன்லைன் அங்கீகாரத்திற்காகப் பயன்படுத்த இங்கே கிளிக் செய்யவும்.",
"viewActivityLog": "செயல்பாட்டு பதிவைக் காண்க",
@@ -191,7 +201,16 @@
"alternateBiometricSuccess": "வெற்றி! இன்ஜி பயன்பாட்டைத் திறக்க, நீங்கள் இப்போது பயோமெட்ரிக்ஸைப் பயன்படுத்தலாம்.",
"activated": "ஆன்லைன் அங்கீகாரத்திற்காக நற்சான்றிதழ்கள் இயக்கப்பட்டுள்ளன.",
"keyPreferenceSuccess": "உங்கள் கீ முன்னுரிமைகள் வெற்றிகரமாக சேமிக்கப்பட்டது!",
"keyPreferenceError": "மன்னிக்கவும்! உங்கள் கீ முன்னுரிமைகளை அமைப்பதில் பிழை ஏற்பட்டது"
"keyPreferenceError": "மன்னிக்கவும்! உங்கள் கீ முன்னுரிமைகளை அமைப்பதில் பிழை ஏற்பட்டது",
"reverifiedSuccessfully": {
"VALID": "நிலைச் சரிபார்ப்பு வெற்றிகரமாக முடிந்தது. உங்கள் {{vcType}} நிலை செல்லுபடியாகும்.",
"REVOKED": "நிலைச் சரிபார்ப்பு வெற்றிகரமாக முடிந்தது. உங்கள் {{vcType}} நிலை இரத்து செய்யப்பட்டுள்ளது.",
"PENDING": "நிலைச் சரிபார்ப்பு வெற்றிகரமாக முடிந்தது. உங்கள் {{vcType}} நிலை சரிபார்ப்பிற்காக காத்திருக்கிறது.",
"EXPIRED": "நிலைச் சரிபார்ப்பு வெற்றிகரமாக முடிந்தது. உங்கள் {{vcType}} நிலை காலாவதியாகியுள்ளது."
},
"reverificationFailed": {
"PENDING": "நிலைச் சரிபார்ப்பு தோல்வியடைந்தது. உங்கள் {{vcType}} சான்றிதழைச் சரிபார்க்க முடியவில்லை, தயவுசெய்து பின்னர் முயற்சிக்கவும்."
}
},
"AboutInji": {
"aboutInji": "இன்ஜி வாலட் பற்றி",
@@ -503,7 +522,9 @@
"VcVerificationBanner": {
"inProgress": "உங்கள் கார்டை நாங்கள் சரிபார்க்கிறோம், இதற்கு சிறிது நேரம் ஆகலாம். சரிபார்க்கப்பட்டதும், உங்கள் கார்டைச் செயல்படுத்த முடியும்.",
"success": "{{vcDetails}} வெற்றிகரமாகச் சரிபார்க்கப்பட்டது, இப்போது செயல்படுத்துவதற்குக் கிடைக்கிறது.",
"error": "மன்னிக்கவும், இப்போது எங்களால் {{vcDetails}} ஐச் சரிபார்க்க முடியவில்லை. பிறகு முயற்சிக்கவும். அதுவரை உங்களால் கார்டை இயக்கவோ பகிரவோ முடியாது."
"error": "மன்னிக்கவும், இப்போது எங்களால் {{vcDetails}} ஐச் சரிபார்க்க முடியவில்லை. பிறகு முயற்சிக்கவும். அதுவரை உங்களால் கார்டை இயக்கவோ பகிரவோ முடியாது.",
"revoked": "{{vcDetails}} வெளியீட்டாளரால் இரத்து செய்யப்பட்டுள்ளது மற்றும் இனி செல்லுபடியாகாது. மேலும் தகவலுக்கு வெளியீட்டாளரை தொடர்பு கொள்ளவும்.",
"expired": "{{vcDetails}} காலாவதியானது மற்றும் இனி செல்லுபடியாகாது. மேலும் தகவலுக்கு வெளியீட்டாளரை தொடர்பு கொள்ளவும்."
},
"ViewVcModal": {
"title": "அடையாள விவரங்கள்",
@@ -520,6 +541,10 @@
"expired": {
"title": "காலாவதியான நிலை:",
"description": "நற்சான்றிதழ் காலாவதியாகிவிட்டது."
},
"revoked": {
"title": "இரத்து செய்யப்பட்ட நிலை:",
"description": "சான்றிதழ் இரத்து செய்யப்பட்டுள்ளது."
}
}
},

View File

@@ -39,6 +39,8 @@ export const IssuersActions = (model: any) => {
...context.vcMetadata,
isVerified: true,
isExpired: event.data.verificationErrorCode == EXPIRED_VC_ERROR_CODE,
isRevoked: event.data.isRevoked,
lastKnownStatusTimestamp: new Date().toISOString()
}),
}),
resetVerificationResult: assign({
@@ -47,6 +49,7 @@ export const IssuersActions = (model: any) => {
...context.vcMetadata,
isVerified: false,
isExpired: false,
isRevoked: false,
}),
}),
setIssuers: model.assign({

View File

@@ -1,8 +1,12 @@
import {assign, send} from 'xstate';
import {CommunicationDetails, UUID} from '../../../shared/Utils';
import {CommunicationDetails, UUID, VerificationStatus} from '../../../shared/Utils';
import {StoreEvents} from '../../store';
import {VCMetadata} from '../../../shared/VCMetadata';
import {MIMOTO_BASE_URL, MY_VCS_STORE_KEY} from '../../../shared/constants';
import {
EXPIRED_VC_ERROR_CODE,
MIMOTO_BASE_URL,
MY_VCS_STORE_KEY,
} from '../../../shared/constants';
import i18n from '../../../i18n';
import {getHomeMachineService} from '../../../screens/Home/HomeScreenController';
import {DownloadProps} from '../../../shared/api';
@@ -30,19 +34,42 @@ import {VCActivityLog} from '../../../components/ActivityLogEvent';
export const VCItemActions = model => {
return {
setIsVerified: assign({
vcMetadata: (context: any) =>
new VCMetadata({
...context.vcMetadata,
isVerified: true,
}),
setIsVerified: assign((context: any, event: any) => {
const previous = context.vcMetadata;
const next = new VCMetadata({
...previous,
isVerified: true,
isRevoked: event.data.isRevoked,
isExpired: event.data.verificationErrorCode == EXPIRED_VC_ERROR_CODE,
lastKnownStatusTimestamp: new Date().toISOString(),
});
const statusChanged =
previous.isVerified !== next.isVerified ||
previous.isRevoked !== next.isRevoked ||
previous.isExpired !== next.isExpired;
return {
...context,
vcMetadata: next,
statusChangedDuringVerification: statusChanged,
};
}),
resetIsVerified: assign({
vcMetadata: (context: any) =>
new VCMetadata({
...context.vcMetadata,
resetIsVerified: assign((context: any) => {
const previous = context.vcMetadata;
const statusChanged = previous.isVerified;
return {
...context,
vcMetadata: new VCMetadata({
...previous,
isVerified: false,
isRevoked: false,
isExpired: false,
lastKnownStatusTimestamp: new Date().toISOString(),
}),
statusChangedDuringVerification: statusChanged,
};
}),
setVerificationStatus: assign({
@@ -53,7 +80,6 @@ export const VCItemActions = model => {
: event.response.vcMetadata.isVerified
? BannerStatusType.SUCCESS
: BannerStatusType.ERROR;
return getVcVerificationDetails(
statusType,
context.vcMetadata,
@@ -63,6 +89,67 @@ export const VCItemActions = model => {
},
}),
sendReverificationSuccessToVcMeta: send(
(context: any) => ({
type: 'REVERIFY_VC_SUCCESS',
statusValue: context.vcMetadata.isRevoked
? VerificationStatus.REVOKED
: context.vcMetadata.isExpired
? VerificationStatus.EXPIRED
: context.vcMetadata.isVerified
? VerificationStatus.VALID
: VerificationStatus.PENDING,
vcKey: context.vcMetadata.getVcKey(),
vcType: context.vcMetadata.credentialType
}),
{
to: (context: any) => context.serviceRefs.vcMeta,
},
),
resetStatusChangedFlag: assign({
statusChangedDuringVerification: () => false,
}),
sendReverificationFailureToVcMeta: send(
(context:any) => ({
type: 'REVERIFY_VC_FAILED',
statusValue: VerificationStatus.PENDING,
vcKey: context.vcMetadata.getVcKey(),
vcType: context.vcMetadata.credentialType
}),
{
to: (context: any) => context.serviceRefs.vcMeta,
},
),
logStatusChangedOnReverification: send(
(context: any) => {
const status = context.vcMetadata.isRevoked
? VerificationStatus.REVOKED
: context.vcMetadata.isExpired
? VerificationStatus.EXPIRED
: context.vcMetadata.isVerified
? VerificationStatus.VALID
: VerificationStatus.PENDING;
return ActivityLogEvents.LOG_ACTIVITY(
VCActivityLog.getLogFromObject({
_vcKey: context.vcMetadata.getVcKey(),
type: 'VC_STATUS_CHANGED',
issuer: context.vcMetadata.issuerHost!!,
credentialConfigurationId:
context.verifiableCredential.credentialConfigurationId,
timestamp: Date.now(),
deviceName: '',
vcStatus: status,
}),
);
},
{
to: (context: any) => context.serviceRefs.activityLog,
},
),
resetVerificationStatus: assign({
verificationStatus: (context: any) => {
return context.verificationStatus?.statusType ===

View File

@@ -28,5 +28,9 @@ export const VCItemGaurds = () => {
isVerificationPendingBecauseOfNetworkIssue: (_context, event) =>
(event.data as Error).message == VerificationErrorType.NETWORK_ERROR,
hasVcStatusChangedAfterReverification: (context:any) => {
return context.statusChangedDuringVerification;
}
};
};

View File

@@ -394,6 +394,9 @@ export const VCItemMachine = model.createMachine(
target:
'#vc-item-machine.vcUtilitiesState.kebabPopUp.showActivities',
},
REVERIFY_VC: {
target: '#vc-item-machine.vcUtilitiesState.reverificationState',
},
REMOVE: {
actions: 'setVcKey',
target:
@@ -543,6 +546,51 @@ export const VCItemMachine = model.createMachine(
},
},
},
reverificationState: {
initial: 'verifying',
states: {
verifying: {
entry: () => console.log('🔄 reverification started'),
invoke: {
src: 'verifyCredential',
onDone: {
actions: [
'setIsVerified',
'storeContext',
'updateVcMetadata',
'sendReverificationSuccessToVcMeta',
],
target: 'checkingStatusChange',
},
onError: {
actions: [
'resetIsVerified',
'storeContext',
'updateVcMetadata',
'sendReverificationFailureToVcMeta',
],
target: 'checkingStatusChange',
},
},
},
checkingStatusChange: {
always: [
{
cond: 'hasVcStatusChangedAfterReverification',
actions: [
'logStatusChangedOnReverification',
'resetStatusChangedFlag',
],
target: '#vc-item-machine.vcUtilitiesState.idle',
},
{
target: '#vc-item-machine.vcUtilitiesState.idle',
},
],
},
},
},
},
},
verifyState: {
@@ -561,10 +609,10 @@ export const VCItemMachine = model.createMachine(
states: {
idle: {},
verifyingCredential: {
entry: send({
entry: [send({
type: 'SET_VERIFICATION_STATUS',
response: {statusType: BannerStatusType.IN_PROGRESS},
}),
}),()=>console.info("auto verification started 🔄")],
invoke: {
src: 'verifyCredential',
@@ -592,7 +640,7 @@ export const VCItemMachine = model.createMachine(
'sendVerificationStatusToVcMeta',
'updateVcMetadata',
],
target: 'verificationCompleted',
target: 'checkingStatusChange',
},
SET_VERIFICATION_STATUS: {
actions: 'setVerificationStatus',
@@ -607,6 +655,21 @@ export const VCItemMachine = model.createMachine(
STORE_ERROR: {},
},
},
checkingStatusChange: {
always: [
{
cond: 'hasVcStatusChangedAfterReverification',
actions: [
'logStatusChangedOnReverification',
'resetStatusChangedFlag',
],
target: 'verificationCompleted',
},
{
target: 'verificationCompleted',
},
],
},
verificationCompleted: {
entry: 'showVerificationBannerStatus',
on: {

View File

@@ -11,6 +11,7 @@
"done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromContext.fetchWellknown:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromContext.fetchWellknown:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
@@ -22,6 +23,7 @@
"error.platform.checkStatus": { type: "error.platform.checkStatus"; data: unknown };
"error.platform.downloadCredential": { type: "error.platform.downloadCredential"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]"; data: unknown };
@@ -44,12 +46,12 @@
"isUserSignedAlready": "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload:invocation[0]";
"loadDownloadLimitConfig": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]";
"requestBindingOTP": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]";
"verifyCredential": "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
"verifyCredential": "done.invoke.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
};
missingImplementations: {
actions: "addVcToInProgressDownloads" | "closeViewVcModal" | "incrementDownloadCounter" | "logDownloaded" | "logRemovedVc" | "logWalletBindingFailure" | "logWalletBindingSuccess" | "refreshAllVcs" | "removeVcFromInProgressDownloads" | "removeVcItem" | "removeVcMetaDataFromStorage" | "removeVcMetaDataFromVcMachineContext" | "removeVerificationStatusFromVcMeta" | "requestVcContext" | "resetIsMachineInKebabPopupState" | "resetIsVerified" | "resetPrivateKey" | "resetVerificationStatus" | "sendActivationStartEvent" | "sendActivationSuccessEvent" | "sendBackupEvent" | "sendDownloadLimitExpire" | "sendDownloadingFailedToVcMeta" | "sendTelemetryEvents" | "sendUserCancelledActivationFailedEndEvent" | "sendVerificationError" | "sendVerificationStatusToVcMeta" | "sendWalletBindingErrorEvent" | "sendWalletBindingSuccess" | "setCommunicationDetails" | "setContext" | "setDownloadInterval" | "setErrorAsVerificationError" | "setErrorAsWalletBindingError" | "setIsVerified" | "setMaxDownloadCount" | "setOTP" | "setPinCard" | "setPrivateKey" | "setPublicKey" | "setThumbprintForWalletBindingId" | "setVcKey" | "setVcMetadata" | "setVerificationStatus" | "setWalletBindingResponse" | "showVerificationBannerStatus" | "storeContext" | "storeVcInContext" | "unSetBindingTransactionId" | "unSetError" | "unSetOTP" | "updateVcMetadata" | "updateWellknownResponse";
actions: "addVcToInProgressDownloads" | "closeViewVcModal" | "incrementDownloadCounter" | "logDownloaded" | "logRemovedVc" | "logStatusChangedOnReverification" | "logWalletBindingFailure" | "logWalletBindingSuccess" | "refreshAllVcs" | "removeVcFromInProgressDownloads" | "removeVcItem" | "removeVcMetaDataFromStorage" | "removeVcMetaDataFromVcMachineContext" | "removeVerificationStatusFromVcMeta" | "requestVcContext" | "resetIsMachineInKebabPopupState" | "resetIsVerified" | "resetPrivateKey" | "resetStatusChangedFlag" | "resetVerificationStatus" | "sendActivationStartEvent" | "sendActivationSuccessEvent" | "sendBackupEvent" | "sendDownloadLimitExpire" | "sendDownloadingFailedToVcMeta" | "sendReverificationFailureToVcMeta" | "sendReverificationSuccessToVcMeta" | "sendTelemetryEvents" | "sendUserCancelledActivationFailedEndEvent" | "sendVerificationError" | "sendVerificationStatusToVcMeta" | "sendWalletBindingErrorEvent" | "sendWalletBindingSuccess" | "setCommunicationDetails" | "setContext" | "setDownloadInterval" | "setErrorAsVerificationError" | "setErrorAsWalletBindingError" | "setIsVerified" | "setMaxDownloadCount" | "setOTP" | "setPinCard" | "setPrivateKey" | "setPublicKey" | "setThumbprintForWalletBindingId" | "setVcKey" | "setVcMetadata" | "setVerificationStatus" | "setWalletBindingResponse" | "showVerificationBannerStatus" | "storeContext" | "storeVcInContext" | "unSetBindingTransactionId" | "unSetError" | "unSetOTP" | "updateVcMetadata" | "updateWellknownResponse";
delays: never;
guards: "hasCredential" | "hasCredentialAndWellknown" | "hasKeyPair" | "isCustomSecureKeystore" | "isDownloadAllowed" | "isSignedIn" | "isVerificationPendingBecauseOfNetworkIssue";
guards: "hasCredential" | "hasCredentialAndWellknown" | "hasKeyPair" | "hasVcStatusChangedAfterReverification" | "isCustomSecureKeystore" | "isDownloadAllowed" | "isSignedIn" | "isVerificationPendingBecauseOfNetworkIssue";
services: "addWalletBindingId" | "checkDownloadExpiryLimit" | "checkStatus" | "downloadCredential" | "fetchIssuerWellknown" | "fetchKeyPair" | "generateKeypairAndStore" | "isUserSignedAlready" | "loadDownloadLimitConfig" | "requestBindingOTP" | "verifyCredential";
};
eventsCausingActions: {
@@ -58,6 +60,7 @@
"incrementDownloadCounter": "POLL" | "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]";
"logDownloaded": "STORE_RESPONSE";
"logRemovedVc": "STORE_RESPONSE";
"logStatusChangedOnReverification": "";
"logWalletBindingFailure": "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.generateKeyPair:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]";
"logWalletBindingSuccess": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]";
"refreshAllVcs": "STORE_RESPONSE" | "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]";
@@ -67,15 +70,18 @@
"removeVcMetaDataFromVcMachineContext": "DISMISS";
"removeVerificationStatusFromVcMeta": "RESET_VERIFICATION_STATUS";
"requestVcContext": "DISMISS" | "REFRESH" | "STORE_ERROR" | "xstate.init";
"resetIsMachineInKebabPopupState": "" | "ADD_WALLET_BINDING_ID" | "CANCEL" | "CLOSE_VC_MODAL" | "DISMISS" | "REFRESH" | "REMOVE" | "SHOW_ACTIVITY" | "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]" | "xstate.stop";
"resetIsVerified": "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "error.platform.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
"resetIsMachineInKebabPopupState": "" | "ADD_WALLET_BINDING_ID" | "CANCEL" | "CLOSE_VC_MODAL" | "DISMISS" | "REFRESH" | "REMOVE" | "REVERIFY_VC" | "SHOW_ACTIVITY" | "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]" | "xstate.stop";
"resetIsVerified": "error.platform.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "error.platform.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
"resetPrivateKey": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]";
"resetStatusChangedFlag": "";
"resetVerificationStatus": "REMOVE_VERIFICATION_STATUS_BANNER" | "RESET_VERIFICATION_STATUS";
"sendActivationStartEvent": "CONFIRM";
"sendActivationSuccessEvent": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]";
"sendBackupEvent": "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload:invocation[0]";
"sendDownloadLimitExpire": "FAILED" | "error.platform.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]";
"sendDownloadingFailedToVcMeta": "error.platform.downloadCredential";
"sendReverificationFailureToVcMeta": "error.platform.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]";
"sendReverificationSuccessToVcMeta": "done.invoke.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]";
"sendTelemetryEvents": "STORE_RESPONSE";
"sendUserCancelledActivationFailedEndEvent": "DISMISS";
"sendVerificationError": "STORE_RESPONSE";
@@ -87,7 +93,7 @@
"setDownloadInterval": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]";
"setErrorAsVerificationError": "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]";
"setErrorAsWalletBindingError": "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.generateKeyPair:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]";
"setIsVerified": "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
"setIsVerified": "done.invoke.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
"setMaxDownloadCount": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]";
"setOTP": "INPUT_OTP";
"setPinCard": "PIN_CARD";
@@ -98,13 +104,13 @@
"setVcMetadata": "UPDATE_VC_METADATA";
"setVerificationStatus": "SET_VERIFICATION_STATUS" | "SHOW_VERIFICATION_STATUS_BANNER" | "STORE_RESPONSE";
"setWalletBindingResponse": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]";
"showVerificationBannerStatus": "SHOW_VERIFICATION_STATUS_BANNER" | "STORE_RESPONSE" | "xstate.after(500)#vc-item-machine.verifyState.verifyingCredential";
"storeContext": "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "error.platform.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
"showVerificationBannerStatus": "" | "SHOW_VERIFICATION_STATUS_BANNER" | "xstate.after(500)#vc-item-machine.verifyState.verifyingCredential";
"storeContext": "done.invoke.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "error.platform.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
"storeVcInContext": "STORE_RESPONSE";
"unSetBindingTransactionId": "DISMISS";
"unSetError": "CANCEL" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]";
"unSetOTP": "DISMISS" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]";
"updateVcMetadata": "PIN_CARD" | "STORE_RESPONSE";
"updateVcMetadata": "PIN_CARD" | "STORE_RESPONSE" | "done.invoke.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.reverificationState.verifying:invocation[0]";
"updateWellknownResponse": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromContext.fetchWellknown:invocation[0]";
};
eventsCausingDelays: {
@@ -114,6 +120,7 @@
"hasCredential": "GET_VC_RESPONSE";
"hasCredentialAndWellknown": "GET_VC_RESPONSE";
"hasKeyPair": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]";
"hasVcStatusChangedAfterReverification": "";
"isCustomSecureKeystore": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]";
"isDownloadAllowed": "POLL";
"isSignedIn": "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload:invocation[0]";
@@ -130,14 +137,15 @@
"isUserSignedAlready": "STORE_RESPONSE";
"loadDownloadLimitConfig": "GET_VC_RESPONSE" | "STORE_ERROR";
"requestBindingOTP": "CONFIRM" | "RESEND_OTP";
"verifyCredential": "CREDENTIAL_DOWNLOADED" | "VERIFY";
"verifyCredential": "CREDENTIAL_DOWNLOADED" | "REVERIFY_VC" | "VERIFY";
};
matchesStates: "vcUtilitiesState" | "vcUtilitiesState.idle" | "vcUtilitiesState.kebabPopUp" | "vcUtilitiesState.kebabPopUp.idle" | "vcUtilitiesState.kebabPopUp.pinCard" | "vcUtilitiesState.kebabPopUp.removeWallet" | "vcUtilitiesState.kebabPopUp.removingVc" | "vcUtilitiesState.kebabPopUp.showActivities" | "vcUtilitiesState.kebabPopUp.triggerAutoBackup" | "vcUtilitiesState.loadVc" | "vcUtilitiesState.loadVc.loadVcFromContext" | "vcUtilitiesState.loadVc.loadVcFromContext.fetchWellknown" | "vcUtilitiesState.loadVc.loadVcFromContext.idle" | "vcUtilitiesState.loadVc.loadVcFromServer" | "vcUtilitiesState.loadVc.loadVcFromServer.checkingStatus" | "vcUtilitiesState.loadVc.loadVcFromServer.downloadingCredential" | "vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig" | "vcUtilitiesState.loadVc.loadVcFromServer.savingFailed" | "vcUtilitiesState.loadVc.loadVcFromServer.savingFailed.idle" | "vcUtilitiesState.loadVc.loadVcFromServer.savingFailed.viewingVc" | "vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry" | "vcUtilitiesState.verifyingCredential" | "vcUtilitiesState.verifyingCredential.handleVCVerificationFailure" | "vcUtilitiesState.verifyingCredential.idle" | "vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload" | "vcUtilitiesState.walletBinding" | "vcUtilitiesState.walletBinding.acceptingBindingOTP" | "vcUtilitiesState.walletBinding.acceptingBindingOTP.idle" | "vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP" | "vcUtilitiesState.walletBinding.addKeyPair" | "vcUtilitiesState.walletBinding.addingWalletBindingId" | "vcUtilitiesState.walletBinding.generateKeyPair" | "vcUtilitiesState.walletBinding.requestingBindingOTP" | "vcUtilitiesState.walletBinding.showBindingWarning" | "vcUtilitiesState.walletBinding.showingWalletBindingError" | "vcUtilitiesState.walletBinding.updatingContextVariables" | "verifyState" | "verifyState.idle" | "verifyState.verificationCompleted" | "verifyState.verifyingCredential" | { "vcUtilitiesState"?: "idle" | "kebabPopUp" | "loadVc" | "verifyingCredential" | "walletBinding" | { "kebabPopUp"?: "idle" | "pinCard" | "removeWallet" | "removingVc" | "showActivities" | "triggerAutoBackup";
matchesStates: "vcUtilitiesState" | "vcUtilitiesState.idle" | "vcUtilitiesState.kebabPopUp" | "vcUtilitiesState.kebabPopUp.idle" | "vcUtilitiesState.kebabPopUp.pinCard" | "vcUtilitiesState.kebabPopUp.removeWallet" | "vcUtilitiesState.kebabPopUp.removingVc" | "vcUtilitiesState.kebabPopUp.showActivities" | "vcUtilitiesState.kebabPopUp.triggerAutoBackup" | "vcUtilitiesState.loadVc" | "vcUtilitiesState.loadVc.loadVcFromContext" | "vcUtilitiesState.loadVc.loadVcFromContext.fetchWellknown" | "vcUtilitiesState.loadVc.loadVcFromContext.idle" | "vcUtilitiesState.loadVc.loadVcFromServer" | "vcUtilitiesState.loadVc.loadVcFromServer.checkingStatus" | "vcUtilitiesState.loadVc.loadVcFromServer.downloadingCredential" | "vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig" | "vcUtilitiesState.loadVc.loadVcFromServer.savingFailed" | "vcUtilitiesState.loadVc.loadVcFromServer.savingFailed.idle" | "vcUtilitiesState.loadVc.loadVcFromServer.savingFailed.viewingVc" | "vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry" | "vcUtilitiesState.reverificationState" | "vcUtilitiesState.reverificationState.checkingStatusChange" | "vcUtilitiesState.reverificationState.verifying" | "vcUtilitiesState.verifyingCredential" | "vcUtilitiesState.verifyingCredential.handleVCVerificationFailure" | "vcUtilitiesState.verifyingCredential.idle" | "vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload" | "vcUtilitiesState.walletBinding" | "vcUtilitiesState.walletBinding.acceptingBindingOTP" | "vcUtilitiesState.walletBinding.acceptingBindingOTP.idle" | "vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP" | "vcUtilitiesState.walletBinding.addKeyPair" | "vcUtilitiesState.walletBinding.addingWalletBindingId" | "vcUtilitiesState.walletBinding.generateKeyPair" | "vcUtilitiesState.walletBinding.requestingBindingOTP" | "vcUtilitiesState.walletBinding.showBindingWarning" | "vcUtilitiesState.walletBinding.showingWalletBindingError" | "vcUtilitiesState.walletBinding.updatingContextVariables" | "verifyState" | "verifyState.checkingStatusChange" | "verifyState.idle" | "verifyState.verificationCompleted" | "verifyState.verifyingCredential" | { "vcUtilitiesState"?: "idle" | "kebabPopUp" | "loadVc" | "reverificationState" | "verifyingCredential" | "walletBinding" | { "kebabPopUp"?: "idle" | "pinCard" | "removeWallet" | "removingVc" | "showActivities" | "triggerAutoBackup";
"loadVc"?: "loadVcFromContext" | "loadVcFromServer" | { "loadVcFromContext"?: "fetchWellknown" | "idle";
"loadVcFromServer"?: "checkingStatus" | "downloadingCredential" | "loadDownloadLimitConfig" | "savingFailed" | "verifyingDownloadLimitExpiry" | { "savingFailed"?: "idle" | "viewingVc"; }; };
"reverificationState"?: "checkingStatusChange" | "verifying";
"verifyingCredential"?: "handleVCVerificationFailure" | "idle" | "triggerAutoBackupForVcDownload";
"walletBinding"?: "acceptingBindingOTP" | "addKeyPair" | "addingWalletBindingId" | "generateKeyPair" | "requestingBindingOTP" | "showBindingWarning" | "showingWalletBindingError" | "updatingContextVariables" | { "acceptingBindingOTP"?: "idle" | "resendOTP"; }; };
"verifyState"?: "idle" | "verificationCompleted" | "verifyingCredential"; };
"verifyState"?: "checkingStatusChange" | "idle" | "verificationCompleted" | "verifyingCredential"; };
tags: never;
}

View File

@@ -28,6 +28,7 @@ const VCItemEvents = {
PIN_CARD: () => ({}),
KEBAB_POPUP: () => ({}),
SHOW_ACTIVITY: () => ({}),
REVERIFY_VC: () => ({}),
CLOSE_VC_MODAL: () => ({}),
REMOVE: (vcMetadata: VCMetadata) => ({vcMetadata}),
UPDATE_VC_METADATA: (vcMetadata: VCMetadata) => ({vcMetadata}),
@@ -38,6 +39,7 @@ const VCItemEvents = {
RESET_VERIFICATION_STATUS: () => ({}),
REMOVE_VERIFICATION_STATUS_BANNER: () => ({}),
SHOW_VERIFICATION_STATUS_BANNER: (response: unknown) => ({response}),
CLOSE_BANNER: () => ({}),
};
export const VCItemModel = createModel(
@@ -63,6 +65,9 @@ export const VCItemModel = createModel(
verificationStatus: null as vcVerificationBannerDetails | null,
showVerificationStatusBanner: false as boolean,
wellknownResponse: {} as Object,
showReverificationSuccessBanner: false as boolean,
showVerificationFailureBanner: false as boolean,
statusChangedDuringVerification: false,
},
{
events: VCItemEvents,

View File

@@ -136,6 +136,18 @@ export function selectShowWalletBindingError(state: State) {
);
}
export function isReverifyingVc(state: State) {
return state.matches('vcUtilitiesState.reverificationState');
}
export function showReverificationSuccessBanner(state: State) {
return state.context.showReverificationSuccessBanner;
}
export function showVerificationFailureBanner(state: State) {
return state.context.showVerificationFailureBanner;
}
export function selectVc(state: State) {
const {serviceRefs, ...data} = state.context;
return data;

View File

@@ -1,19 +1,23 @@
import { NativeModules } from 'react-native';
import {NativeModules} from 'react-native';
import getAllConfigurations, {
API_URLS,
CACHED_API,
DownloadProps,
} from '../../../shared/api';
import Cloud from '../../../shared/CloudBackupAndRestoreUtils';
import { isIOS } from '../../../shared/constants';
import {isIOS} from '../../../shared/constants';
import {
fetchKeyPair,
generateKeyPair,
} from '../../../shared/cryptoutil/cryptoUtil';
import { getMatchingCredentialIssuerMetadata, verifyCredentialData } from '../../../shared/openId4VCI/Utils';
import { CredentialDownloadResponse, request } from '../../../shared/request';
import { WalletBindingResponse } from '../VCMetaMachine/vc';
import { getVerifiableCredential } from './VCItemSelectors';
import {
getMatchingCredentialIssuerMetadata,
verifyCredentialData,
} from '../../../shared/openId4VCI/Utils';
import {CredentialDownloadResponse, request} from '../../../shared/request';
import {WalletBindingResponse} from '../VCMetaMachine/vc';
import {getVerifiableCredential} from './VCItemSelectors';
import { VERIFICATION_TIMEOUT_IN_MS } from '../../../shared/vcjs/verifyCredential';
const {RNSecureKeystoreModule} = NativeModules;
export const VCItemServices = model => {
@@ -107,7 +111,7 @@ export const VCItemServices = model => {
},
fetchIssuerWellknown: async context => {
const wellknownResponse = await CACHED_API.fetchIssuerWellknownConfig(
context.vcMetadata.issuer,
context.vcMetadata.issuerHost,
context.vcMetadata.issuerHost,
true,
);
@@ -192,16 +196,43 @@ export const VCItemServices = model => {
},
verifyCredential: async (context: any) => {
if(context.verifiableCredential){
const verificationResult = await verifyCredentialData(
getVerifiableCredential(context.verifiableCredential),
context.selectedCredentialType.format
try {
if (!context.verifiableCredential) {
throw new Error('Missing verifiable credential in context');
}
const credential = getVerifiableCredential(context.verifiableCredential);
const format = context.selectedCredentialType?.format ?? context.format;
const verificationResult = await withTimeout(
verifyCredentialData(credential, format),
VERIFICATION_TIMEOUT_IN_MS
);
if(!verificationResult.isVerified) {
throw new Error(verificationResult.verificationErrorCode);
}
return verificationResult;
if (!verificationResult.isVerified) {
throw new Error(verificationResult.verificationErrorCode);
}
return verificationResult;
} catch (error) {
console.error('Credential verification failed:', error);
throw error;
}
},
}
};
};
function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
return Promise.race([
promise,
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error('VERIFICATION_TIMEOUT')), ms),
),
]);
}

View File

@@ -23,8 +23,52 @@ export const VCMetaActions = (model: any) => {
}),
setVerificationStatus: model.assign({
verificationStatus: (_, event) =>
event.verificationStatus as vcVerificationBannerDetails,
verificationStatus: (_, event) =>{
return event.verificationStatus as vcVerificationBannerDetails}
}),
setReverificationSuccess: model.assign({
reverificationSuccess: (context,event) => ({
status: true,
statusValue: event.statusValue,
vcKey: event.vcKey,
vcType: event.vcType
})
}),
resetReverificationSuccess: model.assign({
reverificationSuccess: () => ({
status: false,
statusValue: '',
vcKey:'',
vcType:''
})
}),
resetHighlightVcKey: model.assign({
reverificationSuccess: (context:any) => ({
...context.reverificationSuccess,
vcKey:''
}),
reverificationFailed: (context:any) => ({
...context.reverificationFailed,
vcKey:''
}),
}),
setReverificationFailed: model.assign({
reverificationFailed: (context,event) => ({
status: true,
statusValue: event.statusValue,
vcKey: event.vcKey,
vcType: event.vcType
})
}),
resetReverificationFailed: model.assign({
reverificationFailed: (context,event) => ({
status: false,
statusValue: event.statusValue,
vcKey: '',
vcType:''
})
}),
sendBackupEvent: send(BackupEvents.DATA_BACKUP(true), {

View File

@@ -18,6 +18,15 @@ export const VcMetaEvents = {
REFRESH_MY_VCS_TWO: (vc: VC) => ({vc}),
REFRESH_RECEIVED_VCS: () => ({}),
WALLET_BINDING_SUCCESS: (vcKey: string, vc: VC) => ({vcKey, vc}),
REVERIFY_VC_SUCCESS: (vcKey: string, statusValue: string, vcType: string) => ({
vcKey,
statusValue,
vcType,
}),
RESET_REVERIFY_VC_SUCCESS: () => ({}),
RESET_HIGHLIGHT: () => ({}),
REVERIFY_VC_FAILED: (vcKey: string, statusValue: string, vcType: string) => ({vcKey,statusValue,vcType}),
RESET_REVERIFY_VC_FAILED: () => ({}),
RESET_WALLET_BINDING_SUCCESS: () => ({}),
ADD_VC_TO_IN_PROGRESS_DOWNLOADS: (requestId: string) => ({requestId}),
REMOVE_VC_FROM_IN_PROGRESS_DOWNLOADS: (vcMetadata: VCMetadata) => ({

View File

@@ -1,4 +1,4 @@
import {EventFrom, send, sendParent} from 'xstate';
import {actions, EventFrom, send, sendParent} from 'xstate';
import {AppServices} from '../../../shared/GlobalContext';
import {VCMetamodel} from './VCMetaModel';
import {VCMetaActions} from './VCMetaActions';
@@ -25,8 +25,8 @@ export const vcMetaMachine =
VC_DOWNLOADING_FAILED: {
actions: 'setDownloadCreadentialsFailed',
},
RESET_DOWNLOADING_SUCCESS:{
actions: 'resetDownloadCredentialsSuccess'
RESET_DOWNLOADING_SUCCESS: {
actions: 'resetDownloadCredentialsSuccess',
},
RESET_DOWNLOADING_FAILED: {
actions: 'resetDownloadCreadentialsFailed',
@@ -105,6 +105,21 @@ export const vcMetaMachine =
WALLET_BINDING_SUCCESS: {
actions: 'setWalletBindingSuccess',
},
REVERIFY_VC_SUCCESS: {
actions: 'setReverificationSuccess',
},
RESET_REVERIFY_VC_SUCCESS: {
actions: 'resetReverificationSuccess',
},
REVERIFY_VC_FAILED: {
actions: 'setReverificationFailed',
},
RESET_REVERIFY_VC_FAILED: {
actions: 'resetReverificationFailed',
},
RESET_HIGHLIGHT: {
actions: 'resetHighlightVcKey',
},
GET_VC_ITEM: {
actions: 'getVcItemResponse',
},
@@ -118,7 +133,7 @@ export const vcMetaMachine =
actions: ['updateMyVcsMetadata', 'setUpdatedVcMetadatas'],
},
VC_DOWNLOADED: {
actions: ['setDownloadCredentialsSuccess','setDownloadedVc',]
actions: ['setDownloadCredentialsSuccess', 'setDownloadedVc'],
},
ADD_VC_TO_IN_PROGRESS_DOWNLOADS: {
actions: 'addVcToInProgressDownloads',

View File

@@ -11,7 +11,7 @@
"isUserSignedAlready": "done.invoke.vcMeta.ready.tamperedVCs.triggerAutoBackupForTamperedVcDeletion:invocation[0]";
};
missingImplementations: {
actions: "addVcToInProgressDownloads" | "getVcItemResponse" | "loadMyVcs" | "loadReceivedVcs" | "logTamperedVCsremoved" | "prependToMyVcsMetadata" | "removeDownloadFailedVcsFromStorage" | "removeDownloadingFailedVcsFromMyVcs" | "removeVcFromInProgressDownlods" | "removeVcFromMyVcsMetadata" | "resetDownloadCreadentialsFailed" | "resetDownloadCredentialsSuccess" | "resetDownloadFailedVcs" | "resetInProgressVcsDownloaded" | "resetTamperedVcs" | "resetVerificationErrorMessage" | "resetVerificationStatus" | "resetWalletBindingSuccess" | "sendBackupEvent" | "setDownloadCreadentialsFailed" | "setDownloadCredentialsSuccess" | "setDownloadedVc" | "setDownloadingFailedVcs" | "setMyVcs" | "setReceivedVcs" | "setUpdatedVcMetadatas" | "setVerificationErrorMessage" | "setVerificationStatus" | "setWalletBindingSuccess" | "updateMyVcsMetadata";
actions: "addVcToInProgressDownloads" | "getVcItemResponse" | "loadMyVcs" | "loadReceivedVcs" | "logTamperedVCsremoved" | "prependToMyVcsMetadata" | "removeDownloadFailedVcsFromStorage" | "removeDownloadingFailedVcsFromMyVcs" | "removeVcFromInProgressDownlods" | "removeVcFromMyVcsMetadata" | "resetDownloadCreadentialsFailed" | "resetDownloadCredentialsSuccess" | "resetDownloadFailedVcs" | "resetHighlightVcKey" | "resetInProgressVcsDownloaded" | "resetReverificationFailed" | "resetReverificationSuccess" | "resetTamperedVcs" | "resetVerificationErrorMessage" | "resetVerificationStatus" | "resetWalletBindingSuccess" | "sendBackupEvent" | "setDownloadCreadentialsFailed" | "setDownloadCredentialsSuccess" | "setDownloadedVc" | "setDownloadingFailedVcs" | "setMyVcs" | "setReceivedVcs" | "setReverificationFailed" | "setReverificationSuccess" | "setUpdatedVcMetadatas" | "setVerificationErrorMessage" | "setVerificationStatus" | "setWalletBindingSuccess" | "updateMyVcsMetadata";
delays: never;
guards: "isAnyVcTampered" | "isSignedIn";
services: "isUserSignedAlready";
@@ -30,7 +30,10 @@
"resetDownloadCreadentialsFailed": "RESET_DOWNLOADING_FAILED";
"resetDownloadCredentialsSuccess": "RESET_DOWNLOADING_SUCCESS";
"resetDownloadFailedVcs": "STORE_RESPONSE";
"resetHighlightVcKey": "RESET_HIGHLIGHT";
"resetInProgressVcsDownloaded": "RESET_IN_PROGRESS_VCS_DOWNLOADED";
"resetReverificationFailed": "RESET_REVERIFY_VC_FAILED";
"resetReverificationSuccess": "RESET_REVERIFY_VC_SUCCESS";
"resetTamperedVcs": "REMOVE_TAMPERED_VCS";
"resetVerificationErrorMessage": "RESET_VERIFY_ERROR";
"resetVerificationStatus": "RESET_VERIFICATION_STATUS";
@@ -42,6 +45,8 @@
"setDownloadingFailedVcs": "DOWNLOAD_LIMIT_EXPIRED";
"setMyVcs": "STORE_RESPONSE";
"setReceivedVcs": "STORE_RESPONSE";
"setReverificationFailed": "REVERIFY_VC_FAILED";
"setReverificationSuccess": "REVERIFY_VC_SUCCESS";
"setUpdatedVcMetadatas": "VC_METADATA_UPDATED";
"setVerificationErrorMessage": "VERIFY_VC_FAILED";
"setVerificationStatus": "SET_VERIFICATION_STATUS";

View File

@@ -20,7 +20,9 @@ export const VCMetamodel = createModel(
verificationErrorMessage: '' as string,
verificationStatus: null as vcVerificationBannerDetails | null,
DownloadingCredentialsFailed: false,
DownloadingCredentialsSuccess: false
DownloadingCredentialsSuccess: false,
reverificationSuccess: {status: false, statusValue: '', vcKey: '', vcType: ''},
reverificationFailed: {status: false, statusValue: '', vcKey: '', vcType: ''},
},
{
events: VcMetaEvents,

View File

@@ -91,3 +91,11 @@ export function selectIsDownloadingFailed(state: State) {
export function selectIsDownloadingSuccess(state: State) {
return state.context.DownloadingCredentialsSuccess;
}
export function selectIsReverificationSuccess(state: State) {
return state.context.reverificationSuccess;
}
export function selectIsReverificationFailure(state: State) {
return state.context.reverificationFailed;
}

View File

@@ -37,4 +37,43 @@
matchesStates: "init" | "loadWellknownConfig" | "ready" | "ready.idle" | "ready.logging" | "ready.refreshing" | { "ready"?: "idle" | "logging" | "refreshing"; };
tags: never;
}
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
"fetchAllWellKnownConfigResponse": "STORE_RESPONSE";
"loadActivities": "REFRESH" | "xstate.init";
"loadWellknownIntoContext": "LOG_ACTIVITY";
"prependActivity": "STORE_RESPONSE";
"setActivities": "STORE_RESPONSE";
"setAllWellknownConfigResponse": "STORE_RESPONSE";
"storeActivity": "LOG_ACTIVITY";
"storeWellknownConfig": "STORE_INCOMING_VC_WELLKNOWN_CONFIG";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
};
eventsCausingServices: {
};
matchesStates: "init" | "loadWellknownConfig" | "ready" | "ready.idle" | "ready.logging" | "ready.refreshing" | { "ready"?: "idle" | "logging" | "refreshing"; };
tags: never;
}

View File

@@ -46,59 +46,6 @@
"generateEncryptionKey": "ERROR" | "IGNORE" | "READY";
"getEncryptionKey": "TRY_AGAIN";
"hasEncryptionKey": never;
"store": "KEY_RECEIVED" | "READY" | "done.invoke.store.resettingStorage:invocation[0]";
};
matchesStates: "checkEncryptionKey" | "checkFreshInstall" | "checkStorageInitialisation" | "clearIosKeys" | "failedReadingKey" | "generatingEncryptionKey" | "gettingEncryptionKey" | "ready" | "resettingStorage";
tags: never;
}
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"done.invoke._store": { type: "done.invoke._store"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.store.checkFreshInstall:invocation[0]": { type: "done.invoke.store.checkFreshInstall:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.store.resettingStorage:invocation[0]": { type: "done.invoke.store.resettingStorage:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"error.platform._store": { type: "error.platform._store"; data: unknown };
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
"checkFreshInstall": "done.invoke.store.checkFreshInstall:invocation[0]";
"checkStorageInitialisedOrNot": "done.invoke.store.checkStorageInitialisation:invocation[0]";
"clear": "done.invoke.store.resettingStorage:invocation[0]";
"clearKeys": "done.invoke.store.clearIosKeys:invocation[0]";
"generateEncryptionKey": "done.invoke.store.generatingEncryptionKey:invocation[0]";
"getEncryptionKey": "done.invoke.store.gettingEncryptionKey:invocation[0]";
"hasEncryptionKey": "done.invoke.store.checkEncryptionKey:invocation[0]";
"store": "done.invoke._store";
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
"forwardStoreRequest": "APPEND" | "CLEAR" | "EXPORT" | "FETCH_ALL_WELLKNOWN_CONFIG" | "GET" | "GET_VCS_DATA" | "PREPEND" | "REMOVE" | "REMOVE_ITEMS" | "REMOVE_VC_METADATA" | "RESTORE_BACKUP" | "SET" | "UPDATE";
"notifyParent": "KEY_RECEIVED" | "READY" | "done.invoke.store.resettingStorage:invocation[0]";
"setEncryptionKey": "KEY_RECEIVED";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
"hasData": "done.invoke.store.checkFreshInstall:invocation[0]";
"isCustomSecureKeystore": "KEY_RECEIVED";
};
eventsCausingServices: {
"checkFreshInstall": "BIOMETRIC_CANCELLED" | "xstate.init";
"checkStorageInitialisedOrNot": "ERROR";
"clear": "KEY_RECEIVED";
"clearKeys": "done.invoke.store.checkFreshInstall:invocation[0]";
"generateEncryptionKey": "ERROR" | "IGNORE" | "READY";
"getEncryptionKey": "TRY_AGAIN";
"hasEncryptionKey": never;
"store": "KEY_RECEIVED" | "READY" | "done.invoke.store.resettingStorage:invocation[0]";
};
matchesStates: "checkEncryptionKey" | "checkFreshInstall" | "checkStorageInitialisation" | "clearIosKeys" | "failedReadingKey" | "generatingEncryptionKey" | "gettingEncryptionKey" | "ready" | "resettingStorage";

View File

@@ -79,6 +79,8 @@ export const HomeScreen: React.FC<HomeRouteProps> = props => {
isVisible={controller.activeTab === 0}
service={controller.tabRefs.myVcs}
vcItemActor={controller.selectedVc}
isViewingVc={controller.isViewingVc}
/>
<ReceivedVcsTab
isVisible={controller.activeTab === 1}
@@ -117,6 +119,7 @@ export const HomeScreen: React.FC<HomeRouteProps> = props => {
};
export interface HomeScreenTabProps {
isViewingVc: any;
isVisible: boolean;
service: TabRef;
vcItemActor: ActorRefFrom<typeof VCItemMachine>;

View File

@@ -37,4 +37,43 @@
"tabs"?: "checkStorage" | "gotoIssuers" | "history" | "idle" | "init" | "myVcs" | "receivedVcs" | "storageLimitReached"; };
tags: never;
}
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"done.invoke.HomeScreen.tabs.checkStorage:invocation[0]": { type: "done.invoke.HomeScreen.tabs.checkStorage:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"xstate.after(100)#HomeScreen.tabs.init": { type: "xstate.after(100)#HomeScreen.tabs.init" };
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
"checkStorageAvailability": "done.invoke.HomeScreen.tabs.checkStorage:invocation[0]";
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
"resetSelectedVc": "DISMISS_MODAL" | "xstate.init";
"sendAddEvent": "DOWNLOAD_ID";
"setSelectedVc": "VIEW_VC";
"spawnTabActors": "xstate.init";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
"isMinimumStorageLimitReached": "done.invoke.HomeScreen.tabs.checkStorage:invocation[0]";
};
eventsCausingServices: {
"checkStorageAvailability": "GOTO_ISSUERS";
"issuersMachine": "done.invoke.HomeScreen.tabs.checkStorage:invocation[0]";
};
matchesStates: "modals" | "modals.none" | "modals.viewingVc" | "tabs" | "tabs.checkStorage" | "tabs.gotoIssuers" | "tabs.history" | "tabs.idle" | "tabs.init" | "tabs.myVcs" | "tabs.receivedVcs" | "tabs.storageLimitReached" | { "modals"?: "none" | "viewingVc";
"tabs"?: "checkStorage" | "gotoIssuers" | "history" | "idle" | "init" | "myVcs" | "receivedVcs" | "storageLimitReached"; };
tags: never;
}

View File

@@ -41,6 +41,13 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = props => {
Array<Record<string, VCMetadata>>
>([]);
const [showPinVc, setShowPinVc] = useState(true);
const [highlightCardLayout, setHighlightCardLayout] = useState<null | {
x: number;
y: number;
width: number;
height: number;
type: 'success' | 'failure';
}>(null);
const getId = () => {
controller.DISMISS();
@@ -68,6 +75,13 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = props => {
}
}, []);
useEffect(() => {
if (!props.isViewingVc) {
controller.RESET_HIGHLIGHT?.();
setHighlightCardLayout(null);
}
}, [props.isViewingVc]);
useEffect(() => {
filterVcs(search);
}, [controller.vcData]);
@@ -276,23 +290,40 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = props => {
)}
</Row>
{showPinVc &&
vcMetadataOrderedByPinStatus.map((vcMetadata, index) => {
return (
<VcItemContainer
key={vcMetadata.getVcKey()}
vcMetadata={vcMetadata}
margin="0 2 8 2"
onPress={controller.VIEW_VC}
isDownloading={controller.inProgressVcDownloads?.has(
vcMetadata.getVcKey(),
)}
isPinned={vcMetadata.isPinned}
isInitialLaunch={controller.isInitialDownloading}
isTopCard={index === 0}
/>
);
})}
vcMetadataOrderedByPinStatus.map((vcMetadata, index) => {
const vcKey = vcMetadata.getVcKey();
const isSuccessHighlighted =
controller.reverificationSuccess.status &&
controller.reverificationSuccess.vcKey === vcKey;
const isFailureHighlighted =
controller.reverificationfailure.status &&
controller.reverificationfailure.vcKey === vcKey;
const highlightType = isSuccessHighlighted
? 'success'
: isFailureHighlighted
? 'failure'
: null;
return (
<VcItemContainer
key={vcKey}
vcMetadata={vcMetadata}
margin="0 2 8 2"
onPress={controller.VIEW_VC}
isDownloading={controller.inProgressVcDownloads?.has(vcKey)}
isPinned={vcMetadata.isPinned}
isInitialLaunch={controller.isInitialDownloading}
isTopCard={index === 0}
onMeasured={rect => {
if (highlightType && !highlightCardLayout) {
setHighlightCardLayout({ ...rect, type: highlightType });
}
}}
/>
);
})}
{filteredSearchData.length > 0 && !showPinVc
? filteredSearchData.map(vcMetadataObj => {
const [vcKey, vcMetadata] =
@@ -470,6 +501,14 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = props => {
primaryButtonTestID="tryAgain"
/>
)}
<MessageOverlay
overlayMode='highlight'
isVisible={!!highlightCardLayout && !props.isViewingVc}
cardLayout={highlightCardLayout ?? undefined}
onBackdropPress={() => {
controller.RESET_HIGHLIGHT()
setHighlightCardLayout(null)}}
/>
</React.Fragment>
);
};

View File

@@ -6,6 +6,8 @@ import {
selectDownloadingFailedVcs,
selectInProgressVcDownloads,
selectIsRefreshingMyVcs,
selectIsReverificationFailure,
selectIsReverificationSuccess,
selectIsTampered,
selectMyVcs,
selectMyVcsMetadata,
@@ -44,7 +46,6 @@ export function useMyVcsTab(props: HomeScreenTabProps) {
const vcMetaService = appService.children.get('vcMeta')!!;
const settingsService = appService.children.get('settings')!!;
const authService = appService.children.get('auth');
return {
service,
AddVcModalService: useSelector(service, selectAddVcModal),
@@ -72,7 +73,17 @@ export function useMyVcsTab(props: HomeScreenTabProps) {
vcMetaService,
selectVerificationErrorMessage,
),
reverificationSuccess: useSelector(vcMetaService,selectIsReverificationSuccess),
reverificationfailure: useSelector(vcMetaService,selectIsReverificationFailure),
RESET_REVERIFICATION_FAILURE: () => {
vcMetaService.send(VcMetaEvents.RESET_REVERIFY_VC_FAILED());
},
RESET_REVERIFICATION_SUCCESS: () => {
vcMetaService.send(VcMetaEvents.RESET_REVERIFY_VC_SUCCESS());
},
RESET_HIGHLIGHT:()=>{
vcMetaService.send(VcMetaEvents.RESET_HIGHLIGHT());
},
SET_STORE_VC_ITEM_STATUS: () =>
service.send(MyVcsTabEvents.SET_STORE_VC_ITEM_STATUS()),

View File

@@ -1,73 +1,89 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
'done.invoke.AddVcModal': {
type: 'done.invoke.AddVcModal';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.GetVcModal': {
type: 'done.invoke.GetVcModal';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]': {
type: 'done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'xstate.init': {type: 'xstate.init'};
};
invokeSrcNameMap: {
checkNetworkStatus: 'done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]';
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
resetStoringVcItemStatus: 'RESET_STORE_VC_ITEM_STATUS';
sendVcAdded: 'STORE_RESPONSE';
setStoringVcItemStatus: 'SET_STORE_VC_ITEM_STATUS' | 'STORE_RESPONSE';
storeVcItem: 'done.invoke.AddVcModal';
viewVcFromParent: 'VIEW_VC';
};
eventsCausingDelays: {};
eventsCausingGuards: {
isNetworkOn: 'done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]';
};
eventsCausingServices: {
AddVcModal:
| 'done.invoke.GetVcModal'
| 'done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]';
GetVcModal: 'GET_VC';
checkNetworkStatus: 'ADD_VC' | 'TRY_AGAIN';
};
matchesStates:
| 'addVc'
| 'addVc.checkNetwork'
| 'addVc.networkOff'
| 'addingVc'
| 'addingVc.savingFailed'
| 'addingVc.savingFailed.idle'
| 'addingVc.storing'
| 'addingVc.waitingForvcKey'
| 'gettingVc'
| 'gettingVc.waitingForvcKey'
| 'idle'
| 'viewingVc'
| {
addVc?: 'checkNetwork' | 'networkOff';
addingVc?:
| 'savingFailed'
| 'storing'
| 'waitingForvcKey'
| {savingFailed?: 'idle'};
gettingVc?: 'waitingForvcKey';
};
tags: never;
}
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"done.invoke.AddVcModal": { type: "done.invoke.AddVcModal"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.GetVcModal": { type: "done.invoke.GetVcModal"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]": { type: "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
"checkNetworkStatus": "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]";
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
"resetStoringVcItemStatus": "RESET_STORE_VC_ITEM_STATUS";
"sendDownloadingFailedToVcMeta": "STORE_ERROR";
"sendVcAdded": "STORE_RESPONSE";
"setStoringVcItemStatus": "SET_STORE_VC_ITEM_STATUS" | "STORE_RESPONSE";
"storeVcItem": "done.invoke.AddVcModal";
"viewVcFromParent": "VIEW_VC";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
"isNetworkOn": "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]";
};
eventsCausingServices: {
"AddVcModal": "done.invoke.GetVcModal" | "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]";
"GetVcModal": "GET_VC";
"checkNetworkStatus": "ADD_VC" | "TRY_AGAIN";
};
matchesStates: "addVc" | "addVc.checkNetwork" | "addVc.networkOff" | "addingVc" | "addingVc.savingFailed" | "addingVc.savingFailed.idle" | "addingVc.storing" | "addingVc.waitingForvcKey" | "gettingVc" | "gettingVc.waitingForvcKey" | "idle" | "viewingVc" | { "addVc"?: "checkNetwork" | "networkOff";
"addingVc"?: "savingFailed" | "storing" | "waitingForvcKey" | { "savingFailed"?: "idle"; };
"gettingVc"?: "waitingForvcKey"; };
tags: never;
}
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"done.invoke.AddVcModal": { type: "done.invoke.AddVcModal"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.GetVcModal": { type: "done.invoke.GetVcModal"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]": { type: "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
"checkNetworkStatus": "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]";
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
"resetStoringVcItemStatus": "RESET_STORE_VC_ITEM_STATUS";
"sendDownloadingFailedToVcMeta": "STORE_ERROR";
"sendVcAdded": "STORE_RESPONSE";
"setStoringVcItemStatus": "SET_STORE_VC_ITEM_STATUS" | "STORE_RESPONSE";
"storeVcItem": "done.invoke.AddVcModal";
"viewVcFromParent": "VIEW_VC";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
"isNetworkOn": "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]";
};
eventsCausingServices: {
"AddVcModal": "done.invoke.GetVcModal" | "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]";
"GetVcModal": "GET_VC";
"checkNetworkStatus": "ADD_VC" | "TRY_AGAIN";
};
matchesStates: "addVc" | "addVc.checkNetwork" | "addVc.networkOff" | "addingVc" | "addingVc.savingFailed" | "addingVc.savingFailed.idle" | "addingVc.storing" | "addingVc.waitingForvcKey" | "gettingVc" | "gettingVc.waitingForvcKey" | "idle" | "viewingVc" | { "addVc"?: "checkNetwork" | "networkOff";
"addingVc"?: "savingFailed" | "storing" | "waitingForvcKey" | { "savingFailed"?: "idle"; };
"gettingVc"?: "waitingForvcKey"; };
tags: never;
}

View File

@@ -40,6 +40,7 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
const controller = useViewVcModal(props);
const profileImage = controller.verifiableCredentialData.face;
const verificationStatus = controller.verificationStatus;
const verificationStatusMessage = controller.verificationStatus?.isRevoked ? "revoked" : controller.verificationStatus?.isExpired ? "expired" : controller.verificationStatus?.statusType;
const [verifiableCredential, setVerifiableCredential] = useState(null);
const [svgTemplate, setSvgTemplate] = useState<string[] | null>(null);
const [svgRendererError, setSvgRendererError] = useState<string[] | null>(
@@ -68,7 +69,7 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
!controller.verifiableCredentialData.vcMetadata.isVerified &&
!controller.isVerificationInProgress
) {
props.vcItemActor.send({type: 'VERIFY'});
props.vcItemActor.send({ type: 'VERIFY' });
}
}, [controller.verifiableCredentialData.vcMetadata.isVerified]);
@@ -179,8 +180,8 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
{controller.showVerificationStatusBanner && (
<BannerNotification
type={verificationStatus?.statusType as BannerStatus}
message={t(`VcVerificationBanner:${verificationStatus?.statusType}`, {
vcDetails: `${verificationStatus.vcType} ${verificationStatus?.vcNumber}`,
message={t(`VcVerificationBanner:${verificationStatusMessage}`, {
vcDetails: `${verificationStatus?.vcType} ${verificationStatus?.vcNumber ?? ""}`,
})}
onClosePress={controller.RESET_VERIFICATION_STATUS}
key={'reVerificationInProgress'}
@@ -238,7 +239,7 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
/>
<MessageOverlay
isVisible={controller.isWalletBindingInProgress}
isVisible={controller.isWalletBindingInProgress || controller.isReverifyingVc}
title={t('inProgress')}
progress
/>

View File

@@ -22,6 +22,7 @@ import {
selectShowVerificationStatusBanner,
selectIsVerificationCompleted,
selectCredential,
isReverifyingVc,
} from '../../machines/VerifiableCredential/VCItemMachine/VCItemSelectors';
import {selectPasscode} from '../../machines/auth';
import {biometricsMachine, selectIsSuccess} from '../../machines/biometrics';
@@ -113,6 +114,7 @@ export function useViewVcModal({vcItemActor, isVisible}: ViewVcModalProps) {
isBindingError: useSelector(vcItemActor, selectShowWalletBindingError),
isBindingSuccess: useSelector(vcItemActor, selectWalletBindingSuccess),
isBindingWarning: useSelector(vcItemActor, selectBindingWarning),
isReverifyingVc: useSelector(vcItemActor, isReverifyingVc),
isCommunicationDetails: useSelector(
vcItemActor,
selectIsCommunicationDetails,

View File

@@ -143,3 +143,10 @@ export const isCacheExpired = (timestamp: number) => {
export function getVerifierKey(verifier: string): string {
return `trusted_verifier_${verifier}`;
}
export const enum VerificationStatus {
VALID = 'VALID',
REVOKED = 'REVOKED',
PENDING = 'PENDING',
EXPIRED = 'EXPIRED',
}

View File

@@ -31,10 +31,12 @@ export class VCMetadata {
mosipIndividualId: string = '';
format: string = '';
isExpired: boolean = false;
isRevoked: boolean = false;
downloadKeyType: string = '';
credentialType: string = '';
issuerHost: string = '';
lastKnownStatusTimestamp?: string = '';
constructor({
idType = '',
@@ -49,8 +51,10 @@ export class VCMetadata {
format = '',
downloadKeyType = '',
isExpired = false,
isRevoked = false,
credentialType = '',
issuerHost = '',
lastKnownStatusTimestamp = '',
} = {}) {
this.idType = idType;
this.requestId = requestId;
@@ -64,8 +68,10 @@ export class VCMetadata {
this.format = format;
this.downloadKeyType = downloadKeyType;
this.isExpired = isExpired;
this.isRevoked = isRevoked;
this.credentialType = credentialType;
this.issuerHost = issuerHost;
this.lastKnownStatusTimestamp = lastKnownStatusTimestamp;
}
//TODO: Remove any typing and use appropriate typing
@@ -81,6 +87,7 @@ export class VCMetadata {
timestamp: vc.vcMetadata ? vc.vcMetadata.timestamp : vc.timestamp,
isVerified: vc.isVerified,
isExpired: vc.isExpired,
isRevoked: vc.isRevoked,
mosipIndividualId: vc.mosipIndividualId
? vc.mosipIndividualId
: vc.vcMetadata
@@ -89,6 +96,7 @@ export class VCMetadata {
downloadKeyType: vc.downloadKeyType,
credentialType: vc.credentialType,
issuerHost: vc.issuerHost,
lastKnownStatusTimestamp: vc.lastKnownStatusTimestamp,
});
}
@@ -156,6 +164,8 @@ export const getVCMetadata = (context: object, keyType: string) => {
timestamp: context.timestamp ?? '',
isVerified: context.vcMetadata.isVerified ?? false,
isExpired: context.vcMetadata.isExpired ?? false,
isRevoked: context.vcMetadata.isRevoked ?? false,
lastKnownStatusTimestamp:context.vcMetadata.lastKnownStatusTimestamp ?? '',
mosipIndividualId: getMosipIndividualId(
context['verifiableCredential'] as VerifiableCredential,
issuer,

View File

@@ -1,5 +1,5 @@
import { DocumentDirectoryPath } from "react-native-fs";
import { MY_VCS_STORE_KEY } from "../constants";
import { EXPIRED_VC_ERROR_CODE, MY_VCS_STORE_KEY } from "../constants";
import { decryptJson, encryptJson } from "../cryptoutil/cryptoUtil";
import fileStorage from "../fileStorage";
import Storage, { MMKV } from "../storage";
@@ -162,24 +162,33 @@ async function handlePreviousBackup(
const timestamp = Date.now() + Math.random().toString().substring(2, 10);
const prevUnixTimeStamp = vcData.vcMetadata.timestamp;
//verify the credential and update the metadata
const verifiableCredential = vcData.verifiableCredential?.credential || vcData.verifiableCredential;
const verificationResult = await verifyCredentialData(verifiableCredential, vcData.vcMetadata.format)
const isVerified = verificationResult.isVerified;
vcData.vcMetadata.timestamp = timestamp;
vcData.vcMetadata.isVerified = isVerified;
//verify the credential and update the metadata
const verifiableCredential =
vcData.verifiableCredential?.credential || vcData.verifiableCredential;
const verificationResult = await verifyCredentialData(
verifiableCredential,
vcData.vcMetadata.format,
);
const isVerified = verificationResult.isVerified;
const isRevoked = verificationResult.isRevoked ?? false;
const isExpired =
verificationResult.verificationErrorCode === EXPIRED_VC_ERROR_CODE;
vcData.vcMetadata.timestamp = timestamp;
vcData.vcMetadata.isVerified = isVerified;
//update the vcMetadata
dataFromDB.myVCs.forEach(myVcMetadata => {
if (
myVcMetadata.requestId === vcData.vcMetadata.requestId &&
myVcMetadata.timestamp === prevUnixTimeStamp
) {
myVcMetadata.timestamp = timestamp;
myVcMetadata.isVerified = isVerified;
}
});
//update the vcMetadata
dataFromDB.myVCs.forEach(myVcMetadata => {
if (
myVcMetadata.requestId === vcData.vcMetadata.requestId &&
myVcMetadata.timestamp === prevUnixTimeStamp
) {
myVcMetadata.timestamp = timestamp;
myVcMetadata.isVerified = isVerified;
myVcMetadata.isRevoked = isRevoked;
myVcMetadata.isExpired = isExpired;
myVcMetadata.lastKnownStatusTimestamp = new Date().toISOString();
}
});
// Encrypt and store the VC
const updatedVcKey = new VCMetadata(vcData.vcMetadata).getVcKey();

View File

@@ -46,15 +46,12 @@ export const Issuers = {
export function getVcVerificationDetails(
statusType,
vcMetadata: VCMetadata,
verifiableCredential,
wellknown: Object,
): vcVerificationBannerDetails {
const credentialType = getCredentialTypeFromWellKnown(
wellknown,
getVerifiableCredential(verifiableCredential).credentialConfigurationId,
);
const credentialType = vcMetadata.credentialType
return {
statusType: statusType,
isRevoked:vcMetadata.isRevoked,
isExpired: vcMetadata.isExpired,
vcType: credentialType,
};
}

View File

@@ -0,0 +1,65 @@
import {NativeModules} from 'react-native';
export type CredentialStatusResult = {
status: number;
purpose: string;
errorCode?: string;
errorMessage?: string;
statusListVC?: string;
};
export type VerificationSummaryResult = {
verificationStatus: boolean;
verificationMessage: string;
verificationErrorCode: string;
credentialStatus: CredentialStatusResult[];
};
class VCVerifier {
private static instance: VCVerifier;
private vcVerifier;
private constructor() {
this.vcVerifier = NativeModules.VCVerifierModule;
}
static getInstance(): VCVerifier {
if (!VCVerifier.instance) {
VCVerifier.instance = new VCVerifier();
}
return VCVerifier.instance;
}
async getCredentialStatus(
credential: any,
format: string,
): Promise<CredentialStatusResult[]> {
try {
const result: CredentialStatusResult[] =
await this.vcVerifier.getCredentialStatus(
JSON.stringify(credential),
format,
);
return result;
} catch (error) {
throw new Error(`Failed to get credential status: ${error}`);
}
}
async getVerificationSummary(
credentialString: string,
credentialFormat: string,
): Promise<VerificationSummaryResult> {
try {
const result = await this.vcVerifier.getVerificationSummary(
credentialString,
credentialFormat,
[],
);
return result;
} catch (error) {
throw new Error(`Failed to get verification summary: ${error}`);
}
}
}
export default VCVerifier;

View File

@@ -12,8 +12,9 @@ import {getErrorEventData, sendErrorEvent} from '../telemetry/TelemetryUtils';
import {TelemetryConstants} from '../telemetry/TelemetryConstants';
import {getMosipIdentifier} from '../commonUtil';
import {NativeModules} from 'react-native';
import {isAndroid} from '../constants';
import {isAndroid, isIOS} from '../constants';
import {VCFormat} from '../VCFormat';
import VCVerifier, {CredentialStatusResult, VerificationSummaryResult} from '../vcVerifier/VcVerifier';
// FIXME: Ed25519Signature2018 not fully supported yet.
// Ed25519Signature2018 proof type check is not tested with its real credential
@@ -61,7 +62,7 @@ async function verifyCredentialForAndroid(
typeof verifiableCredential === 'string'
? verifiableCredential
: JSON.stringify(verifiableCredential);
const vcVerifierResult = await vcVerifier.verifyCredentials(
const vcVerifierResult = await VCVerifier.getInstance().getVerificationSummary(
credentialString,
credentialFormat,
);
@@ -72,28 +73,45 @@ async function verifyCredentialForIos(
verifiableCredential: Credential,
credentialFormat: string,
): Promise<VerificationResult> {
if (credentialFormat === VCFormat.mso_mdoc || credentialFormat === VCFormat.vc_sd_jwt || credentialFormat === VCFormat.dc_sd_jwt) {
if (
credentialFormat === VCFormat.mso_mdoc ||
credentialFormat === VCFormat.vc_sd_jwt ||
credentialFormat === VCFormat.dc_sd_jwt
) {
return createSuccessfulVerificationResult();
}
/*
Since Digital Bazaar library is not able to verify ProofType: "Ed25519Signature2020",
defaulting it to return true until VcVerifier is implemented for iOS.
*/
let verificationResponse: VerificationResult;
if (verifiableCredential.proof.type === ProofType.ED25519_2020) {
return createSuccessfulVerificationResult();
verificationResponse = createSuccessfulVerificationResult();
}
else{
const purpose = getPurposeFromProof(verifiableCredential.proof.proofPurpose);
const suite = selectVerificationSuite(verifiableCredential.proof);
const vcjsOptions = {
purpose,
suite,
credential: verifiableCredential,
documentLoader: jsonld.documentLoaders.xhr(),
};
const result = await vcjs.verifyCredential(vcjsOptions);
verificationResponse = handleResponse(result, verifiableCredential);
}
const purpose = getPurposeFromProof(verifiableCredential.proof.proofPurpose);
const suite = selectVerificationSuite(verifiableCredential.proof);
const vcjsOptions = {
purpose,
suite,
credential: verifiableCredential,
documentLoader: jsonld.documentLoaders.xhr(),
};
const result = await vcjs.verifyCredential(vcjsOptions);
return handleResponse(result, verifiableCredential);
if (verificationResponse.isVerified) {
const statusArray = await VCVerifier.getInstance().getCredentialStatus(
verifiableCredential,
credentialFormat,
);
const isRevoked = await checkIsStatusRevoked(statusArray);
verificationResponse.isRevoked = isRevoked;
}
return verificationResponse;
}
function getPurposeFromProof(proofPurpose) {
@@ -159,10 +177,10 @@ function handleResponse(
return verificationResult;
}
function handleVcVerifierResponse(
verificationResult: any,
async function handleVcVerifierResponse(
verificationResult: VerificationSummaryResult,
verifiableCredential: VerifiableCredential | Credential,
): VerificationResult {
): Promise<VerificationResult> {
try {
if (!verificationResult.verificationStatus) {
verificationResult.verificationErrorCode =
@@ -174,10 +192,12 @@ function handleVcVerifierResponse(
verifiableCredential,
);
}
const isRevoked = await checkIsStatusRevoked(verificationResult.credentialStatus)
return {
isVerified: verificationResult.verificationStatus,
verificationMessage: verificationResult.verificationMessage,
verificationErrorCode: verificationResult.verificationErrorCode,
isRevoked: isRevoked,
};
} catch (error) {
console.error(
@@ -193,6 +213,50 @@ function handleVcVerifierResponse(
}
}
export async function checkIsStatusRevoked(
vcStatus: CredentialStatusResult[],
): Promise<boolean> {
if (!vcStatus || vcStatus.length === 0) return false;
const revocationStatuses = vcStatus.filter(
s => s.purpose?.toLowerCase() === 'revocation',
);
let result = false;
for (const status of revocationStatuses) {
if (status.status > 0) {
if (isIOS()) {
const isValid = await verifyStatusListVC(status.statusListVC);
if (!isValid) {
throw new Error(`Revoked statusListVC verification failed`);
}
}
result = true;
} else if (status.status < 0) {
throw new Error(
`Error fetching revocation status : ${status.errorMessage}`,
);
}
}
if (result) return true;
if (isIOS()) {
for (const status of revocationStatuses) {
if (status.status === 0) {
const isValid = await verifyStatusListVC(status.statusListVC);
if (!isValid) {
throw new Error(
`StatusListVC verification failed for valid entry ${status.errorMessage}`,
);
}
}
}
}
return false;
}
function createSuccessfulVerificationResult(): VerificationResult {
return {
isVerified: true,
@@ -241,4 +305,13 @@ export interface VerificationResult {
isVerified: boolean;
verificationMessage: string;
verificationErrorCode: string;
isRevoked?: boolean;
}
//TODO: Implement status list VC verification for iOS.
//Currently Digital Bazaar library does not support VC 2.0 status list VC verification.
function verifyStatusListVC(statusListVC: string | undefined) {
return true;
}
export const VERIFICATION_TIMEOUT_IN_MS = 5000;