[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

@@ -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,