Files
inji-wallet/machines/vc.ts
Sreenadh S bad21e409c Inji 364 UI update for success messages (#863)
* feat(INJI-364): add new ui for successful activation of VC

* feat(INJI-364): update VC receive UI

* feat(INJI-364): extract banner notification component

* feat(INJI-364): re suse banner notification component

* feat(INJI-364): add successfully share popup and translations

* feat(INJI-364): use proper state for showing  the success modal

* fix(INJI-364): show activate popup in respective screens only

* refactor(INJI-364): rename props

* refactor(INJI-364): remove logs

* fix(INJI-372): fix hindi translation

* chore(INJI-364): update package-lock.json

* refactor(INJI-364): add proper testID implementation for BannerNotification

* refactor(INJI-364): remove unused imports

* refactor(INJI-364): remove multiple state set

* refactor(INJI-364): add missing testID

* refactor(INJI-364): add missing testID

* feat(INJI-364): add activated notification to esignet VC also

* chore(INJI-364): update package-lock.json
2023-09-26 21:05:22 +05:30

421 lines
14 KiB
TypeScript

import {EventFrom, send, sendParent, StateFrom} from 'xstate';
import {createModel} from 'xstate/lib/model';
import {StoreEvents} from './store';
import {VC} from '../types/VC/ExistingMosipVC/vc';
import {AppServices} from '../shared/GlobalContext';
import {log, respond} from 'xstate/lib/actions';
import {ExistingMosipVCItemEvents} from './VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {MY_VCS_STORE_KEY, RECEIVED_VCS_STORE_KEY} from '../shared/constants';
import {parseMetadatas, VCMetadata} from '../shared/VCMetadata';
import {OpenId4VCIProtocol} from '../shared/openId4VCI/Utils';
import {EsignetMosipVCItemEvents} from './VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
const model = createModel(
{
serviceRefs: {} as AppServices,
myVcs: [] as VCMetadata[],
receivedVcs: [] as VCMetadata[],
vcs: {} as Record<string, VC>,
inProgressVcDownloads: new Set<string>(),
areAllVcsDownloaded: false as boolean,
walletBindingSuccess: false,
},
{
events: {
VIEW_VC: (vc: VC) => ({vc}),
GET_VC_ITEM: (vcMetadata: VCMetadata) => ({vcMetadata}),
STORE_RESPONSE: (response: unknown) => ({response}),
STORE_ERROR: (error: Error) => ({error}),
VC_ADDED: (vcMetadata: VCMetadata) => ({vcMetadata}),
REMOVE_VC_FROM_CONTEXT: (vcMetadata: VCMetadata) => ({vcMetadata}),
VC_METADATA_UPDATED: (vcMetadata: VCMetadata) => ({vcMetadata}),
VC_RECEIVED: (vcMetadata: VCMetadata) => ({vcMetadata}),
VC_DOWNLOADED: (vc: VC) => ({vc}),
VC_DOWNLOADED_FROM_OPENID4VCI: (vc: VC, vcMetadata: VCMetadata) => ({
vc,
vcMetadata,
}),
VC_UPDATE: (vc: VC) => ({vc}),
REFRESH_MY_VCS: () => ({}),
REFRESH_MY_VCS_TWO: (vc: VC) => ({vc}),
REFRESH_RECEIVED_VCS: () => ({}),
GET_RECEIVED_VCS: () => ({}),
WALLET_BINDING_SUCCESS: () => ({}),
RESET_WALLET_BINDING_SUCCESS: () => ({}),
ADD_VC_TO_IN_PROGRESS_DOWNLOADS: (requestId: string) => ({requestId}),
REMOVE_VC_FROM_IN_PROGRESS_DOWNLOADS: (requestId: string) => ({
requestId,
}),
RESET_ARE_ALL_VCS_DOWNLOADED: () => ({}),
},
},
);
export const VcEvents = model.events;
export const vcMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QDcDGA6AlgO0wF3QFsBPANVVgGIBlAFQHkAlAUQH0XqAFegOWucSgADgHtY+TCOyCQAD0QBmABwAmdAEYALCoCsANgAMOgJx6dAdnNmANCGKIlBvemWbLKvcc06FVgL5+tmhYuAQATmCoYJjIkORUdExsHNx8AkggouJ4ktIZ8gjKalq65qrmmup6SpqatvYI6k6a6HoKmko6OkpK5sb9AUEYEQCGEMREZBRYEAA2YJQsAGIcABKsALIAmqykAMLUMlkSUjIF6uom6JV6ujrqxo7qZfWITRUamgpOKubdWuYVIMQMFRuNJvF0BEAGYRWAACxwUBoDBY7GYXF4-COYhOeVA50u5nQxh8fyKCm6uleCFMxKayjaBgUCjaFmBoLAYwmESiMTi00wcwWyzW6L2zAAkqRmAARXYHHHZXJnN7qXwacwGAyVJymJTqGnqlnoToqFS+XzqA1ODnDLng3nRWIQSEwuGI7DIxJolJY9LCXE5U75NV6dToJy+YwGXrGFRuI0WiNKUnqvS3LwGcwKO1Qh3ESgAcWYtHFUpl8v2hwyx2D+LkbxUplangzPVumhjdTsiEMBg0XWb6rp1vMebBhZLZf2rEltGYGyVeNVjQuEeZvi1zO1Nl7CH7g50w4Uo56E4LlFnAEFZbK5cv66vjAoI1ahyz9OSaYfLsfjCOejmGOF7cleeysLK9AAOo8AAMvQt4PrWQYqqGjTHgokbxhaKgPF46gqEoP7akeug1H0x7aKB4zgeW0rIYGyohgSfa6C4NRVO0mh6DxlQ0n8OitE0mhOD4OpuLmwLYCIEBwDIwQ4PgEIUI+aGsYU1ToBY0baM2Si3LxP5WJGNTNgYeFUgMgQghgSnhJEzoCvAKHMQ2BTtPSRg6o4omVBUNIAcUqgKCoTg9DqJh5vZaksY2hQWho3kdDqOrPD2DSGFhsbaAZDyAjxNETCQkJCvMsXuYgjzOOafxakorJuBaRoWUJ3wWRUHQ+AZehFSpsD5rCcCelAFWriOKbtg1XUmC8+79MYpras2Zj9H0CZ9SVqmuSu6GmGotUWLGjWAgoNKpi0DIhfoFhmFJQz5ty+Z8i6pXCmNe2dBoZj3BZlw1KyRqvhGGYWGtxhlAZSh9U6-KutM7rDUiH0aSOag+I8zQ-N4SYGd9VIPIRzZETDjlw-EKPxaYEZVF0TSWQDehGv9pqkgYENVJcvhFZTBQvvSP30-9XxM-ulgDldvjVA15qaHavOIM2i1NDoPmpf5GWIMe9JDjotSGFm6gBAEQA */
model.createMachine(
{
predictableActionArguments: true,
preserveActionOrder: true,
tsTypes: {} as import('./vc.typegen').Typegen0,
schema: {
context: model.initialContext,
events: {} as EventFrom<typeof model>,
},
id: 'vc',
initial: 'init',
states: {
init: {
on: {
REFRESH_MY_VCS: {
target: '#vc.ready.myVcs.refreshing',
},
},
initial: 'myVcs',
states: {
myVcs: {
entry: 'loadMyVcs',
on: {
STORE_RESPONSE: {
actions: 'setMyVcs',
target: 'receivedVcs',
},
},
},
receivedVcs: {
entry: 'loadReceivedVcs',
on: {
STORE_RESPONSE: {
actions: 'setReceivedVcs',
target: '#vc.ready',
},
},
},
},
},
ready: {
entry: sendParent('READY'),
type: 'parallel',
states: {
myVcs: {
initial: 'idle',
states: {
idle: {
on: {
REFRESH_MY_VCS: {
actions: [log('REFRESH_MY_VCS:myVcs---')],
target: 'refreshing',
},
WALLET_BINDING_SUCCESS: {
actions: 'setWalletBindingSuccess',
},
},
},
refreshing: {
entry: 'loadMyVcs',
on: {
STORE_RESPONSE: {
actions: 'setMyVcs',
target: 'idle',
},
},
},
},
},
receivedVcs: {
initial: 'idle',
states: {
idle: {
on: {
REFRESH_RECEIVED_VCS: {
target: 'refreshing',
},
},
},
refreshing: {
entry: 'loadReceivedVcs',
on: {
STORE_RESPONSE: {
actions: 'setReceivedVcs',
target: 'idle',
},
},
},
},
},
},
on: {
GET_RECEIVED_VCS: {
actions: 'getReceivedVcsResponse',
},
GET_VC_ITEM: {
actions: 'getVcItemResponse',
},
VC_ADDED: {
actions: 'prependToMyVcs',
},
REMOVE_VC_FROM_CONTEXT: {
actions: 'removeVcFromMyVcs',
},
VC_METADATA_UPDATED: {
actions: ['updateMyVcs', 'setUpdatedVcMetadatas'],
},
VC_DOWNLOADED: {
actions: 'setDownloadedVc',
},
ADD_VC_TO_IN_PROGRESS_DOWNLOADS: {
actions: 'addVcToInProgressDownloads',
},
REMOVE_VC_FROM_IN_PROGRESS_DOWNLOADS: {
actions: 'removeVcFromInProgressDownlods',
},
RESET_ARE_ALL_VCS_DOWNLOADED: {
actions: 'resetAreAllVcsDownloaded',
},
VC_DOWNLOADED_FROM_OPENID4VCI: {
actions: 'setDownloadedVCFromOpenId4VCI',
},
VC_UPDATE: {
actions: 'setVcUpdate',
},
RESET_WALLET_BINDING_SUCCESS: {
actions: 'resetWalletBindingSuccess',
},
VC_RECEIVED: [
{
actions: 'moveExistingVcToTop',
cond: 'hasExistingReceivedVc',
},
{
actions: 'prependToReceivedVcs',
},
],
},
},
},
},
{
actions: {
getReceivedVcsResponse: respond(context => ({
type: 'VC_RESPONSE',
response: context.receivedVcs || [],
})),
getVcItemResponse: respond((context, event) => {
const vc = context.vcs[event.vcMetadata?.getVcKey()];
if (event.protocol === OpenId4VCIProtocol) {
return EsignetMosipVCItemEvents.GET_VC_RESPONSE(vc);
}
return ExistingMosipVCItemEvents.GET_VC_RESPONSE(vc);
}),
loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), {
to: context => context.serviceRefs.store,
}),
loadReceivedVcs: send(StoreEvents.GET(RECEIVED_VCS_STORE_KEY), {
to: context => context.serviceRefs.store,
}),
setMyVcs: model.assign({
myVcs: (_context, event) => {
return parseMetadatas((event.response || []) as object[]);
},
}),
setReceivedVcs: model.assign({
receivedVcs: (_context, event) => {
return parseMetadatas((event.response || []) as object[]);
},
}),
setDownloadedVc: (context, event) => {
const vcUniqueId = VCMetadata.fromVC(event.vc).getVcKey();
context.vcs[vcUniqueId] = event.vc;
},
addVcToInProgressDownloads: model.assign({
inProgressVcDownloads: (context, event) => {
let paresedInProgressList: Set<string> =
context.inProgressVcDownloads;
const newVcRequestID = event.requestId;
const newInProgressList = paresedInProgressList.add(newVcRequestID);
return newInProgressList;
},
}),
removeVcFromInProgressDownlods: model.assign({
inProgressVcDownloads: (context, event) => {
let paresedInProgressList: Set<string> =
context.inProgressVcDownloads;
const removeVcRequestID = event.requestId;
paresedInProgressList.delete(removeVcRequestID);
return paresedInProgressList;
},
areAllVcsDownloaded: context => {
if (context.inProgressVcDownloads.size == 0) {
return true;
}
return false;
},
}),
resetAreAllVcsDownloaded: model.assign({
areAllVcsDownloaded: () => false,
inProgressVcDownloads: new Set<string>(),
}),
setDownloadedVCFromOpenId4VCI: (context, event) => {
if (event.vc) context.vcs[event.vcMetadata.getVcKey()] = event.vc;
},
setVcUpdate: (context, event) => {
Object.keys(context.vcs).map(vcUniqueId => {
const eventVCMetadata = VCMetadata.fromVC(event.vc);
if (vcUniqueId === eventVCMetadata.getVcKey()) {
context.vcs[eventVCMetadata.getVcKey()] = context.vcs[vcUniqueId];
delete context.vcs[vcUniqueId];
return context.vcs[eventVCMetadata.getVcKey()];
}
});
},
setUpdatedVcMetadatas: send(
_context => {
return StoreEvents.SET(MY_VCS_STORE_KEY, _context.myVcs);
},
{to: context => context.serviceRefs.store},
),
prependToMyVcs: model.assign({
myVcs: (context, event) => [event.vcMetadata, ...context.myVcs],
}),
removeVcFromMyVcs: model.assign({
myVcs: (context, event) =>
context.myVcs.filter(
(vc: VCMetadata) => !vc.equals(event.vcMetadata),
),
}),
updateMyVcs: model.assign({
myVcs: (context, event) => [
...getUpdatedVCMetadatas(context.myVcs, event.vcMetadata),
],
}),
setWalletBindingSuccess: model.assign({
walletBindingSuccess: true,
}),
resetWalletBindingSuccess: model.assign({
walletBindingSuccess: false,
}),
prependToReceivedVcs: model.assign({
receivedVcs: (context, event) => [
event.vcMetadata,
...context.receivedVcs,
],
}),
moveExistingVcToTop: model.assign({
receivedVcs: (context, event) => {
return [
event.vcMetadata,
...context.receivedVcs.filter(value =>
value.equals(event.vcMetadata),
),
];
},
}),
},
guards: {
hasExistingReceivedVc: (context, event) =>
context.receivedVcs.find(vcMetadata =>
vcMetadata.equals(event.vcMetadata),
) != null,
},
},
);
export function createVcMachine(serviceRefs: AppServices) {
return vcMachine.withContext({
...vcMachine.context,
serviceRefs,
});
}
type State = StateFrom<typeof vcMachine>;
export function selectMyVcsMetadata(state: State): VCMetadata[] {
return state.context.myVcs;
}
export function selectShareableVcsMetadata(state: State): VCMetadata[] {
return state.context.myVcs.filter(
vcMetadata => state.context.vcs[vcMetadata.getVcKey()]?.credential != null,
);
}
export function selectReceivedVcsMetadata(state: State): VCMetadata[] {
return state.context.receivedVcs;
}
export function selectIsRefreshingMyVcs(state: State) {
return state.matches('ready.myVcs.refreshing');
}
export function selectIsRefreshingReceivedVcs(state: State) {
return state.matches('ready.receivedVcs.refreshing');
}
/*
this methods returns all the binded vc's in the wallet.
*/
export function selectBindedVcsMetadata(state: State): VCMetadata[] {
return state.context.myVcs.filter(vcMetadata => {
const walletBindingResponse =
state.context.vcs[vcMetadata.getVcKey()]?.walletBindingResponse;
return (
!isEmpty(walletBindingResponse) &&
!isEmpty(walletBindingResponse?.walletBindingId)
);
});
}
export function selectAreAllVcsDownloaded(state: State) {
return state.context.areAllVcsDownloaded;
}
export function selectInProgressVcDownloadsCount(state: State) {
return state.context.inProgressVcDownloads.size;
}
function getUpdatedVCMetadatas(
existingVCMetadatas: VCMetadata[],
updatedVcMetadata: VCMetadata,
) {
const isPinStatusUpdated = updatedVcMetadata.isPinned;
return existingVCMetadatas.map(value => {
if (value.equals(updatedVcMetadata)) {
return updatedVcMetadata;
} else if (isPinStatusUpdated) {
return new VCMetadata({...value, isPinned: false});
} else {
return value;
}
});
}
function isEmpty(object) {
return object == null || object == '' || object == undefined;
}
export function selectWalletBindingSuccess(state: State) {
return state.context.walletBindingSuccess;
}