From ec9fd9f6d93b5585d53d20cb9754332c867af149 Mon Sep 17 00:00:00 2001 From: Tilak Puli <34330361+tilak-puli@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:51:59 +0530 Subject: [PATCH] Inji-344: Refactoring VC Key (#798) * feat(inji-344): Use VC Key class instead of separate functions for managing vc key * feat(inji-344): Use properties from VcKey Class instead of reading from vckey string * feat(inji-344): Rename vcKey to vcMetadata * feat(inji-344): Use vc's unique id or vckey instead of joined string of vc metadata * feat(inji-344): Use vc key instead of unique id to avoid confusion. Fix issues reg parsing vc metadata * feat(inji-344):fix redownloading issue Co-authored-by: Tilak * feat(inji-344): Remove vc getting stored on update of pin status * feat(inji-344): update other vc's pin status to false when any vc is pinned * feat(inji-344): remove hash ID for UIN * feat(inji-344): revert google services json * feat(inji-344): remove mmkv logs added for debugging * feat(inji-344): fix received vcs not getting displayed on reopen of app * feat(inji-344): fix id not shown in revoke component --------- Co-authored-by: Sri Kanth Kola --- App.tsx | 40 +-- components/EditableListItem.tsx | 26 +- components/KebabPopUp.tsx | 12 +- components/KebabPopUpController.tsx | 4 +- components/LanguageSelector.tsx | 2 +- components/VcItem.tsx | 15 +- components/VidItem.tsx | 5 +- components/ui/Button.tsx | 10 +- components/ui/Layout.tsx | 22 +- components/ui/Text.tsx | 8 +- i18n.ts | 16 +- ios/Inji.xcodeproj/project.pbxproj | 1 + ios/Podfile.lock | 6 +- machines/QrLoginMachine.ts | 102 +++--- machines/app.ts | 106 +++--- machines/app.typegen.ts | 20 +- machines/auth.ts | 52 +-- machines/bleShare/request/requestMachine.ts | 33 +- machines/bleShare/scan/scanMachine.ts | 7 +- machines/revoke.ts | 41 ++- machines/settings.ts | 46 +-- machines/store.ts | 163 ++++----- machines/vc.ts | 163 +++++---- machines/vcItem.ts | 330 ++++++++---------- metro.config.js | 6 +- screens/Home/MyVcs/AddVcModalMachine.ts | 83 ++--- .../Home/MyVcs/AddVcModalMachine.typegen.ts | 22 +- screens/Home/MyVcs/HistoryTab.tsx | 25 +- screens/Home/MyVcs/IdInputModal.tsx | 32 +- screens/Home/MyVcs/WalletBindingController.ts | 6 +- screens/Home/MyVcsTab.tsx | 50 ++- screens/Home/MyVcsTabController.ts | 4 +- screens/Home/MyVcsTabMachine.ts | 7 +- screens/Home/ReceivedVcsTab.tsx | 36 +- screens/Home/ReceivedVcsTabController.ts | 4 +- screens/Home/ReceivedVcsTabMachine.ts | 2 +- screens/Profile/ProfileScreen.tsx | 40 +-- screens/Profile/ProfileScreenController.ts | 40 +-- screens/QrLogin/MyBindedVcs.tsx | 30 +- screens/QrLogin/QrLoginController.ts | 4 +- screens/Scan/ScanScreenController.ts | 9 +- screens/Scan/SelectVcOverlay.tsx | 6 +- screens/Scan/SelectVcOverlayController.ts | 3 +- screens/Scan/SendVcScreen.tsx | 10 +- screens/Scan/SendVcScreenController.ts | 4 +- screens/Settings/AboutInji.tsx | 3 +- screens/Settings/AppMetaData.tsx | 20 +- screens/Settings/ReceivedCardsModal.tsx | 8 +- screens/Settings/Revoke.tsx | 68 ++-- screens/Settings/RevokeController.tsx | 35 +- screens/Settings/SettingScreen.tsx | 2 +- screens/Settings/SettingScreenController.ts | 4 +- screens/SetupLanguageScreen.tsx | 2 +- shared/GlobalVariables.ts | 8 +- shared/VCMetadata.ts | 55 +++ shared/commonUtil.ts | 8 +- shared/constants.ts | 26 +- shared/javascript.ts | 14 + shared/request.ts | 14 +- shared/storage.ts | 31 +- types/vc.ts | 23 +- 61 files changed, 1006 insertions(+), 968 deletions(-) create mode 100644 shared/VCMetadata.ts create mode 100644 shared/javascript.ts diff --git a/App.tsx b/App.tsx index 57ec58b8..7962a7dc 100644 --- a/App.tsx +++ b/App.tsx @@ -1,29 +1,29 @@ -import React, { useContext, useEffect } from 'react'; +import React, {useContext, useEffect} from 'react'; import AppLoading from 'expo-app-loading'; -import { AppLayout } from './screens/AppLayout'; -import { useFont } from './shared/hooks/useFont'; -import { GlobalContextProvider } from './components/GlobalContextProvider'; -import { GlobalContext } from './shared/GlobalContext'; -import { useSelector } from '@xstate/react'; -import { useTranslation } from 'react-i18next'; +import {AppLayout} from './screens/AppLayout'; +import {useFont} from './shared/hooks/useFont'; +import {GlobalContextProvider} from './components/GlobalContextProvider'; +import {GlobalContext} from './shared/GlobalContext'; +import {useSelector} from '@xstate/react'; +import {useTranslation} from 'react-i18next'; import { selectIsDecryptError, selectIsKeyInvalidateError, selectIsReadError, selectIsReady, } from './machines/app'; -import { DualMessageOverlay } from './components/DualMessageOverlay'; -import { useApp } from './screens/AppController'; -import { Alert } from 'react-native'; +import {DualMessageOverlay} from './components/DualMessageOverlay'; +import {useApp} from './screens/AppController'; +import {Alert} from 'react-native'; import { getAppInfoData, getTelemetryConfigData, initializeTelemetry, sendAppInfoEvent, } from './shared/telemetry/TelemetryUtils'; -import { ErrorMessageOverlay } from './components/MessageOverlay'; +import {ErrorMessageOverlay} from './components/MessageOverlay'; import SecureKeystore from 'react-native-secure-keystore'; -import { isCustomSecureKeystore } from './shared/cryptoutil/cryptoUtil'; +import {isCustomSecureKeystore} from './shared/cryptoutil/cryptoUtil'; import i18n from './i18n'; // kludge: this is a bad practice but has been done temporarily to surface @@ -48,10 +48,10 @@ function configureTelemetry() { } const AppLayoutWrapper: React.FC = () => { - const { appService } = useContext(GlobalContext); + const {appService} = useContext(GlobalContext); const isDecryptError = useSelector(appService, selectIsDecryptError); const controller = useApp(); - const { t } = useTranslation('WelcomeScreen'); + const {t} = useTranslation('WelcomeScreen'); if (isDecryptError) { DecryptErrorAlert(controller, t); } @@ -60,14 +60,14 @@ const AppLayoutWrapper: React.FC = () => { }; const AppLoadingWrapper: React.FC = () => { - const { appService } = useContext(GlobalContext); + const {appService} = useContext(GlobalContext); const isReadError = useSelector(appService, selectIsReadError); const isKeyInvalidateError = useSelector( appService, - selectIsKeyInvalidateError + selectIsKeyInvalidateError, ); const controller = useApp(); - const { t } = useTranslation('WelcomeScreen'); + const {t} = useTranslation('WelcomeScreen'); return ( <> @@ -93,16 +93,16 @@ const AppLoadingWrapper: React.FC = () => { }; const AppInitialization: React.FC = () => { - const { appService } = useContext(GlobalContext); + const {appService} = useContext(GlobalContext); const isReady = useSelector(appService, selectIsReady); const hasFontsLoaded = useFont(); - const { t } = useTranslation('common'); + const {t} = useTranslation('common'); useEffect(() => { if (isCustomSecureKeystore()) { SecureKeystore.updatePopup( t('biometricPopup.title'), - t('biometricPopup.description') + t('biometricPopup.description'), ); } }, [i18n.language]); diff --git a/components/EditableListItem.tsx b/components/EditableListItem.tsx index ce8239d8..847b8a30 100644 --- a/components/EditableListItem.tsx +++ b/components/EditableListItem.tsx @@ -1,12 +1,12 @@ -import React, { useEffect, useState } from 'react'; -import { Dimensions, I18nManager } from 'react-native'; -import { Icon, ListItem, Overlay, Input } from 'react-native-elements'; -import { Text, Column, Row, Button } from './ui'; -import { Theme } from './ui/styleUtils'; -import { useTranslation } from 'react-i18next'; +import React, {useEffect, useState} from 'react'; +import {Dimensions, I18nManager} from 'react-native'; +import {Icon, ListItem, Overlay, Input} from 'react-native-elements'; +import {Text, Column, Row, Button} from './ui'; +import {Theme} from './ui/styleUtils'; +import {useTranslation} from 'react-i18next'; -export const EditableListItem: React.FC = (props) => { - const { t } = useTranslation('common'); +export const EditableListItem: React.FC = props => { + const {t} = useTranslation('common'); const [isEditing, setIsEditing] = useState(false); const [items, setItems] = useState(props.items); const [overlayOpened, setOverlayOpened] = useState(true); @@ -18,9 +18,9 @@ export const EditableListItem: React.FC = (props) => { }, [props.response]); function updateItems(label: string, value: string) { - const updatedItems = items.map((item) => { + const updatedItems = items.map(item => { if (item.label === label) { - return { ...item, value: value }; + return {...item, value: value}; } return item; }); @@ -50,18 +50,18 @@ export const EditableListItem: React.FC = (props) => { color={Theme.Colors.profileLanguageValue} /> {props.items.map((item: ListItemProps, index) => { return ( - {t('editLabel', { label: item.label })} + {t('editLabel', {label: item.label})} updateItems(item.label, value)} + onChangeText={value => updateItems(item.label, value)} selectionColor={Theme.Colors.Cursor} inputStyle={{ textAlign: I18nManager.isRTL ? 'right' : 'left', diff --git a/components/KebabPopUp.tsx b/components/KebabPopUp.tsx index 01297704..51e797c5 100644 --- a/components/KebabPopUp.tsx +++ b/components/KebabPopUp.tsx @@ -1,4 +1,3 @@ -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'; @@ -11,6 +10,7 @@ import {useTranslation} from 'react-i18next'; import {HistoryTab} from '../screens/Home/MyVcs/HistoryTab'; import {RemoveVcWarningOverlay} from '../screens/Home/MyVcs/RemoveVcWarningOverlay'; import {ScrollView} from 'react-native-gesture-handler'; +import {VCMetadata} from '../shared/VCMetadata'; import testIDProps from '../shared/commonUtil'; export const KebabPopUp: React.FC = props => { @@ -46,9 +46,7 @@ export const KebabPopUp: React.FC = props => { - {props.vcKey.split(':')[4] == 'true' - ? t('unPinCard') - : t('pinCard')} + {props.vcMetadata.isPinned ? t('unPinCard') : t('pinCard')} @@ -65,13 +63,13 @@ export const KebabPopUp: React.FC = props => { testID="viewActivityLog" service={props.service} label={t('viewActivityLog')} - vcKey={props.vcKey} + vcMetadata={props.vcMetadata} /> - controller.REMOVE(props.vcKey)}> + controller.REMOVE(props.vcMetadata)}> {t('removeFromWallet')} @@ -94,7 +92,7 @@ export const KebabPopUp: React.FC = props => { export interface KebabPopUpProps { iconName: string; iconType?: string; - vcKey: string; + vcMetadata: VCMetadata; isVisible: boolean; onDismiss: () => void; service: ActorRefFrom; diff --git a/components/KebabPopUpController.tsx b/components/KebabPopUpController.tsx index 29241534..030699ad 100644 --- a/components/KebabPopUpController.tsx +++ b/components/KebabPopUpController.tsx @@ -18,6 +18,7 @@ import { import { selectActivities } from '../machines/activityLog'; import { GlobalContext } from '../shared/GlobalContext'; import { useContext } from 'react'; +import { VCMetadata } from '../shared/VCMetadata'; export function useKebabPopUp(props) { const service = props.service as ActorRefFrom; @@ -26,7 +27,8 @@ export function useKebabPopUp(props) { const ADD_WALLET_BINDING_ID = () => service.send(VcItemEvents.ADD_WALLET_BINDING_ID()); const CONFIRM = () => service.send(VcItemEvents.CONFIRM()); - const REMOVE = (vcKey: string) => service.send(VcItemEvents.REMOVE(vcKey)); + const REMOVE = (vcMetadata: VCMetadata) => + service.send(VcItemEvents.REMOVE(vcMetadata)); const DISMISS = () => service.send(VcItemEvents.DISMISS()); const CANCEL = () => service.send(VcItemEvents.CANCEL()); const SHOW_ACTIVITY = () => service.send(VcItemEvents.SHOW_ACTIVITY()); diff --git a/components/LanguageSelector.tsx b/components/LanguageSelector.tsx index ce3f5249..e975bcfc 100644 --- a/components/LanguageSelector.tsx +++ b/components/LanguageSelector.tsx @@ -6,7 +6,7 @@ import Storage from '../shared/storage'; import {useTranslation} from 'react-i18next'; import i18next from 'i18next'; import RNRestart from 'react-native-restart'; -import { __SelectedLanguage } from '../shared/GlobalVariables'; +import {__SelectedLanguage} from '../shared/GlobalVariables'; export const LanguageSelector: React.FC = props => { const {i18n} = useTranslation(); diff --git a/components/VcItem.tsx b/components/VcItem.tsx index 6fc53cab..2877c5fe 100644 --- a/components/VcItem.tsx +++ b/components/VcItem.tsx @@ -1,4 +1,4 @@ -import React, {useContext, useRef} from 'react'; +import React, {useContext, useEffect, useRef} from 'react'; import {useInterpret, useSelector} from '@xstate/react'; import {Pressable} from 'react-native'; import {ActorRefFrom} from 'xstate'; @@ -22,18 +22,23 @@ import {VcItemActivationStatus} from './VcItemActivationStatus'; import {Row} from './ui'; import {KebabPopUp} from './KebabPopUp'; import {logState} from '../machines/app'; +import {VCMetadata} from '../shared/VCMetadata'; export const VcItem: React.FC = props => { const {appService} = useContext(GlobalContext); const machine = useRef( createVcItemMachine( appService.getSnapshot().context.serviceRefs, - props.vcKey, + props.vcMetadata, ), ); const service = useInterpret(machine.current, {devTools: __DEV__}); - service.subscribe(logState); + + useEffect(() => { + service.subscribe(logState); + }, [service]); + const context = useSelector(service, selectContext); const verifiableCredential = useSelector(service, selectVerifiableCredential); const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId); @@ -82,7 +87,7 @@ export const VcItem: React.FC = props => { )} = props => { }; interface VcItemProps { - vcKey: string; + vcMetadata: VCMetadata; margin?: string; selectable?: boolean; selected?: boolean; diff --git a/components/VidItem.tsx b/components/VidItem.tsx index d7772d2b..55bd1f5b 100644 --- a/components/VidItem.tsx +++ b/components/VidItem.tsx @@ -16,13 +16,14 @@ import { Theme } from './ui/styleUtils'; import { RotatingIcon } from './RotatingIcon'; import { GlobalContext } from '../shared/GlobalContext'; import { getLocalizedField } from '../i18n'; +import { VCMetadata } from '../shared/VCMetadata'; export const VidItem: React.FC = (props) => { const { appService } = useContext(GlobalContext); const machine = useRef( createVcItemMachine( appService.getSnapshot().context.serviceRefs, - props.vcKey + props.vcMetadata ) ); const service = useInterpret(machine.current); @@ -99,7 +100,7 @@ export const VidItem: React.FC = (props) => { }; interface VcItemProps { - vcKey: string; + vcMetadata: VCMetadata; margin?: string; selectable?: boolean; selected?: boolean; diff --git a/components/ui/Button.tsx b/components/ui/Button.tsx index 3aec3dcd..6a944454 100644 --- a/components/ui/Button.tsx +++ b/components/ui/Button.tsx @@ -3,17 +3,17 @@ import { Button as RNEButton, ButtonProps as RNEButtonProps, } from 'react-native-elements'; -import { GestureResponderEvent, StyleProp, ViewStyle } from 'react-native'; -import { Text } from './Text'; -import { Theme, Spacing } from './styleUtils'; +import {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; +import {Text} from './Text'; +import {Theme, Spacing} from './styleUtils'; import testIDProps from '../../shared/commonUtil'; -export const Button: React.FC = (props) => { +export const Button: React.FC = props => { const type = props.type || 'solid' || 'radius' || 'gradient'; const buttonStyle: StyleProp = [ props.fill ? Theme.ButtonStyles.fill : null, Theme.ButtonStyles[type], - { width: props.width ?? '100%' }, + {width: props.width ?? '100%'}, ]; const containerStyle: StyleProp = [ !(type === 'gradient') ? Theme.ButtonStyles.container : null, diff --git a/components/ui/Layout.tsx b/components/ui/Layout.tsx index 3806ae83..970cd54a 100644 --- a/components/ui/Layout.tsx +++ b/components/ui/Layout.tsx @@ -9,13 +9,13 @@ import { RefreshControlProps, SafeAreaView, } from 'react-native'; -import { Theme, ElevationLevel, Spacing } from './styleUtils'; +import {Theme, ElevationLevel, Spacing} from './styleUtils'; import testIDProps from '../../shared/commonUtil'; function createLayout( direction: FlexStyle['flexDirection'], mainAlign?: FlexStyle['justifyContent'], - crossAlign?: FlexStyle['alignItems'] + crossAlign?: FlexStyle['alignItems'], ) { const layoutStyles = StyleSheet.create({ base: { @@ -28,21 +28,21 @@ function createLayout( }, }); - const Layout: React.FC = (props) => { + const Layout: React.FC = props => { const styles: StyleProp = [ layoutStyles.base, props.fill ? layoutStyles.fill : null, props.padding ? Theme.spacing('padding', props.padding) : null, props.margin ? Theme.spacing('margin', props.margin) : null, - props.backgroundColor ? { backgroundColor: props.backgroundColor } : null, - props.width ? { width: props.width } : null, - props.height ? { height: props.height } : null, - props.align ? { justifyContent: props.align } : null, - props.crossAlign ? { alignItems: props.crossAlign } : null, + props.backgroundColor ? {backgroundColor: props.backgroundColor} : null, + props.width ? {width: props.width} : null, + props.height ? {height: props.height} : null, + props.align ? {justifyContent: props.align} : null, + props.crossAlign ? {alignItems: props.crossAlign} : null, props.elevation ? Theme.elevation(props.elevation) : null, props.style ? props.style : null, - props.pY ? { paddingVertical: props.pY } : null, - props.pX ? { paddingHorizontal: props.pX } : null, + props.pY ? {paddingVertical: props.pY} : null, + props.pX ? {paddingHorizontal: props.pX} : null, ]; const ViewType = props.safe ? SafeAreaView : View; @@ -73,7 +73,7 @@ export const Centered = createLayout('column', 'center', 'center'); export const HorizontallyCentered = createLayout( 'column', 'flex-start', - 'center' + 'center', ); interface LayoutProps { diff --git a/components/ui/Text.tsx b/components/ui/Text.tsx index e4ce39ef..faaed1f1 100644 --- a/components/ui/Text.tsx +++ b/components/ui/Text.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { StyleProp, TextStyle, Text as RNText } from 'react-native'; -import { Theme, Spacing } from './styleUtils'; +import {StyleProp, TextStyle, Text as RNText} from 'react-native'; +import {Theme, Spacing} from './styleUtils'; import testIDProps from '../../shared/commonUtil'; export const Text: React.FC = (props: TextProps) => { @@ -9,8 +9,8 @@ export const Text: React.FC = (props: TextProps) => { const textStyles: StyleProp = [ Theme.TextStyles.base, Theme.TextStyles[weight], - props.color ? { color: props.color } : null, - props.align ? { textAlign: props.align } : { textAlign: 'left' }, + props.color ? {color: props.color} : null, + props.align ? {textAlign: props.align} : {textAlign: 'left'}, props.margin ? Theme.spacing('margin', props.margin) : null, props.size ? Theme.TextStyles[props.size] : null, props.style ? props.style : null, diff --git a/i18n.ts b/i18n.ts index c3b18413..b12884a1 100644 --- a/i18n.ts +++ b/i18n.ts @@ -1,6 +1,6 @@ import i18next from 'i18next'; import * as Localization from 'expo-localization'; -import { initReactI18next } from 'react-i18next'; +import {initReactI18next} from 'react-i18next'; import en from './locales/en.json'; import fil from './locales/fil.json'; @@ -10,12 +10,12 @@ import kn from './locales/kan.json'; import ta from './locales/tam.json'; import Storage from './shared/storage'; -import { iso6393To1 } from 'iso-639-3'; -import { LocalizedField } from './types/vc'; +import {iso6393To1} from 'iso-639-3'; +import {LocalizedField} from './types/vc'; -import { APPLICATION_LANGUAGE } from 'react-native-dotenv'; +import {APPLICATION_LANGUAGE} from 'react-native-dotenv'; -const resources = { en, fil, ar, hi, kn, ta }; +const resources = {en, fil, ar, hi, kn, ta}; const locale = Localization.locale; const languageCodeMap = {}; @@ -59,7 +59,7 @@ export function getLanguageCode(code: string) { export function getVCDetailsForCurrentLanguage(locales) { const currentLanguage = i18next.language; const vcDetailsForCurrentLanguage = locales.filter( - (obj) => obj.language === languageCodeMap[currentLanguage] + obj => obj.language === languageCodeMap[currentLanguage], ); return vcDetailsForCurrentLanguage[0]?.value ? vcDetailsForCurrentLanguage[0].value @@ -70,13 +70,13 @@ export function getVCDetailsForCurrentLanguage(locales) { // The response received from the server is three letter language code and the value in the inji code base is two letter language code. Hence the conversion is done. function getThreeLetterLanguageCode(twoLetterLanguageCode) { return Object.keys(iso6393To1).find( - (key) => iso6393To1[key] === twoLetterLanguageCode + key => iso6393To1[key] === twoLetterLanguageCode, ); } function populateLanguageCodeMap() { const supportedLanguages = Object.keys(SUPPORTED_LANGUAGES); - supportedLanguages.forEach((languageCode) => { + supportedLanguages.forEach(languageCode => { let threeLetterLanguageCode = languageCode; if (isTwoLetterLanguageCode(languageCode)) { diff --git a/ios/Inji.xcodeproj/project.pbxproj b/ios/Inji.xcodeproj/project.pbxproj index 2487e1dc..c422dfdc 100644 --- a/ios/Inji.xcodeproj/project.pbxproj +++ b/ios/Inji.xcodeproj/project.pbxproj @@ -281,6 +281,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GNSSharedResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Assets.car", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 38527e0c..6ba07d27 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -762,7 +762,7 @@ SPEC CHECKSUMS: BVLinearGradient: 916632041121a658c704df89d99f04acb038de0f CatCrypto: a477899b6be4954e75be4897e732da098cc0a5a8 CrcSwift: f85dea6b41dddb5f98bb3743fd777ce58b77bc2e - DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 + DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de EASClient: 950674e1098ebc09c4c2cf064a61e42e84d9d4c6 EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903 EXBarCodeScanner: 8e23fae8d267dbef9f04817833a494200f1fce35 @@ -785,7 +785,7 @@ SPEC CHECKSUMS: FBLazyVector: f637f31eacba90d4fdeff3fa41608b8f361c173b FBReactNativeSpec: 0d9a4f4de7ab614c49e98c00aedfd3bfbda33d59 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62 GoogleInterchangeUtilities: d5bc4d88d5b661ab72f9d70c58d02ca8c27ad1f7 GoogleNetworkingUtilities: 3edd3a8161347494f2da60ea0deddc8a472d94cb GoogleSymbolUtilities: 631ee17048aa5e9ab133470d768ea997a5ef9b96 @@ -855,4 +855,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 01f58b130fa221dabb14b2d82d981ef24dcaba53 -COCOAPODS: 1.12.1 +COCOAPODS: 1.11.3 diff --git a/machines/QrLoginMachine.ts b/machines/QrLoginMachine.ts index 2d8de3b1..b65b63b7 100644 --- a/machines/QrLoginMachine.ts +++ b/machines/QrLoginMachine.ts @@ -6,29 +6,27 @@ import { sendParent, StateFrom, } from 'xstate'; -import { createModel } from 'xstate/lib/model'; -import { AppServices } from '../shared/GlobalContext'; -import { MY_VCS_STORE_KEY, ESIGNET_BASE_URL } from '../shared/constants'; -import { StoreEvents } from './store'; -import { linkTransactionResponse, VC } from '../types/vc'; -import { request } from '../shared/request'; -import { - getJwt, - isCustomSecureKeystore, -} from '../shared/cryptoutil/cryptoUtil'; +import {createModel} from 'xstate/lib/model'; +import {AppServices} from '../shared/GlobalContext'; +import {MY_VCS_STORE_KEY, ESIGNET_BASE_URL} from '../shared/constants'; +import {StoreEvents} from './store'; +import {linkTransactionResponse, VC} from '../types/vc'; +import {request} from '../shared/request'; +import {getJwt, isCustomSecureKeystore} from '../shared/cryptoutil/cryptoUtil'; import { getBindingCertificateConstant, getPrivateKey, } from '../shared/keystore/SecureKeystore'; import i18n from '../i18n'; -import { getEndData, sendEndEvent } from '../shared/telemetry/TelemetryUtils'; +import {parseMetadatas, VCMetadata} from '../shared/VCMetadata'; +import {getEndData, sendEndEvent} from '../shared/telemetry/TelemetryUtils'; const model = createModel( { serviceRefs: {} as AppServices, selectedVc: {} as VC, linkCode: '', - myVcs: [] as string[], + myVcs: [] as VCMetadata[], thumbprint: '', linkTransactionResponse: {} as linkTransactionResponse, authFactors: [], @@ -48,24 +46,24 @@ const model = createModel( }, { events: { - SELECT_VC: (vc: VC) => ({ vc }), - SCANNING_DONE: (params: string) => ({ params }), - STORE_RESPONSE: (response: unknown) => ({ response }), - STORE_ERROR: (error: Error) => ({ error }), + SELECT_VC: (vc: VC) => ({vc}), + SCANNING_DONE: (params: string) => ({params}), + STORE_RESPONSE: (response: unknown) => ({response}), + STORE_ERROR: (error: Error) => ({error}), TOGGLE_CONSENT_CLAIM: (enable: boolean, claim: string) => ({ enable, claim, }), DISMISS: () => ({}), CONFIRM: () => ({}), - GET: (value: string) => ({ value }), + GET: (value: string) => ({value}), VERIFY: () => ({}), CANCEL: () => ({}), FACE_VALID: () => ({}), FACE_INVALID: () => ({}), RETRY_VERIFICATION: () => ({}), }, - } + }, ); export const QrLoginEvents = model.events; @@ -235,7 +233,7 @@ export const qrLoginMachine = }, done: { type: 'final', - data: (context) => context, + data: context => context, }, }, }, @@ -247,22 +245,24 @@ export const qrLoginMachine = linkCode: (context, event) => event.value, }), + // TODO: loaded VCMetadatas are not used anywhere. remove? loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), { - to: (context) => context.serviceRefs.store, + to: context => context.serviceRefs.store, }), setMyVcs: model.assign({ - myVcs: (_context, event) => (event.response || []) as string[], + myVcs: (_context, event) => + parseMetadatas((event.response || []) as object[]), }), loadThumbprint: send( - (context) => + context => StoreEvents.GET( getBindingCertificateConstant( - context.selectedVc.walletBindingResponse?.walletBindingId - ) + context.selectedVc.walletBindingResponse?.walletBindingId, + ), ), - { to: (context) => context.serviceRefs.store } + {to: context => context.serviceRefs.store}, ), setThumbprint: assign({ thumbprint: (_context, event) => { @@ -279,7 +279,7 @@ export const qrLoginMachine = setSelectedVc: assign({ selectedVc: (context, event) => { - return { ...event.vc }; + return {...event.vc}; }, }), @@ -289,29 +289,29 @@ export const qrLoginMachine = }), expandLinkTransResp: assign({ - authFactors: (context) => context.linkTransactionResponse.authFactors, + authFactors: context => context.linkTransactionResponse.authFactors, - authorizeScopes: (context) => + authorizeScopes: context => context.linkTransactionResponse.authorizeScopes, - clientName: (context) => context.linkTransactionResponse.clientName, + clientName: context => context.linkTransactionResponse.clientName, - configs: (context) => context.linkTransactionResponse.configs, + configs: context => context.linkTransactionResponse.configs, - essentialClaims: (context) => + essentialClaims: context => context.linkTransactionResponse.essentialClaims, - linkTransactionId: (context) => + linkTransactionId: context => context.linkTransactionResponse.linkTransactionId, - logoUrl: (context) => context.linkTransactionResponse.logoUrl, + logoUrl: context => context.linkTransactionResponse.logoUrl, - voluntaryClaims: (context) => + voluntaryClaims: context => context.linkTransactionResponse.voluntaryClaims, }), - setClaims: (context) => { - context.voluntaryClaims.map((claim) => { + setClaims: context => { + context.voluntaryClaims.map(claim => { context.isSharing[claim] = false; }); }, @@ -331,10 +331,10 @@ export const qrLoginMachine = } else { context.selectedVoluntaryClaims = context.selectedVoluntaryClaims.filter( - (eachClaim) => eachClaim !== event.claim + eachClaim => eachClaim !== event.claim, ); } - return { ...context.isSharing }; + return {...context.isSharing}; }, }), setLinkedTransactionId: assign({ @@ -342,7 +342,7 @@ export const qrLoginMachine = }), }, services: { - linkTransaction: async (context) => { + linkTransaction: async context => { const response = await request( 'POST', '/v1/esignet/linked-authorization/link-transaction', @@ -352,17 +352,17 @@ export const qrLoginMachine = linkCode: context.linkCode, }, }, - ESIGNET_BASE_URL + ESIGNET_BASE_URL, ); return response.response; }, - sendAuthenticate: async (context) => { + sendAuthenticate: async context => { let privateKey; if (!isCustomSecureKeystore()) { privateKey = await getPrivateKey( - context.selectedVc.walletBindingResponse?.walletBindingId + context.selectedVc.walletBindingResponse?.walletBindingId, ); } @@ -370,7 +370,7 @@ export const qrLoginMachine = var jwt = await getJwt( privateKey, context.selectedVc.id, - context.thumbprint + context.thumbprint, ); const response = await request( @@ -390,23 +390,23 @@ export const qrLoginMachine = ], }, }, - ESIGNET_BASE_URL + ESIGNET_BASE_URL, ); return response.response.linkedTransactionId; }, - sendConsent: async (context) => { + sendConsent: async context => { let privateKey; if (!isCustomSecureKeystore()) { privateKey = await getPrivateKey( - context.selectedVc.walletBindingResponse?.walletBindingId + context.selectedVc.walletBindingResponse?.walletBindingId, ); } const jwt = await getJwt( privateKey, context.selectedVc.id, - context.thumbprint + context.thumbprint, ); const response = await request( @@ -426,7 +426,7 @@ export const qrLoginMachine = ], }, }, - ESIGNET_BASE_URL + ESIGNET_BASE_URL, ); var linkedTrnId = response.response.linkedTransactionId; @@ -438,17 +438,17 @@ export const qrLoginMachine = request: { linkedTransactionId: linkedTrnId, acceptedClaims: context.essentialClaims.concat( - context.selectedVoluntaryClaims + context.selectedVoluntaryClaims, ), permittedAuthorizeScopes: context.authorizeScopes, }, }, - ESIGNET_BASE_URL + ESIGNET_BASE_URL, ); console.log(resp.response.linkedTransactionId); }, }, - } + }, ); export function createQrLoginMachine(serviceRefs: AppServices) { diff --git a/machines/app.ts b/machines/app.ts index 5c329a91..fc86c93a 100644 --- a/machines/app.ts +++ b/machines/app.ts @@ -1,26 +1,26 @@ -import NetInfo, { NetInfoStateType } from '@react-native-community/netinfo'; -import { AppState, AppStateStatus, Platform } from 'react-native'; +import NetInfo, {NetInfoStateType} from '@react-native-community/netinfo'; +import {AppState, AppStateStatus, Platform} from 'react-native'; import { getDeviceId, getDeviceName, getDeviceNameSync, } from 'react-native-device-info'; -import { EventFrom, spawn, StateFrom, send, assign, AnyState } from 'xstate'; -import { createModel } from 'xstate/lib/model'; -import { authMachine, createAuthMachine } from './auth'; -import { createSettingsMachine, settingsMachine } from './settings'; -import { StoreEvents, storeMachine } from './store'; -import { createVcMachine, vcMachine } from './vc'; -import { createActivityLogMachine, activityLogMachine } from './activityLog'; +import {EventFrom, spawn, StateFrom, send, assign, AnyState} from 'xstate'; +import {createModel} from 'xstate/lib/model'; +import {authMachine, createAuthMachine} from './auth'; +import {createSettingsMachine, settingsMachine} from './settings'; +import {StoreEvents, storeMachine} from './store'; +import {createVcMachine, vcMachine} from './vc'; +import {createActivityLogMachine, activityLogMachine} from './activityLog'; import { createRequestMachine, requestMachine, } from './bleShare/request/requestMachine'; -import { createScanMachine, scanMachine } from './bleShare/scan/scanMachine'; -import { createRevokeMachine, revokeVidsMachine } from './revoke'; -import { pure, respond } from 'xstate/lib/actions'; -import { AppServices } from '../shared/GlobalContext'; -import { request } from '../shared/request'; +import {createScanMachine, scanMachine} from './bleShare/scan/scanMachine'; +import {createRevokeMachine, revokeVidsMachine} from './revoke'; +import {pure, respond} from 'xstate/lib/actions'; +import {AppServices} from '../shared/GlobalContext'; +import {request} from '../shared/request'; import { changeCrendetialRegistry, SETTINGS_STORE_KEY, @@ -47,15 +47,15 @@ const model = createModel( DECRYPT_ERROR_DISMISS: () => ({}), KEY_INVALIDATE_ERROR: () => ({}), OFFLINE: () => ({}), - ONLINE: (networkType: NetInfoStateType) => ({ networkType }), + ONLINE: (networkType: NetInfoStateType) => ({networkType}), REQUEST_DEVICE_INFO: () => ({}), - READY: (data?: unknown) => ({ data }), - APP_INFO_RECEIVED: (info: AppInfo) => ({ info }), - BACKEND_INFO_RECEIVED: (info: BackendInfo) => ({ info }), - STORE_RESPONSE: (response: unknown) => ({ response }), + READY: (data?: unknown) => ({data}), + APP_INFO_RECEIVED: (info: AppInfo) => ({info}), + BACKEND_INFO_RECEIVED: (info: BackendInfo) => ({info}), + STORE_RESPONSE: (response: unknown) => ({response}), RESET_KEY_INVALIDATE_ERROR_DISMISS: () => ({}), }, - } + }, ); export const APP_EVENTS = model.events; @@ -208,9 +208,9 @@ export const appMachine = model.createMachine( { actions: { forwardToServices: pure((context, event) => - Object.values(context.serviceRefs).map((serviceRef) => - send({ ...event, type: `APP_${event.type}` }, { to: serviceRef }) - ) + Object.values(context.serviceRefs).map(serviceRef => + send({...event, type: `APP_${event.type}`}, {to: serviceRef}), + ), ), setIsReadError: assign({ isReadError: true, @@ -237,7 +237,7 @@ export const appMachine = model.createMachine( isKeyInvalidateError: false, }), - requestDeviceInfo: respond((context) => ({ + requestDeviceInfo: respond(context => ({ type: 'RECEIVE_DEVICE_INFO', info: { ...context.info, @@ -246,63 +246,63 @@ export const appMachine = model.createMachine( })), spawnStoreActor: assign({ - serviceRefs: (context) => ({ + serviceRefs: context => ({ ...context.serviceRefs, store: spawn(storeMachine, storeMachine.id), }), }), - logStoreEvents: (context) => { + logStoreEvents: context => { if (__DEV__) { context.serviceRefs.store.subscribe(logState); } }, spawnServiceActors: model.assign({ - serviceRefs: (context) => { + serviceRefs: context => { const serviceRefs = { ...context.serviceRefs, }; serviceRefs.auth = spawn( createAuthMachine(serviceRefs), - authMachine.id + authMachine.id, ); serviceRefs.vc = spawn(createVcMachine(serviceRefs), vcMachine.id); serviceRefs.settings = spawn( createSettingsMachine(serviceRefs), - settingsMachine.id + settingsMachine.id, ); serviceRefs.activityLog = spawn( createActivityLogMachine(serviceRefs), - activityLogMachine.id + activityLogMachine.id, ); serviceRefs.scan = spawn( createScanMachine(serviceRefs), - scanMachine.id + scanMachine.id, ); if (Platform.OS === 'android') { serviceRefs.request = spawn( createRequestMachine(serviceRefs), - requestMachine.id + requestMachine.id, ); } serviceRefs.revoke = spawn( createRevokeMachine(serviceRefs), - revokeVidsMachine.id + revokeVidsMachine.id, ); return serviceRefs; }, }), - logServiceEvents: (context) => { + logServiceEvents: context => { if (__DEV__) { context.serviceRefs.auth.subscribe(logState); context.serviceRefs.vc.subscribe(logState); @@ -329,19 +329,19 @@ export const appMachine = model.createMachine( loadCredentialRegistryHostFromStorage: send( StoreEvents.GET(SETTINGS_STORE_KEY), { - to: (context) => context.serviceRefs.store, - } + to: context => context.serviceRefs.store, + }, ), loadEsignetHostFromStorage: send(StoreEvents.GET(SETTINGS_STORE_KEY), { - to: (context) => context.serviceRefs.store, + to: context => context.serviceRefs.store, }), loadCredentialRegistryInConstants: (_context, event) => { changeCrendetialRegistry( !event.response?.credentialRegistry ? MIMOTO_BASE_URL - : event.response?.credentialRegistry + : event.response?.credentialRegistry, ); }, @@ -349,13 +349,13 @@ export const appMachine = model.createMachine( changeEsignetUrl( !event.response?.esignetHostUrl ? ESIGNET_BASE_URL - : event.response?.esignetHostUrl + : event.response?.esignetHostUrl, ); }, }, services: { - getAppInfo: () => async (callback) => { + getAppInfo: () => async callback => { const appInfo = { deviceId: getDeviceId(), deviceName: await getDeviceName(), @@ -363,7 +363,7 @@ export const appMachine = model.createMachine( callback(model.events.APP_INFO_RECEIVED(appInfo)); }, - getBackendInfo: () => async (callback) => { + getBackendInfo: () => async callback => { let backendInfo = { application: { name: '', @@ -380,21 +380,21 @@ export const appMachine = model.createMachine( } }, - checkFocusState: () => (callback) => { + checkFocusState: () => callback => { const changeHandler = (newState: AppStateStatus) => { switch (newState) { case 'background': case 'inactive': - callback({ type: 'INACTIVE' }); + callback({type: 'INACTIVE'}); break; case 'active': - callback({ type: 'ACTIVE' }); + callback({type: 'ACTIVE'}); break; } }; - const blurHandler = () => callback({ type: 'INACTIVE' }); - const focusHandler = () => callback({ type: 'ACTIVE' }); + const blurHandler = () => callback({type: 'INACTIVE'}); + const focusHandler = () => callback({type: 'ACTIVE'}); AppState.addEventListener('change', changeHandler); @@ -414,17 +414,17 @@ export const appMachine = model.createMachine( }; }, - checkNetworkState: () => (callback) => { - return NetInfo.addEventListener((state) => { + checkNetworkState: () => callback => { + return NetInfo.addEventListener(state => { if (state.isConnected) { - callback({ type: 'ONLINE', networkType: state.type }); + callback({type: 'ONLINE', networkType: state.type}); } else { - callback({ type: 'OFFLINE' }); + callback({type: 'OFFLINE'}); } }); }, }, - } + }, ); interface AppInfo { @@ -477,7 +477,7 @@ export function logState(state: AnyState) { } return value; }, - 2 + 2, ); console.log( `[${getDeviceNameSync()}] ${state.machine.id}: ${ @@ -485,7 +485,7 @@ export function logState(state: AnyState) { } -> ${state.toStrings().pop()}\n${ data.length > 300 ? data.slice(0, 300) + '...' : data } - ` + `, ); } diff --git a/machines/app.typegen.ts b/machines/app.typegen.ts index ff3cf418..ce1e9c6f 100644 --- a/machines/app.typegen.ts +++ b/machines/app.typegen.ts @@ -2,22 +2,22 @@ export interface Typegen0 { '@@xstate/typegen': true; - 'internalEvents': { - 'xstate.init': { type: 'xstate.init' }; + internalEvents: { + 'xstate.init': {type: 'xstate.init'}; }; - 'invokeSrcNameMap': { + invokeSrcNameMap: { checkFocusState: 'done.invoke.app.ready.focus:invocation[0]'; checkNetworkState: 'done.invoke.app.ready.network:invocation[0]'; getAppInfo: 'done.invoke.app.init.info:invocation[0]'; getBackendInfo: 'done.invoke.app.init.devinfo:invocation[0]'; }; - 'missingImplementations': { + missingImplementations: { actions: never; delays: never; guards: never; services: never; }; - 'eventsCausingActions': { + eventsCausingActions: { forwardToServices: 'ACTIVE' | 'INACTIVE' | 'OFFLINE' | 'ONLINE'; loadCredentialRegistryHostFromStorage: 'READY'; loadCredentialRegistryInConstants: 'STORE_RESPONSE'; @@ -41,15 +41,15 @@ export interface Typegen0 { unsetIsReadError: 'READY'; updateKeyInvalidateError: 'ERROR' | 'KEY_INVALIDATE_ERROR'; }; - 'eventsCausingDelays': {}; - 'eventsCausingGuards': {}; - 'eventsCausingServices': { + eventsCausingDelays: {}; + eventsCausingGuards: {}; + eventsCausingServices: { checkFocusState: 'BACKEND_INFO_RECEIVED'; checkNetworkState: 'BACKEND_INFO_RECEIVED'; getAppInfo: 'STORE_RESPONSE'; getBackendInfo: 'APP_INFO_RECEIVED'; }; - 'matchesStates': + matchesStates: | 'init' | 'init.credentialRegistry' | 'init.devinfo' @@ -76,5 +76,5 @@ export interface Typegen0 { network?: 'checking' | 'offline' | 'online'; }; }; - 'tags': never; + tags: never; } diff --git a/machines/auth.ts b/machines/auth.ts index 3ed6db77..89d39aa9 100644 --- a/machines/auth.ts +++ b/machines/auth.ts @@ -1,12 +1,12 @@ -import { init } from 'mosip-inji-face-sdk'; -import { assign, ContextFrom, EventFrom, send, StateFrom } from 'xstate'; -import { createModel } from 'xstate/lib/model'; +import {init} from 'mosip-inji-face-sdk'; +import {assign, ContextFrom, EventFrom, send, StateFrom} from 'xstate'; +import {createModel} from 'xstate/lib/model'; import getAllConfigurations, { downloadModel, } from '../shared/commonprops/commonProps'; -import { AppServices } from '../shared/GlobalContext'; -import { StoreEvents, StoreResponseEvent } from './store'; -import { generateSecureRandom } from 'react-native-securerandom'; +import {AppServices} from '../shared/GlobalContext'; +import {StoreEvents, StoreResponseEvent} from './store'; +import {generateSecureRandom} from 'react-native-securerandom'; import binaryToBase64 from 'react-native/Libraries/Utilities/binaryToBase64'; const model = createModel( @@ -20,15 +20,15 @@ const model = createModel( }, { events: { - SETUP_PASSCODE: (passcode: string) => ({ passcode }), - SETUP_BIOMETRICS: (biometrics: string) => ({ biometrics }), + SETUP_PASSCODE: (passcode: string) => ({passcode}), + SETUP_BIOMETRICS: (biometrics: string) => ({biometrics}), LOGOUT: () => ({}), LOGIN: () => ({}), - STORE_RESPONSE: (response?: unknown) => ({ response }), + STORE_RESPONSE: (response?: unknown) => ({response}), SELECT: () => ({}), NEXT: () => ({}), }, - } + }, ); export const AuthEvents = model.events; @@ -56,7 +56,7 @@ export const authMachine = model.createMachine( target: 'checkingAuth', actions: ['setContext'], }, - { target: 'savingDefaults' }, + {target: 'savingDefaults'}, ], }, }, @@ -68,10 +68,10 @@ export const authMachine = model.createMachine( }, checkingAuth: { always: [ - { cond: 'hasLanguageset', target: 'languagesetup' }, - { cond: 'hasPasscodeSet', target: 'unauthorized' }, - { cond: 'hasBiometricSet', target: 'unauthorized' }, - { target: 'settingUp' }, + {cond: 'hasLanguageset', target: 'languagesetup'}, + {cond: 'hasPasscodeSet', target: 'unauthorized'}, + {cond: 'hasBiometricSet', target: 'unauthorized'}, + {target: 'settingUp'}, ], }, languagesetup: { @@ -131,19 +131,19 @@ export const authMachine = model.createMachine( { actions: { requestStoredContext: send(StoreEvents.GET('auth'), { - to: (context) => context.serviceRefs.store, + to: context => context.serviceRefs.store, }), storeContext: send( - (context) => { - const { serviceRefs, ...data } = context; + context => { + const {serviceRefs, ...data} = context; return StoreEvents.SET('auth', data); }, - { to: (context) => context.serviceRefs.store } + {to: context => context.serviceRefs.store}, ), setContext: model.assign((_, event) => { - const { serviceRefs, ...data } = event.response as ContextFrom< + const {serviceRefs, ...data} = event.response as ContextFrom< typeof model >; return data; @@ -158,7 +158,7 @@ export const authMachine = model.createMachine( }), setLanguage: assign({ - selectLanguage: (context) => true, + selectLanguage: context => true, }), setPasscodeSalt: assign({ @@ -172,7 +172,7 @@ export const authMachine = model.createMachine( downloadFaceSdkModel: () => () => { downloadModel(); }, - generatePasscodeSalt: () => async (context) => { + generatePasscodeSalt: () => async context => { const randomBytes = await generateSecureRandom(16); return binaryToBase64(randomBytes) as string; }, @@ -181,17 +181,17 @@ export const authMachine = model.createMachine( guards: { hasData: (_, event: StoreResponseEvent) => event.response != null, - hasPasscodeSet: (context) => { + hasPasscodeSet: context => { return context.passcode !== ''; }, - hasBiometricSet: (context) => { + hasBiometricSet: context => { return context.biometrics !== '' && context.passcode !== ''; }, - hasLanguageset: (context) => { + hasLanguageset: context => { return !context.selectLanguage; }, }, - } + }, ); export function createAuthMachine(serviceRefs: AppServices) { diff --git a/machines/bleShare/request/requestMachine.ts b/machines/bleShare/request/requestMachine.ts index d894ef1e..5649626e 100644 --- a/machines/bleShare/request/requestMachine.ts +++ b/machines/bleShare/request/requestMachine.ts @@ -14,10 +14,7 @@ import {getDeviceNameSync} from 'react-native-device-info'; import {StoreEvents} from '../../store'; import {VC} from '../../../types/vc'; import {AppServices} from '../../../shared/GlobalContext'; -import { - RECEIVED_VCS_STORE_KEY, - VC_ITEM_STORE_KEY, -} from '../../../shared/constants'; +import {RECEIVED_VCS_STORE_KEY} from '../../../shared/constants'; import {ActivityLogEvents, ActivityLogType} from '../../activityLog'; import {VcEvents} from '../../vc'; import {subscribe} from '../../../shared/openIdBLE/verifierEventHandler'; @@ -25,6 +22,7 @@ import {log} from 'xstate/lib/actions'; import {VerifierDataEvent} from 'react-native-tuvali/src/types/events'; import {BLEError} from '../types'; import Storage from '../../../shared/storage'; +import {VCMetadata} from '../../../shared/VCMetadata'; // import { verifyPresentation } from '../shared/vcjs/verifyPresentation'; const {verifier, EventTypes, VerificationStatus} = tuvali; @@ -67,7 +65,7 @@ const model = createModel( STORE_ERROR: (error: Error) => ({error}), RECEIVE_DEVICE_INFO: (info: DeviceInfo) => ({info}), RECEIVED_VCS_UPDATED: () => ({}), - VC_RESPONSE: (response: unknown) => ({response}), + VC_RESPONSE: (vcMetadatas: VCMetadata[]) => ({vcMetadatas}), GOTO_SETTINGS: () => ({}), APP_ACTIVE: () => ({}), FACE_VALID: () => ({}), @@ -584,13 +582,14 @@ export const requestMachine = context => StoreEvents.PREPEND( RECEIVED_VCS_STORE_KEY, - VC_ITEM_STORE_KEY(context.incomingVc), + VCMetadata.fromVC(context.incomingVc), ), {to: context => context.serviceRefs.store}, ), requestExistingVc: send( - context => StoreEvents.GET(VC_ITEM_STORE_KEY(context.incomingVc)), + context => + StoreEvents.GET(VCMetadata.fromVC(context.incomingVc).getVcKey()), {to: context => context.serviceRefs.store}, ), @@ -601,7 +600,10 @@ export const requestMachine = ...existing, reason: existing.reason.concat(context.incomingVc.reason), }; - return StoreEvents.SET(VC_ITEM_STORE_KEY(updated), updated); + return StoreEvents.SET( + VCMetadata.fromVC(updated).getVcKey(), + updated, + ); }, {to: context => context.serviceRefs.store}, ), @@ -609,7 +611,7 @@ export const requestMachine = storeVc: send( context => StoreEvents.SET( - VC_ITEM_STORE_KEY(context.incomingVc), + VCMetadata.fromVC(context.incomingVc).getVcKey(), context.incomingVc, ), {to: context => context.serviceRefs.store}, @@ -634,7 +636,7 @@ export const requestMachine = logReceived: send( context => ActivityLogEvents.LOG_ACTIVITY({ - _vcKey: VC_ITEM_STORE_KEY(context.incomingVc), + _vcKey: VCMetadata.fromVC(context.incomingVc).getVcKey(), type: context.receiveLogType, timestamp: Date.now(), deviceName: @@ -646,7 +648,7 @@ export const requestMachine = sendVcReceived: send( context => { - return VcEvents.VC_RECEIVED(VC_ITEM_STORE_KEY(context.incomingVc)); + return VcEvents.VC_RECEIVED(VCMetadata.fromVC(context.incomingVc)); }, {to: context => context.serviceRefs.vc}, ), @@ -805,9 +807,12 @@ export const requestMachine = guards: { hasExistingVc: (context, event) => { - const receivedVcs = event.response as string[]; - const vcKey = VC_ITEM_STORE_KEY(context.incomingVc); - return receivedVcs.includes(vcKey); + const receivedVcs = event.vcMetadatas; + const incomingVcMetadata = VCMetadata.fromVC(context.incomingVc); + return receivedVcs?.some( + vcMetadata => + vcMetadata.getVcKey() == incomingVcMetadata.getVcKey(), + ); }, isMinimumStorageLimitReached: (_context, event) => Boolean(event.data), diff --git a/machines/bleShare/scan/scanMachine.ts b/machines/bleShare/scan/scanMachine.ts index 724928b9..5d29b37f 100644 --- a/machines/bleShare/scan/scanMachine.ts +++ b/machines/bleShare/scan/scanMachine.ts @@ -17,7 +17,7 @@ import {getDeviceNameSync} from 'react-native-device-info'; import {VC, VerifiablePresentation} from '../../../types/vc'; import {AppServices} from '../../../shared/GlobalContext'; import {ActivityLogEvents, ActivityLogType} from '../../activityLog'; -import {MY_LOGIN_STORE_KEY, VC_ITEM_STORE_KEY} from '../../../shared/constants'; +import {MY_LOGIN_STORE_KEY} from '../../../shared/constants'; import {subscribe} from '../../../shared/openIdBLE/walletEventHandler'; import { check, @@ -39,6 +39,7 @@ import {WalletDataEvent} from 'react-native-tuvali/lib/typescript/types/events'; import {BLEError} from '../types'; import Storage from '../../../shared/storage'; import {logState} from '../../app'; +import {VCMetadata} from '../../../shared/VCMetadata'; import { getData, getEndData, @@ -794,7 +795,7 @@ export const scanMachine = logShared: send( context => ActivityLogEvents.LOG_ACTIVITY({ - _vcKey: VC_ITEM_STORE_KEY(context.selectedVc), + _vcKey: VCMetadata.fromVC(context.selectedVc).getVcKey(), type: context.selectedVc.shouldVerifyPresence ? 'VC_SHARED_WITH_VERIFICATION_CONSENT' : context.shareLogType, @@ -809,7 +810,7 @@ export const scanMachine = logFailedVerification: send( context => ActivityLogEvents.LOG_ACTIVITY({ - _vcKey: VC_ITEM_STORE_KEY(context.selectedVc), + _vcKey: VCMetadata.fromVC(context.selectedVc).getVcKey(), type: 'PRESENCE_VERIFICATION_FAILED', timestamp: Date.now(), deviceName: diff --git a/machines/revoke.ts b/machines/revoke.ts index 7d3d7543..b2a9c1b7 100644 --- a/machines/revoke.ts +++ b/machines/revoke.ts @@ -10,6 +10,7 @@ import { createModel } from 'xstate/lib/model'; import { request } from '../shared/request'; import { VcIdType } from '../types/vc'; import { MY_VCS_STORE_KEY } from '../shared/constants'; +import { VCMetadata } from '../shared/VCMetadata'; const model = createModel( { @@ -20,7 +21,7 @@ const model = createModel( otpError: '', transactionId: '', requestId: '', - VIDs: [] as string[], + VIDsMetadata: [] as VCMetadata[], }, { events: { @@ -29,7 +30,7 @@ const model = createModel( READY: (idInputRef: TextInput) => ({ idInputRef }), DISMISS: () => ({}), SELECT_ID_TYPE: (idType: VcIdType) => ({ idType }), - REVOKE_VCS: (vcKeys: string[]) => ({ vcKeys }), + REVOKE_VCS: (vcMetadatas: VCMetadata[]) => ({ vcMetadatas }), STORE_RESPONSE: (response: string[]) => ({ response }), ERROR: (data: Error) => ({ data }), SUCCESS: () => ({}), @@ -165,7 +166,7 @@ export const revokeVidsMachine = }), setVIDs: model.assign({ - VIDs: (_context, event) => event.vcKeys, + VIDsMetadata: (_context, event) => event.vcMetadatas, }), setIdBackendError: assign({ @@ -206,12 +207,12 @@ export const revokeVidsMachine = logRevoked: send( (context) => ActivityLogEvents.LOG_ACTIVITY( - context.VIDs.map((vc) => ({ - _vcKey: vc, + context.VIDsMetadata.map((metadata) => ({ + _vcKey: metadata.getVcKey(), type: 'VC_REVOKED', timestamp: Date.now(), deviceName: '', - vcLabel: vc.split(':')[2], + vcLabel: metadata.id, })) ), { @@ -221,7 +222,10 @@ export const revokeVidsMachine = revokeVID: send( (context) => { - return StoreEvents.REMOVE_ITEMS(MY_VCS_STORE_KEY, context.VIDs); + return StoreEvents.REMOVE_ITEMS( + MY_VCS_STORE_KEY, + context.VIDsMetadata.map((m) => m.getVcKey()) + ); }, { to: (context) => context.serviceRefs.store, @@ -233,7 +237,7 @@ export const revokeVidsMachine = requestOtp: async (context) => { const transactionId = String(new Date().valueOf()).substring(3, 13); return request('POST', '/residentmobileapp/req/otp', { - individualId: context.VIDs[0].split(':')[2], + individualId: context.VIDsMetadata[0].id, individualIdType: 'VID', otpChannel: ['EMAIL', 'PHONE'], transactionID: transactionId, @@ -242,20 +246,23 @@ export const revokeVidsMachine = requestRevoke: (context) => async (callback) => { await Promise.all( - context.VIDs.map((vid: string) => { + context.VIDsMetadata.map((metadata: VCMetadata) => { try { - const vidID = vid.split(':')[2]; const transactionId = String(new Date().valueOf()).substring( 3, 13 ); - return request('PATCH', `/residentmobileapp/vid/${vidID}`, { - transactionID: transactionId, - vidStatus: 'REVOKED', - individualId: vidID, - individualIdType: 'VID', - otp: context.otp, - }); + return request( + 'PATCH', + `/residentmobileapp/vid/${metadata.id}`, + { + transactionID: transactionId, + vidStatus: 'REVOKED', + individualId: metadata.id, + individualIdType: 'VID', + otp: context.otp, + } + ); } catch (error) { console.log('error.message', error.message); return error; diff --git a/machines/settings.ts b/machines/settings.ts index a070d926..5905cc52 100644 --- a/machines/settings.ts +++ b/machines/settings.ts @@ -1,6 +1,6 @@ -import { assign, ContextFrom, EventFrom, send, StateFrom } from 'xstate'; -import { createModel } from 'xstate/lib/model'; -import { AppServices } from '../shared/GlobalContext'; +import {assign, ContextFrom, EventFrom, send, StateFrom} from 'xstate'; +import {createModel} from 'xstate/lib/model'; +import {AppServices} from '../shared/GlobalContext'; import { APP_ID_DICTIONARY, APP_ID_LENGTH, @@ -9,15 +9,15 @@ import { SETTINGS_STORE_KEY, ESIGNET_BASE_URL, } from '../shared/constants'; -import { VCLabel } from '../types/vc'; -import { StoreEvents } from './store'; +import {VCLabel} from '../types/vc'; +import {StoreEvents} from './store'; import getAllConfigurations, { COMMON_PROPS_KEY, } from '../shared/commonprops/commonProps'; import Storage from '../shared/storage'; import ShortUniqueId from 'short-unique-id'; -import { __AppId } from '../shared/GlobalVariables'; -import { isCustomSecureKeystore } from '../shared/cryptoutil/cryptoUtil'; +import {__AppId} from '../shared/GlobalVariables'; +import {isCustomSecureKeystore} from '../shared/cryptoutil/cryptoUtil'; const model = createModel( { @@ -36,17 +36,17 @@ const model = createModel( }, { events: { - UPDATE_NAME: (name: string) => ({ name }), - UPDATE_VC_LABEL: (label: string) => ({ label }), - TOGGLE_BIOMETRIC_UNLOCK: (enable: boolean) => ({ enable }), - STORE_RESPONSE: (response: unknown) => ({ response }), - CHANGE_LANGUAGE: (language: string) => ({ language }), + UPDATE_NAME: (name: string) => ({name}), + UPDATE_VC_LABEL: (label: string) => ({label}), + TOGGLE_BIOMETRIC_UNLOCK: (enable: boolean) => ({enable}), + STORE_RESPONSE: (response: unknown) => ({response}), + CHANGE_LANGUAGE: (language: string) => ({language}), UPDATE_MIMOTO_HOST: (credentialRegistry: string) => ({ credentialRegistry, }), - UPDATE_ESIGNET_HOST: (esignetHostUrl: string) => ({ esignetHostUrl }), + UPDATE_ESIGNET_HOST: (esignetHostUrl: string) => ({esignetHostUrl}), UPDATE_CREDENTIAL_REGISTRY_RESPONSE: ( - credentialRegistryResponse: string + credentialRegistryResponse: string, ) => ({ credentialRegistryResponse: credentialRegistryResponse, }), @@ -55,7 +55,7 @@ const model = createModel( CANCEL: () => ({}), ACCEPT_HARDWARE_SUPPORT_NOT_EXISTS: () => ({}), }, - } + }, ); export const SettingsEvents = model.events; @@ -81,8 +81,8 @@ export const settingsMachine = model.createMachine( target: 'idle', actions: ['setContext', 'updatePartialDefaults', 'storeContext'], }, - { cond: 'hasData', target: 'idle', actions: ['setContext'] }, - { target: 'storingDefaults' }, + {cond: 'hasData', target: 'idle', actions: ['setContext']}, + {target: 'storingDefaults'}, ], }, }, @@ -165,7 +165,7 @@ export const settingsMachine = model.createMachine( { actions: { requestStoredContext: send(StoreEvents.GET(SETTINGS_STORE_KEY), { - to: (context) => context.serviceRefs.store, + to: context => context.serviceRefs.store, }), updateDefaults: model.assign({ @@ -179,15 +179,15 @@ export const settingsMachine = model.createMachine( }), updatePartialDefaults: model.assign({ - appId: (context) => context.appId || generateAppId(), + appId: context => context.appId || generateAppId(), }), storeContext: send( - (context) => { - const { serviceRefs, ...data } = context; + context => { + const {serviceRefs, ...data} = context; return StoreEvents.SET(SETTINGS_STORE_KEY, data); }, - { to: (context) => context.serviceRefs.store } + {to: context => context.serviceRefs.store}, ), setContext: model.assign((context, event) => { @@ -255,7 +255,7 @@ export const settingsMachine = model.createMachine( hasPartialData: (_, event) => event.response != null && event.response.appId == null, }, - } + }, ); export function createSettingsMachine(serviceRefs: AppServices) { diff --git a/machines/store.ts b/machines/store.ts index aa8e133e..4105e3b4 100644 --- a/machines/store.ts +++ b/machines/store.ts @@ -9,10 +9,10 @@ import { sendUpdate, StateFrom, } from 'xstate'; -import { createModel } from 'xstate/lib/model'; -import { generateSecureRandom } from 'react-native-securerandom'; -import { log } from 'xstate/lib/actions'; -import { MY_VCS_STORE_KEY, VC_ITEM_STORE_KEY_REGEX } from '../shared/constants'; +import {createModel} from 'xstate/lib/model'; +import {generateSecureRandom} from 'react-native-securerandom'; +import {log} from 'xstate/lib/actions'; +import {MY_VCS_STORE_KEY} from '../shared/constants'; import SecureKeystore from 'react-native-secure-keystore'; import { AUTH_TIMEOUT, @@ -24,8 +24,8 @@ import { HMAC_ALIAS, isCustomSecureKeystore, } from '../shared/cryptoutil/cryptoUtil'; +import {VCMetadata} from '../shared/VCMetadata'; -const vcKeyRegExp = new RegExp(VC_ITEM_STORE_KEY_REGEX); export const keyinvalidatedString = 'Key Invalidated due to biometric enrollment'; export const tamperedErrorMessageString = 'Data is tampered'; @@ -37,38 +37,38 @@ const model = createModel( }, { events: { - KEY_RECEIVED: (key: string) => ({ key }), + KEY_RECEIVED: (key: string) => ({key}), READY: () => ({}), TRY_AGAIN: () => ({}), IGNORE: () => ({}), - GET: (key: string) => ({ key }), + GET: (key: string) => ({key}), DECRYPT_ERROR: () => ({}), KEY_INVALIDATE_ERROR: () => ({}), - SET: (key: string, value: unknown) => ({ key, value }), - APPEND: (key: string, value: unknown) => ({ key, value }), - PREPEND: (key: string, value: unknown) => ({ key, value }), - UPDATE: (key: string, value: string) => ({ key, value }), - REMOVE: (key: string, value: string) => ({ key, value }), - REMOVE_VC_METADATA: (key: string, value: string) => ({ key, value }), - REMOVE_ITEMS: (key: string, values: string[]) => ({ key, values }), + SET: (key: string, value: unknown) => ({key, value}), + APPEND: (key: string, value: unknown) => ({key, value}), + PREPEND: (key: string, value: unknown) => ({key, value}), + UPDATE: (key: string, value: string) => ({key, value}), + REMOVE: (key: string, value: string) => ({key, value}), + REMOVE_VC_METADATA: (key: string, value: string) => ({key, value}), + REMOVE_ITEMS: (key: string, values: string[]) => ({key, values}), CLEAR: () => ({}), - ERROR: (error: Error) => ({ error }), + ERROR: (error: Error) => ({error}), STORE_RESPONSE: (response?: unknown, requester?: string) => ({ response, requester, }), - STORE_ERROR: (error: Error, requester?: string) => ({ error, requester }), + STORE_ERROR: (error: Error, requester?: string) => ({error, requester}), TAMPERED_VC: () => ({}), RESET_IS_TAMPERED: () => ({}), }, - } + }, ); export const StoreEvents = model.events; export type StoreResponseEvent = EventFrom; -type ForwardedEvent = EventFrom & { requester: string }; +type ForwardedEvent = EventFrom & {requester: string}; export const storeMachine = /** @xstate-layout N4IgpgJg5mDOIC5SwC4HsBOYB0MUoEsA7KAUSIGMMBPAB0LSIGkxqBiJ0gTQH0AlUgGFSASQBqpACIBtAAwBdRKFppYBBkSUgAHogBMADj3YArAYDMAdnMBOQ5csA2AIzOTAGhDVErt9kcmzrKGznqyBgYm5gC+0Z6omDh4hCTkVHQaLOykfHwA8nxyikggKmoaWroIhsZmVrb2Tq4eXog2AaY2ACyWdjbBhgbOsfHoWLhgRGAYAIYpZJQ09ASMWRzc-EKiEjIKWmXqK5olVdYG2G4OoeYmlgb9ep7eCDYmjtgWJnp6zjaWvo49CMQAlxjAprN5mklplWGwcvlCnsSgcKidECYuu8vs4uuYAtZHBYDE9EJETKY3OZZLcTJi9OYgXEQWMcFhYGB8MQoABlMYzGBsCCMHDEABuaAA1jhQWy4Jz5nzMAKwAhxWgKHMjkUivtVIdGJUfLI-thZFjMRYrJaSa0Xu1sHo6V0TQFHHiTMDZdh2QruUrZoLphhMNhaAAbOYAM0wAFtsN7fVySAGVWqiBLNRodcjlPq0aAqt1jOFgrJbI47JYfqSEAZrJ0uq9ukMotYvayfWAZhB2ABxUgAFV1KPzRyNCFCJlk-kcljxLibekcjlrQVkM-M+IXRK6PxXHcSXZ77B5Q5HefK4-Rk6dM5X8-xuLsK9r-S6F2sNKaBmCRkP4xYCebAAIIAApgaQAByuzFJeBrHIWPh3rOj6Li+q52r+5wbu0PzmJEhjlgBcrAWBAiQTBF6lGOho3lO95zguz7Lphzz-M4HxYs4kRuE6jJdCRx69mwAgALJ5BI1GoteSG3tOqHMUur52i4FJEno873L8dw9EJQEieJkmkDwYiCDwYlDiBkggYOIHSbRiE6Mh5icdWPzOI4shOG2taBDYlI2PiNiRDc7SesyibdoZpASRIPAiIOsU8g5V50XJDGKU+ylsYge7mNgNwmDY-R3PO4ROvp0XsIIAAypAgUicE0WlTlVJlD5KRhtaMpYFx2E2ZxDBulhVcBPKDgUJkCDyYF5FBZ6pQhE5TsY87LgyFjdO0tZhB+5hdDxJrlnoeGyI4Y0iRNU08AiBRLQWzm3nO-jFS4tgVs4BG1kM2BNq4YRzncDJNrEzJEGgEBwFosp6q1E4ALS5QgSNCck3LQhkRxZHDy03nua7ro6056IdVineWo2RZ24LTFqqSLFjqysLjj1VFixj1hEXldG8XnBG+bz+GVh0hUM-xMqMR5Joq-IwKzslPXYM5dE2+IbtS-Q2G+-zYJYZhfUVNjOJYJqXc88Fs8heKFYb5UmtYhOyJxTYhdWxuVu6EWxEAA */ @@ -213,7 +213,7 @@ export const storeMachine = (_, event) => model.events.STORE_RESPONSE(event.response), { to: (_, event) => event.requester, - } + }, ), sendUpdate(), ], @@ -261,7 +261,7 @@ export const storeMachine = ...event, requester: meta._event.origin, }), - { to: '_store' } + {to: '_store'}, ), setEncryptionKey: model.assign({ @@ -271,13 +271,13 @@ export const storeMachine = services: { clear, - hasAndroidEncryptionKey: () => async (callback) => { + hasAndroidEncryptionKey: () => async callback => { const hasSetCredentials = SecureKeystore.hasAlias(ENCRYPTION_ID); if (hasSetCredentials) { try { await SecureKeystore.encryptData( DUMMY_KEY_FOR_BIOMETRIC_ALIAS, - 'Dummy' + 'Dummy', ); } catch (e) { if (e.message.includes(keyinvalidatedString)) { @@ -292,12 +292,12 @@ export const storeMachine = } else { callback( model.events.ERROR( - new Error('Could not get the android Key alias') - ) + new Error('Could not get the android Key alias'), + ), ); } }, - checkStorageInitialisedOrNot: () => async (callback) => { + checkStorageInitialisedOrNot: () => async callback => { const isDirectoryExist = await Storage.isVCStorageInitialised(); if (!isDirectoryExist) { callback(model.events.READY()); @@ -305,15 +305,15 @@ export const storeMachine = callback( model.events.ERROR( new Error( - 'vc directory exists and decryption key is not available' - ) - ) + 'vc directory exists and decryption key is not available', + ), + ), ); } }, - store: (context) => (callback, onReceive: Receiver) => { - onReceive(async (event) => { + store: context => (callback, onReceive: Receiver) => { + onReceive(async event => { try { let response: unknown; switch (event.type) { @@ -321,7 +321,7 @@ export const storeMachine = response = await getItem( event.key, null, - context.encryptionKey + context.encryptionKey, ); break; } @@ -334,7 +334,7 @@ export const storeMachine = await appendItem( event.key, event.value, - context.encryptionKey + context.encryptionKey, ); response = event.value; break; @@ -343,7 +343,7 @@ export const storeMachine = await prependItem( event.key, event.value, - context.encryptionKey + context.encryptionKey, ); response = event.value; @@ -353,7 +353,7 @@ export const storeMachine = await updateItem( event.key, event.value, - context.encryptionKey + context.encryptionKey, ); response = event.value; @@ -363,7 +363,7 @@ export const storeMachine = await removeItem( event.key, event.value, - context.encryptionKey + context.encryptionKey, ); response = event.value; break; @@ -372,7 +372,7 @@ export const storeMachine = await removeVCMetaData( event.key, event.value, - context.encryptionKey + context.encryptionKey, ); response = event.value; break; @@ -381,7 +381,7 @@ export const storeMachine = await removeItems( event.key, event.values, - context.encryptionKey + context.encryptionKey, ); response = event.values; break; @@ -414,7 +414,7 @@ export const storeMachine = } }); }, - getEncryptionKey: () => async (callback) => { + getEncryptionKey: () => async callback => { const existingCredentials = await Keychain.getGenericPassword(); if (existingCredentials) { console.log('Credentials successfully loaded for user'); @@ -423,18 +423,18 @@ export const storeMachine = console.log('Credentials failed to load for user'); callback( model.events.ERROR( - new Error('Could not get keychain credentials.') - ) + new Error('Could not get keychain credentials.'), + ), ); } }, - generateEncryptionKey: () => async (callback) => { + generateEncryptionKey: () => async callback => { const randomBytes = await generateSecureRandom(32); const randomBytesString = binaryToBase64(randomBytes); if (!isCustomSecureKeystore()) { const hasSetCredentials = await Keychain.setGenericPassword( ENCRYPTION_ID, - randomBytesString + randomBytesString, ); if (hasSetCredentials) { @@ -442,8 +442,8 @@ export const storeMachine = } else { callback( model.events.ERROR( - new Error('Could not generate keychain credentials.') - ) + new Error('Could not generate keychain credentials.'), + ), ); } } else { @@ -451,13 +451,13 @@ export const storeMachine = await SecureKeystore.generateKey( ENCRYPTION_ID, isBiometricsEnabled, - AUTH_TIMEOUT + AUTH_TIMEOUT, ); SecureKeystore.generateHmacshaKey(HMAC_ALIAS); SecureKeystore.generateKey( DUMMY_KEY_FOR_BIOMETRIC_ALIAS, isBiometricsEnabled, - 0 + 0, ); callback(model.events.KEY_RECEIVED('')); } @@ -467,13 +467,13 @@ export const storeMachine = guards: { isCustomSecureKeystore: () => isCustomSecureKeystore(), }, - } + }, ); export async function setItem( key: string, value: unknown, - encryptionKey: string + encryptionKey: string, ) { try { const data = JSON.stringify(value); @@ -488,17 +488,15 @@ export async function setItem( export async function getItem( key: string, defaultValue: unknown, - encryptionKey: string + encryptionKey: string, ) { try { const data = await Storage.getItem(key, encryptionKey); - console.log('getting item for ' + key); - console.log(data); if (data != null) { const decryptedData = await decryptJson(encryptionKey, data); return JSON.parse(decryptedData); } - if (data === null && vcKeyRegExp.exec(key)) { + if (data === null && VCMetadata.isVCKey(key)) { await removeItem(key, data, encryptionKey); throw new Error(tamperedErrorMessageString); } else { @@ -520,7 +518,7 @@ export async function getItem( export async function appendItem( key: string, value: unknown, - encryptionKey: string + encryptionKey: string, ) { try { const list = await getItem(key, [], encryptionKey); @@ -534,7 +532,7 @@ export async function appendItem( export async function prependItem( key: string, value: unknown, - encryptionKey: string + encryptionKey: string, ) { try { const list = await getItem(key, [], encryptionKey); @@ -552,20 +550,21 @@ export async function prependItem( export async function updateItem( key: string, value: string, - encryptionKey: string + encryptionKey: string, ) { + // Used for updating VC metadata in the list. Prepends the passed vcmetadata in value and sets ispinned of other vc metadata to false try { const list = await getItem(key, [], encryptionKey); + const updatedMetaData = VCMetadata.fromVcMetadataString(value); const newList = [ value, - ...list.map((item) => { - const vc = item.split(':'); - if (vc[3] !== value.split(':')[3]) { - vc[4] = 'false'; - return vc.join(':'); + ...list.map(metadataStr => { + const metaData = VCMetadata.fromVcMetadataString(metadataStr); + if (metaData.getVcKey() !== updatedMetaData.getVcKey()) { + return JSON.stringify(metaData); } }), - ].filter((value) => value != undefined && value !== null); + ].filter(value => value != undefined && value !== null); await setItem(key, newList, encryptionKey); } catch (e) { @@ -577,22 +576,18 @@ export async function updateItem( export async function removeItem( key: string, value: string, - encryptionKey: string + encryptionKey: string, ) { try { - if (value === null && vcKeyRegExp.exec(key)) { + if (value === null && VCMetadata.isVCKey(key)) { await Storage.removeItem(key); await removeVCMetaData(MY_VCS_STORE_KEY, key, encryptionKey); - } else { + } else if (key === MY_VCS_STORE_KEY) { const data = await Storage.getItem(key, encryptionKey); const decryptedData = await decryptJson(encryptionKey, data); - const list = JSON.parse(decryptedData); - const vcKeyArray = value.split(':'); - const finalVcKeyArray = vcKeyArray.pop(); - const finalVcKey = vcKeyArray.join(':'); - //console.log('finalVcKeyArray', finalVcKeyArray); - const newList = list.filter((vc: string) => { - return !vc.includes(finalVcKey); + const list = JSON.parse(decryptedData) as string[]; + const newList = list.filter((str: string) => { + return VCMetadata.fromVcMetadataString(str).getVcKey() !== value; }); await setItem(key, newList, encryptionKey); @@ -606,15 +601,15 @@ export async function removeItem( export async function removeVCMetaData( key: string, - value: string, - encryptionKey: string + vcKey: string, + encryptionKey: string, ) { try { const data = await Storage.getItem(key, encryptionKey); const decryptedData = await decryptJson(encryptionKey, data); - const list = JSON.parse(decryptedData); - const newList = list.filter((vc: string) => { - return !vc.includes(value); + const list = JSON.parse(decryptedData) as string[]; + const newList = list.filter((str: string) => { + return VCMetadata.fromVcMetadataString(str).getVcKey() !== vcKey; }); await setItem(key, newList, encryptionKey); @@ -627,20 +622,16 @@ export async function removeVCMetaData( export async function removeItems( key: string, values: string[], - encryptionKey: string + encryptionKey: string, ) { try { const data = await Storage.getItem(key, encryptionKey); const decryptedData = await decryptJson(encryptionKey, data); - const list = JSON.parse(decryptedData); - const newList = list.filter(function (vc: string) { - return !values.find(function (vcKey: string) { - const vcKeyArray = vcKey.split(':'); - const finalVcKeyArray = vcKeyArray.pop(); - const finalVcKey = vcKeyArray.join(':'); - return vc.includes(finalVcKey); - }); - }); + const list = JSON.parse(decryptedData) as string[]; + const newList = list.filter( + (str: string) => + !values.includes(VCMetadata.fromVcMetadataString(str).getVcKey()), + ); await setItem(key, newList, encryptionKey); } catch (e) { diff --git a/machines/vc.ts b/machines/vc.ts index b970b2cb..9128158b 100644 --- a/machines/vc.ts +++ b/machines/vc.ts @@ -1,43 +1,38 @@ -import { EventFrom, StateFrom } from 'xstate'; -import { send, sendParent } from 'xstate'; -import { createModel } from 'xstate/lib/model'; -import { StoreEvents } from './store'; -import { VC } from '../types/vc'; -import { AppServices } from '../shared/GlobalContext'; -import { log, respond } from 'xstate/lib/actions'; -import { VcItemEvents } from './vcItem'; -import { - MY_VCS_STORE_KEY, - RECEIVED_VCS_STORE_KEY, - VC_ITEM_STORE_KEY, - isSameVC, -} from '../shared/constants'; +import {EventFrom, send, sendParent, StateFrom} from 'xstate'; +import {createModel} from 'xstate/lib/model'; +import {StoreEvents} from './store'; +import {VC} from '../types/vc'; +import {AppServices} from '../shared/GlobalContext'; +import {log, respond} from 'xstate/lib/actions'; +import {VcItemEvents} from './vcItem'; +import {MY_VCS_STORE_KEY, RECEIVED_VCS_STORE_KEY} from '../shared/constants'; +import {parseMetadatas, VCMetadata} from '../shared/VCMetadata'; const model = createModel( { serviceRefs: {} as AppServices, - myVcs: [] as string[], - receivedVcs: [] as string[], + myVcs: [] as VCMetadata[], + receivedVcs: [] as VCMetadata[], vcs: {} as Record, }, { events: { - VIEW_VC: (vc: VC) => ({ vc }), - GET_VC_ITEM: (vcKey: string) => ({ vcKey }), - STORE_RESPONSE: (response: unknown) => ({ response }), - STORE_ERROR: (error: Error) => ({ error }), - VC_ADDED: (vcKey: string) => ({ vcKey }), - REMOVE_VC_FROM_CONTEXT: (vcKey: string) => ({ vcKey }), - VC_UPDATED: (vcKey: string) => ({ vcKey }), - VC_RECEIVED: (vcKey: string) => ({ vcKey }), - VC_DOWNLOADED: (vc: VC) => ({ vc }), - VC_UPDATE: (vc: VC) => ({ vc }), + 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_UPDATE: (vc: VC) => ({vc}), REFRESH_MY_VCS: () => ({}), - REFRESH_MY_VCS_TWO: (vc: VC) => ({ vc }), + REFRESH_MY_VCS_TWO: (vc: VC) => ({vc}), REFRESH_RECEIVED_VCS: () => ({}), GET_RECEIVED_VCS: () => ({}), }, - } + }, ); export const VcEvents = model.events; @@ -145,8 +140,8 @@ export const vcMachine = REMOVE_VC_FROM_CONTEXT: { actions: 'removeVcFromMyVcs', }, - VC_UPDATED: { - actions: ['updateMyVcs', 'setUpdateVc'], + VC_METADATA_UPDATED: { + actions: ['updateMyVcs', 'setUpdatedVcMetadatas'], }, VC_DOWNLOADED: { actions: 'setDownloadedVc', @@ -169,79 +164,80 @@ export const vcMachine = }, { actions: { - getReceivedVcsResponse: respond((context) => ({ + getReceivedVcsResponse: respond(context => ({ type: 'VC_RESPONSE', - response: context.receivedVcs, + response: context.receivedVcs || [], })), getVcItemResponse: respond((context, event) => { - const vc = context.vcs[event.vcKey]; + const vc = context.vcs[event.vcMetadata?.getVcKey()]; return VcItemEvents.GET_VC_RESPONSE(vc); }), loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), { - to: (context) => context.serviceRefs.store, + to: context => context.serviceRefs.store, }), loadReceivedVcs: send(StoreEvents.GET(RECEIVED_VCS_STORE_KEY), { - to: (context) => context.serviceRefs.store, + to: context => context.serviceRefs.store, }), setMyVcs: model.assign({ - myVcs: (_context, event) => (event.response || []) as string[], + myVcs: (_context, event) => { + return parseMetadatas((event.response || []) as object[]); + }, }), setReceivedVcs: model.assign({ - receivedVcs: (_context, event) => (event.response || []) as string[], + receivedVcs: (_context, event) => { + return parseMetadatas((event.response || []) as object[]); + }, }), setDownloadedVc: (context, event) => { - context.vcs[VC_ITEM_STORE_KEY(event.vc)] = event.vc; + const vcUniqueId = VCMetadata.fromVC(event.vc).getVcKey(); + context.vcs[vcUniqueId] = event.vc; }, setVcUpdate: (context, event) => { - Object.keys(context.vcs).map((vcKey) => { - if (isSameVC(vcKey, VC_ITEM_STORE_KEY(event.vc))) { - context.vcs[VC_ITEM_STORE_KEY(event.vc)] = context.vcs[vcKey]; - delete context.vcs[vcKey]; - return context.vcs[VC_ITEM_STORE_KEY(event.vc)]; + 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()]; } }); }, - setUpdateVc: send( - (_context, event) => { - return StoreEvents.UPDATE(MY_VCS_STORE_KEY, event.vcKey); + setUpdatedVcMetadatas: send( + _context => { + return StoreEvents.SET(MY_VCS_STORE_KEY, _context.myVcs); }, - { to: (context) => context.serviceRefs.store } + {to: context => context.serviceRefs.store}, ), prependToMyVcs: model.assign({ - myVcs: (context, event) => [event.vcKey, ...context.myVcs], + myVcs: (context, event) => [event.vcMetadata, ...context.myVcs], }), removeVcFromMyVcs: model.assign({ myVcs: (context, event) => - context.myVcs.filter((vc: string) => !vc.includes(event.vcKey)), + context.myVcs.filter( + (vc: VCMetadata) => !vc.equals(event.vcMetadata), + ), }), updateMyVcs: model.assign({ - myVcs: (context, event) => - [ - event.vcKey, - ...context.myVcs.map((value) => { - const vc = value.split(':'); - if (vc[3] !== event.vcKey.split(':')[3]) { - vc[4] = 'false'; - return vc.join(':'); - } - }), - ].filter((value) => value != undefined), + myVcs: (context, event) => [ + ...getUpdatedVCMetadatas(context.myVcs, event.vcMetadata), + ], }), prependToReceivedVcs: model.assign({ receivedVcs: (context, event) => [ - event.vcKey, + event.vcMetadata, ...context.receivedVcs, ], }), @@ -249,8 +245,10 @@ export const vcMachine = moveExistingVcToTop: model.assign({ receivedVcs: (context, event) => { return [ - event.vcKey, - ...context.receivedVcs.filter((value) => value !== event.vcKey), + event.vcMetadata, + ...context.receivedVcs.filter(value => + value.equals(event.vcMetadata), + ), ]; }, }), @@ -258,9 +256,11 @@ export const vcMachine = guards: { hasExistingReceivedVc: (context, event) => - context.receivedVcs.includes(event.vcKey), + context.receivedVcs.find(vcMetadata => + vcMetadata.equals(event.vcMetadata), + ) != null, }, - } + }, ); export function createVcMachine(serviceRefs: AppServices) { @@ -272,17 +272,17 @@ export function createVcMachine(serviceRefs: AppServices) { type State = StateFrom; -export function selectMyVcs(state: State) { +export function selectMyVcsMetadata(state: State): VCMetadata[] { return state.context.myVcs; } -export function selectShareableVcs(state: State) { +export function selectShareableVcsMetadata(state: State): VCMetadata[] { return state.context.myVcs.filter( - (vcKey) => state.context.vcs[vcKey]?.credential != null + vcMetadata => state.context.vcs[vcMetadata.getVcKey()]?.credential != null, ); } -export function selectReceivedVcs(state: State) { +export function selectReceivedVcsMetadata(state: State): VCMetadata[] { return state.context.receivedVcs; } @@ -297,15 +297,34 @@ export function selectIsRefreshingReceivedVcs(state: State) { /* this methods returns all the binded vc's in the wallet. */ -export function selectBindedVcs(state: State) { - return (state.context.myVcs as Array).filter((key) => { - const walletBindingResponse = state.context.vcs[key]?.walletBindingResponse; +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) ); }); } + +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; } diff --git a/machines/vcItem.ts b/machines/vcItem.ts index 879cf573..dbf9bb91 100644 --- a/machines/vcItem.ts +++ b/machines/vcItem.ts @@ -1,29 +1,24 @@ -import { assign, ErrorPlatformEvent, EventFrom, send, StateFrom } from 'xstate'; -import { createModel } from 'xstate/lib/model'; -import { - MIMOTO_BASE_URL, - MY_VCS_STORE_KEY, - VC_ITEM_STORE_KEY, - VC_ITEM_STORE_KEY_AFTER_DOWNLOAD, -} from '../shared/constants'; -import { AppServices } from '../shared/GlobalContext'; -import { CredentialDownloadResponse, request } from '../shared/request'; +import {assign, ErrorPlatformEvent, EventFrom, send, StateFrom} from 'xstate'; +import {createModel} from 'xstate/lib/model'; +import {HOST, MIMOTO_BASE_URL, MY_VCS_STORE_KEY} from '../shared/constants'; +import {AppServices} from '../shared/GlobalContext'; +import {CredentialDownloadResponse, request} from '../shared/request'; import { VC, VerifiableCredential, VcIdType, DecodedCredential, } from '../types/vc'; -import { StoreEvents } from './store'; -import { ActivityLogEvents } from './activityLog'; -import { verifyCredential } from '../shared/vcjs/verifyCredential'; -import { log } from 'xstate/lib/actions'; +import {StoreEvents} from './store'; +import {ActivityLogEvents} from './activityLog'; +import {verifyCredential} from '../shared/vcjs/verifyCredential'; +import {log} from 'xstate/lib/actions'; import { generateKeys, isCustomSecureKeystore, WalletBindingResponse, } from '../shared/cryptoutil/cryptoUtil'; -import { KeyPair } from 'react-native-rsa-native'; +import {KeyPair} from 'react-native-rsa-native'; import { getBindingCertificateConstant, savePrivateKey, @@ -31,9 +26,10 @@ import { import getAllConfigurations, { DownloadProps, } from '../shared/commonprops/commonProps'; -import { VcEvents } from './vc'; +import {VcEvents} from './vc'; import i18n from '../i18n'; import SecureKeystore from 'react-native-secure-keystore'; +import {VCMetadata} from '../shared/VCMetadata'; import { sendStartEvent, getData, @@ -47,7 +43,7 @@ const model = createModel( id: '', idType: '' as VcIdType, tag: '', - vcKey: '' as string, + vcMetadata: {} as VCMetadata, myVcs: [] as string[], generatedOn: null as Date, credential: null as DecodedCredential, @@ -71,36 +67,35 @@ const model = createModel( walletBindingError: '', publicKey: '', privateKey: '', - hashedId: '', }, { events: { - KEY_RECEIVED: (key: string) => ({ key }), - KEY_ERROR: (error: Error) => ({ error }), + KEY_RECEIVED: (key: string) => ({key}), + KEY_ERROR: (error: Error) => ({error}), EDIT_TAG: () => ({}), - SAVE_TAG: (tag: string) => ({ tag }), + SAVE_TAG: (tag: string) => ({tag}), STORE_READY: () => ({}), DISMISS: () => ({}), - CREDENTIAL_DOWNLOADED: (vc: VC) => ({ vc }), - STORE_RESPONSE: (response: VC) => ({ response }), + CREDENTIAL_DOWNLOADED: (vc: VC) => ({vc}), + STORE_RESPONSE: (response: VC) => ({response}), POLL: () => ({}), DOWNLOAD_READY: () => ({}), - GET_VC_RESPONSE: (vc: VC) => ({ vc }), + GET_VC_RESPONSE: (vc: VC) => ({vc}), VERIFY: () => ({}), LOCK_VC: () => ({}), - INPUT_OTP: (otp: string) => ({ otp }), + INPUT_OTP: (otp: string) => ({otp}), REFRESH: () => ({}), REVOKE_VC: () => ({}), ADD_WALLET_BINDING_ID: () => ({}), CANCEL: () => ({}), CONFIRM: () => ({}), - STORE_ERROR: (error: Error) => ({ error }), + STORE_ERROR: (error: Error) => ({error}), PIN_CARD: () => ({}), KEBAB_POPUP: () => ({}), SHOW_ACTIVITY: () => ({}), - REMOVE: (vcKey: string) => ({ vcKey }), + REMOVE: (vcMetadata: VCMetadata) => ({vcMetadata}), }, - } + }, ); export const VcItemEvents = model.events; @@ -184,7 +179,7 @@ export const vcItemMachine = on: { POLL: { cond: 'isDownloadAllowed', - actions: send('POLL_STATUS', { to: 'checkStatus' }), + actions: send('POLL_STATUS', {to: 'checkStatus'}), }, DOWNLOAD_READY: { target: 'downloadingCredential', @@ -201,17 +196,13 @@ export const vcItemMachine = { cond: 'isDownloadAllowed', actions: [ - send('POLL_DOWNLOAD', { to: 'downloadCredential' }), + send('POLL_DOWNLOAD', {to: 'downloadCredential'}), 'incrementDownloadCounter', ], }, ], CREDENTIAL_DOWNLOADED: { - actions: [ - 'setStoreVerifiableCredential', - 'storeContext', - 'editVcKey', - ], + actions: ['setStoreVerifiableCredential', 'storeContext'], }, STORE_RESPONSE: { actions: [ @@ -273,12 +264,9 @@ export const vcItemMachine = }, }, pinCard: { - entry: 'storeContext', - on: { - STORE_RESPONSE: { - actions: ['sendVcUpdated', 'VcUpdated'], - target: 'idle', - }, + entry: 'sendVcUpdated', + always: { + target: 'idle', }, }, kebabPopUp: { @@ -763,7 +751,7 @@ export const vcItemMachine = }, { actions: { - setVerifiableCredential: assign((context) => { + setVerifiableCredential: assign(context => { return { ...context, verifiableCredential: { @@ -785,33 +773,31 @@ export const vcItemMachine = }), removeVcMetaDataFromStorage: send( - (context) => { - const { serviceRefs, ...data } = context; + context => { return StoreEvents.REMOVE_VC_METADATA( MY_VCS_STORE_KEY, - VC_ITEM_STORE_KEY(context) + new VCMetadata(context).getVcKey(), ); }, { - to: (context) => context.serviceRefs.store, - } + to: context => context.serviceRefs.store, + }, ), removeVcMetaDataFromVcMachine: send( - (context, _event) => { - const { serviceRefs, ...data } = context; + context => { return { type: 'REMOVE_VC_FROM_CONTEXT', - vcKey: VC_ITEM_STORE_KEY(context), + vcMetadata: new VCMetadata(context), }; }, { - to: (context) => context.serviceRefs.vc, - } + to: context => context.serviceRefs.vc, + }, ), setWalletBindingError: assign({ - walletBindingError: (context, event) => + walletBindingError: () => i18n.t(`errors.genericError`, { ns: 'common', }), @@ -843,7 +829,7 @@ export const vcItemMachine = event.data as WalletBindingResponse, }), - setPinCard: assign((context) => { + setPinCard: assign(context => { return { ...context, isPinned: !context.isPinned, @@ -851,47 +837,46 @@ export const vcItemMachine = }), sendVcUpdated: send( - (_context, event) => - VcEvents.VC_UPDATED(VC_ITEM_STORE_KEY(event.response) as string), + context => VcEvents.VC_METADATA_UPDATED(new VCMetadata(context)), { - to: (context) => context.serviceRefs.vc, - } + to: context => context.serviceRefs.vc, + }, ), updateVc: send( - (context) => { - const { serviceRefs, ...vc } = context; - return { type: 'VC_DOWNLOADED', vc }; + context => { + const {serviceRefs, ...vc} = context; + return {type: 'VC_DOWNLOADED', vc}; }, { - to: (context) => context.serviceRefs.vc, - } + to: context => context.serviceRefs.vc, + }, ), VcUpdated: send( - (context) => { - const { serviceRefs, ...vc } = context; - return { type: 'VC_UPDATE', vc }; + context => { + const {serviceRefs, ...vc} = context; + return {type: 'VC_UPDATE', vc}; }, { - to: (context) => context.serviceRefs.vc, - } + to: context => context.serviceRefs.vc, + }, ), setThumbprintForWalletBindingId: send( - (context) => { - const { walletBindingResponse } = context; + context => { + const {walletBindingResponse} = context; const walletBindingIdKey = getBindingCertificateConstant( - walletBindingResponse.walletBindingId + walletBindingResponse.walletBindingId, ); return StoreEvents.SET( walletBindingIdKey, - walletBindingResponse.thumbprint + walletBindingResponse.thumbprint, ); }, { - to: (context) => context.serviceRefs.store, - } + to: context => context.serviceRefs.store, + }, ), removedVc: send( @@ -899,52 +884,44 @@ export const vcItemMachine = type: 'REFRESH_MY_VCS', }), { - to: (context) => context.serviceRefs.vc, - } + to: context => context.serviceRefs.vc, + }, ), requestVcContext: send( - (context) => ({ + context => ({ type: 'GET_VC_ITEM', - vcKey: VC_ITEM_STORE_KEY(context), + vcMetadata: new VCMetadata(context), }), { - to: (context) => context.serviceRefs.vc, - } + to: context => context.serviceRefs.vc, + }, ), requestStoredContext: send( - (context) => StoreEvents.GET(VC_ITEM_STORE_KEY(context)), + context => StoreEvents.GET(new VCMetadata(context).getVcKey()), { - to: (context) => context.serviceRefs.store, - } + to: context => context.serviceRefs.store, + }, ), storeContext: send( - (context) => { - const { serviceRefs, ...data } = context; + context => { + const {serviceRefs, ...data} = context; data.credentialRegistry = MIMOTO_BASE_URL; - return StoreEvents.SET(VC_ITEM_STORE_KEY(context), data); + return StoreEvents.SET(new VCMetadata(context).getVcKey(), data); }, { - to: (context) => context.serviceRefs.store, - } + to: context => context.serviceRefs.store, + }, ), - editVcKey: send((context) => { - const { serviceRefs, ...data } = context; - return StoreEvents.SET( - VC_ITEM_STORE_KEY_AFTER_DOWNLOAD(context), - data - ); - }), - setTag: model.assign({ tag: (_, event) => event.tag, }), incrementDownloadCounter: model.assign({ - downloadCounter: ({ downloadCounter }) => downloadCounter + 1, + downloadCounter: ({downloadCounter}) => downloadCounter + 1, }), setMaxDownloadCount: model.assign({ @@ -958,28 +935,28 @@ export const vcItemMachine = }), storeTag: send( - (context) => { - const { serviceRefs, ...data } = context; - return StoreEvents.SET(VC_ITEM_STORE_KEY(context), data); + context => { + const {serviceRefs, ...data} = context; + return StoreEvents.SET(new VCMetadata(context).getVcKey(), data); }, - { to: (context) => context.serviceRefs.store } + {to: context => context.serviceRefs.store}, ), setCredential: model.assign((context, event) => { switch (event.type) { case 'STORE_RESPONSE': - return { ...context, ...event.response }; + return {...context, ...event.response}; case 'GET_VC_RESPONSE': case 'CREDENTIAL_DOWNLOADED': - return { ...context, ...event.vc }; + return {...context, ...event.vc}; } }), logDownloaded: send( - (context) => { - const { serviceRefs, ...data } = context; + context => { + const {serviceRefs, ...data} = context; return ActivityLogEvents.LOG_ACTIVITY({ - _vcKey: VC_ITEM_STORE_KEY(data), + _vcKey: VCMetadata.fromVC(data).getVcKey(), type: 'VC_DOWNLOADED', timestamp: Date.now(), deviceName: '', @@ -987,65 +964,65 @@ export const vcItemMachine = }); }, { - to: (context) => context.serviceRefs.activityLog, - } + to: context => context.serviceRefs.activityLog, + }, ), logWalletBindingSuccess: send( - (context, event) => + context => ActivityLogEvents.LOG_ACTIVITY({ - _vcKey: VC_ITEM_STORE_KEY(context), + _vcKey: new VCMetadata(context).getVcKey(), type: 'WALLET_BINDING_SUCCESSFULL', timestamp: Date.now(), deviceName: '', vcLabel: context.tag || context.id, }), { - to: (context) => context.serviceRefs.activityLog, - } + to: context => context.serviceRefs.activityLog, + }, ), logWalletBindingFailure: send( - (context, event) => + context => ActivityLogEvents.LOG_ACTIVITY({ - _vcKey: VC_ITEM_STORE_KEY(context), + _vcKey: new VCMetadata(context).getVcKey(), type: 'WALLET_BINDING_FAILURE', timestamp: Date.now(), deviceName: '', vcLabel: context.tag || context.id, }), { - to: (context) => context.serviceRefs.activityLog, - } + to: context => context.serviceRefs.activityLog, + }, ), logRevoked: send( - (context) => + context => ActivityLogEvents.LOG_ACTIVITY({ - _vcKey: VC_ITEM_STORE_KEY(context), + _vcKey: new VCMetadata(context).getVcKey(), type: 'VC_REVOKED', timestamp: Date.now(), deviceName: '', vcLabel: context.tag || context.id, }), { - to: (context) => context.serviceRefs.activityLog, - } + to: context => context.serviceRefs.activityLog, + }, ), revokeVID: send( - (context) => { + context => { return StoreEvents.REMOVE( MY_VCS_STORE_KEY, - VC_ITEM_STORE_KEY(context) + new VCMetadata(context).getVcKey(), ); }, { - to: (context) => context.serviceRefs.store, - } + to: context => context.serviceRefs.store, + }, ), - markVcValid: assign((context) => { + markVcValid: assign(context => { return { ...context, isVerified: true, @@ -1057,14 +1034,14 @@ export const vcItemMachine = transactionId: () => String(new Date().valueOf()).substring(3, 13), }), - clearTransactionId: assign({ transactionId: '' }), + clearTransactionId: assign({transactionId: ''}), setOtp: model.assign({ otp: (_, event) => event.otp, }), setVcKey: model.assign({ - vcKey: (_, event) => event.vcKey, + vcMetadata: (_, event) => event.vcMetadata, }), setOtpError: assign({ @@ -1072,10 +1049,10 @@ export const vcItemMachine = (event as ErrorPlatformEvent).data.message, }), - clearOtp: assign({ otp: '' }), + clearOtp: assign({otp: ''}), setLock: assign({ - locked: (context) => !context.locked, + locked: context => !context.locked, }), setRevoke: assign({ @@ -1083,44 +1060,47 @@ export const vcItemMachine = }), storeLock: send( - (context) => { - const { serviceRefs, ...data } = context; - return StoreEvents.SET(VC_ITEM_STORE_KEY(context), data); + context => { + const {serviceRefs, ...data} = context; + return StoreEvents.SET(new VCMetadata(context).getVcKey(), data); }, - { to: (context) => context.serviceRefs.store } + {to: context => context.serviceRefs.store}, ), removeVcItem: send( - (_context, event) => { - return StoreEvents.REMOVE(MY_VCS_STORE_KEY, _context.vcKey); + _context => { + return StoreEvents.REMOVE( + MY_VCS_STORE_KEY, + _context.vcMetadata.getVcKey(), + ); }, - { to: (context) => context.serviceRefs.store } + {to: context => context.serviceRefs.store}, ), logVCremoved: send( (context, _) => ActivityLogEvents.LOG_ACTIVITY({ - _vcKey: VC_ITEM_STORE_KEY(context), + _vcKey: new VCMetadata(context).getVcKey(), type: 'VC_REMOVED', timestamp: Date.now(), deviceName: '', vcLabel: context.id, }), { - to: (context) => context.serviceRefs.activityLog, - } + to: context => context.serviceRefs.activityLog, + }, ), }, services: { - checkDownloadExpiryLimit: async (context) => { + checkDownloadExpiryLimit: async context => { var resp = await getAllConfigurations(); const maxLimit: number = resp.vcDownloadMaxRetry; const vcDownloadPoolInterval: number = resp.vcDownloadPoolInterval; console.log(maxLimit); if (maxLimit <= context.downloadCounter) { throw new Error( - 'Download limit expired for request id: ' + context.requestId + 'Download limit expired for request id: ' + context.requestId, ); } @@ -1132,7 +1112,7 @@ export const vcItemMachine = return downloadProps; }, - addWalletBindnigId: async (context) => { + addWalletBindnigId: async context => { const response = await request( 'POST', '/residentmobileapp/wallet-binding', @@ -1152,12 +1132,12 @@ export const vcItemMachine = }, ], }, - } + }, ); const certificate = response.response.certificate; await savePrivateKey( getBindingCertificateConstant(context.id), - certificate + certificate, ); const walletResponse: WalletBindingResponse = { @@ -1169,10 +1149,10 @@ export const vcItemMachine = return walletResponse; }, - updatePrivateKey: async (context) => { + updatePrivateKey: async context => { const hasSetPrivateKey: boolean = await savePrivateKey( context.walletBindingResponse.walletBindingId, - context.privateKey + context.privateKey, ); if (!hasSetPrivateKey) { throw new Error('Could not store private key in keystore.'); @@ -1180,7 +1160,7 @@ export const vcItemMachine = return ''; }, - generateKeyPair: async (context) => { + generateKeyPair: async context => { if (!isCustomSecureKeystore()) { return await generateKeys(); } @@ -1188,11 +1168,11 @@ export const vcItemMachine = return SecureKeystore.generateKeyPair( context.id, isBiometricsEnabled, - 0 + 0, ); }, - requestBindingOtp: async (context) => { + requestBindingOtp: async context => { const response = await request( 'POST', '/residentmobileapp/binding-otp', @@ -1202,24 +1182,24 @@ export const vcItemMachine = individualId: context.id, otpChannels: ['EMAIL', 'PHONE'], }, - } + }, ); if (response.response == null) { throw new Error('Could not process request'); } }, - checkStatus: (context) => (callback, onReceive) => { + checkStatus: context => (callback, onReceive) => { const pollInterval = setInterval( () => callback(model.events.POLL()), - context.downloadInterval + context.downloadInterval, ); - onReceive(async (event) => { + onReceive(async event => { if (event.type === 'POLL_STATUS') { const response = await request( 'GET', - `/residentmobileapp/credentialshare/request/status/${context.requestId}` + `/residentmobileapp/credentialshare/request/status/${context.requestId}`, ); switch (response.response?.statusCode) { case 'NEW': @@ -1235,13 +1215,13 @@ export const vcItemMachine = return () => clearInterval(pollInterval); }, - downloadCredential: (context) => (callback, onReceive) => { + downloadCredential: context => (callback, onReceive) => { const pollInterval = setInterval( () => callback(model.events.POLL()), - context.downloadInterval + context.downloadInterval, ); - onReceive(async (event) => { + onReceive(async event => { if (event.type === 'POLL_DOWNLOAD') { const response: CredentialDownloadResponse = await request( 'POST', @@ -1249,7 +1229,7 @@ export const vcItemMachine = { individualId: context.id, requestId: context.requestId, - } + }, ); callback( @@ -1267,7 +1247,7 @@ export const vcItemMachine = locked: context.locked, walletBindingResponse: null, credentialRegistry: '', - }) + }), ); } }); @@ -1275,11 +1255,11 @@ export const vcItemMachine = return () => clearInterval(pollInterval); }, - verifyCredential: async (context) => { + verifyCredential: async context => { return verifyCredential(context.verifiableCredential); }, - requestOtp: async (context) => { + requestOtp: async context => { try { return request('POST', '/residentmobileapp/req/otp', { individualId: context.id, @@ -1292,7 +1272,7 @@ export const vcItemMachine = } }, - requestLock: async (context) => { + requestLock: async context => { let response = null; if (context.locked) { response = await request( @@ -1305,7 +1285,7 @@ export const vcItemMachine = transactionID: context.transactionId, authType: ['bio'], unlockForSeconds: '120', - } + }, ); } else { response = await request( @@ -1317,13 +1297,13 @@ export const vcItemMachine = otp: context.otp, transactionID: context.transactionId, authType: ['bio'], - } + }, ); } return response.response; }, - requestRevoke: async (context) => { + requestRevoke: async context => { try { return request('PATCH', `/residentmobileapp/vid/${context.id}`, { transactionID: context.transactionId, @@ -1346,39 +1326,37 @@ export const vcItemMachine = return vc?.credential != null && vc?.verifiableCredential != null; }, - isDownloadAllowed: (_context, event) => { + isDownloadAllowed: _context => { return _context.downloadCounter <= _context.maxDownloadCount; }, - isVcValid: (context) => { + isVcValid: context => { return context.isVerified; }, isCustomSecureKeystore: () => isCustomSecureKeystore(), }, - } + }, ); export const createVcItemMachine = ( serviceRefs: AppServices, - vcKey: string + vcMetadata: VCMetadata, ) => { - const [, idType, hashedId, requestId, isPinned, id] = vcKey.split(':'); return vcItemMachine.withContext({ ...vcItemMachine.context, serviceRefs, - id, - idType: idType as VcIdType, - requestId, - isPinned: isPinned == 'true' ? true : false, - hashedId, + id: vcMetadata.id, + idType: vcMetadata.idType as VcIdType, + requestId: vcMetadata.requestId, + isPinned: vcMetadata.isPinned, }); }; type State = StateFrom; export function selectVc(state: State) { - const { serviceRefs, ...data } = state.context; + const {serviceRefs, ...data} = state.context; return data; } diff --git a/metro.config.js b/metro.config.js index 54801fcf..ac4bd3b6 100644 --- a/metro.config.js +++ b/metro.config.js @@ -1,10 +1,10 @@ // Learn more https://docs.expo.io/guides/customizing-metro -const { getDefaultConfig } = require('expo/metro-config'); +const {getDefaultConfig} = require('expo/metro-config'); // extra config is needed to enable `react-native-svg-transformer` module.exports = (async () => { const { - resolver: { sourceExts, assetExts }, + resolver: {sourceExts, assetExts}, } = await getDefaultConfig(__dirname); return { transformer: { @@ -12,7 +12,7 @@ module.exports = (async () => { assetPlugins: ['expo-asset/tools/hashAssetFiles'], }, resolver: { - assetExts: assetExts.filter((ext) => ext !== 'svg'), + assetExts: assetExts.filter(ext => ext !== 'svg'), sourceExts: [...sourceExts, 'svg'], }, }; diff --git a/screens/Home/MyVcs/AddVcModalMachine.ts b/screens/Home/MyVcs/AddVcModalMachine.ts index a980ea3d..858c90b5 100644 --- a/screens/Home/MyVcs/AddVcModalMachine.ts +++ b/screens/Home/MyVcs/AddVcModalMachine.ts @@ -1,4 +1,4 @@ -import { TextInput } from 'react-native'; +import {TextInput} from 'react-native'; import { assign, DoneInvokeEvent, @@ -7,16 +7,11 @@ import { sendParent, StateFrom, } from 'xstate'; -import { createModel } from 'xstate/lib/model'; -import { BackendResponseError, request } from '../../../shared/request'; -import { - argon2iConfigForUinVid, - argon2iSalt, - VC_ITEM_STORE_KEY, -} from '../../../shared/constants'; -import { VcIdType } from '../../../types/vc'; +import {createModel} from 'xstate/lib/model'; +import {BackendResponseError, request} from '../../../shared/request'; +import {VcIdType} from '../../../types/vc'; import i18n from '../../../i18n'; -import { hashData } from '../../../shared/commonUtil'; +import {VCMetadata} from '../../../shared/VCMetadata'; const model = createModel( { @@ -29,19 +24,18 @@ const model = createModel( transactionId: '', requestId: '', isPinned: false, - hashedId: '', }, { events: { - INPUT_ID: (id: string) => ({ id }), - INPUT_OTP: (otp: string) => ({ otp }), + INPUT_ID: (id: string) => ({id}), + INPUT_OTP: (otp: string) => ({otp}), RESEND_OTP: () => ({}), VALIDATE_INPUT: () => ({}), - READY: (idInputRef: TextInput) => ({ idInputRef }), + READY: (idInputRef: TextInput) => ({idInputRef}), DISMISS: () => ({}), - SELECT_ID_TYPE: (idType: VcIdType) => ({ idType }), + SELECT_ID_TYPE: (idType: VcIdType) => ({idType}), }, - } + }, ); export const AddVcModalEvents = model.events; @@ -212,7 +206,7 @@ export const AddVcModalMachine = onDone: [ { actions: 'setRequestId', - target: 'calculatingHashedId', + target: 'done', }, ], onError: [ @@ -228,18 +222,9 @@ export const AddVcModalMachine = ], }, }, - calculatingHashedId: { - invoke: { - src: 'calculateHashedId', - onDone: { - actions: 'setHashedId', - target: 'done', - }, - }, - }, done: { type: 'final', - data: (context) => VC_ITEM_STORE_KEY(context), + data: context => new VCMetadata(context), }, }, }, @@ -294,22 +279,17 @@ export const AddVcModalMachine = }, }), - clearId: model.assign({ id: '' }), + clearId: model.assign({id: ''}), - setHashedId: model.assign({ - hashedId: (_context, event) => - (event as DoneInvokeEvent).data, - }), - - clearIdError: model.assign({ idError: '' }), + clearIdError: model.assign({idError: ''}), setIdErrorEmpty: model.assign({ - idError: () => i18n.t('errors.input.empty', { ns: 'AddVcModal' }), + idError: () => i18n.t('errors.input.empty', {ns: 'AddVcModal'}), }), setIdErrorWrongFormat: model.assign({ idError: () => - i18n.t('errors.input.invalidFormat', { ns: 'AddVcModal' }), + i18n.t('errors.input.invalidFormat', {ns: 'AddVcModal'}), }), setOtpError: assign({ @@ -335,13 +315,13 @@ export const AddVcModalMachine = idInputRef: null, }), - clearOtp: assign({ otp: '' }), + clearOtp: assign({otp: ''}), - focusInput: (context) => context.idInputRef.focus(), + focusInput: context => context.idInputRef.focus(), }, services: { - requestOtp: async (context) => { + requestOtp: async context => { return request('POST', '/residentmobileapp/req/otp', { id: 'mosip.identity.otp.internal', individualId: context.id, @@ -353,9 +333,9 @@ export const AddVcModalMachine = }); }, - requestCredential: async (context) => { + requestCredential: async context => { // force wait to fix issue with hanging overlay - await new Promise((resolve) => setTimeout(resolve, 1000)); + await new Promise(resolve => setTimeout(resolve, 1000)); const response = await request( 'POST', @@ -365,27 +345,16 @@ export const AddVcModalMachine = individualIdType: context.idType, otp: context.otp, transactionID: context.transactionId, - } + }, ); return response.response.requestId; }, - - calculateHashedId: async (context) => { - const value = context.id; - const hashedid = await hashData( - value, - argon2iSalt, - argon2iConfigForUinVid - ); - context.hashedId = hashedid; - return hashedid; - }, }, guards: { - isEmptyId: ({ id }) => !id || !id.length, + isEmptyId: ({id}) => !id || !id.length, - isWrongIdFormat: ({ idType, id }) => { + isWrongIdFormat: ({idType, id}) => { const validIdType = idType === 'UIN' ? id.length === 10 : id.length === 16; return !(/^\d{10,16}$/.test(id) && validIdType); @@ -393,10 +362,10 @@ export const AddVcModalMachine = isIdInvalid: (_context, event: unknown) => ['IDA-MLC-009', 'RES-SER-29', 'IDA-MLC-018'].includes( - (event as BackendResponseError).name + (event as BackendResponseError).name, ), }, - } + }, ); type State = StateFrom; diff --git a/screens/Home/MyVcs/AddVcModalMachine.typegen.ts b/screens/Home/MyVcs/AddVcModalMachine.typegen.ts index 7cd1289c..fd84d58b 100644 --- a/screens/Home/MyVcs/AddVcModalMachine.typegen.ts +++ b/screens/Home/MyVcs/AddVcModalMachine.typegen.ts @@ -2,7 +2,7 @@ export interface Typegen0 { '@@xstate/typegen': true; - 'internalEvents': { + internalEvents: { 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]': { type: 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]'; data: unknown; @@ -28,21 +28,21 @@ export interface Typegen0 { 'xstate.after(100)#AddVcModal.acceptingIdInput.focusing': { type: 'xstate.after(100)#AddVcModal.acceptingIdInput.focusing'; }; - 'xstate.init': { type: 'xstate.init' }; + 'xstate.init': {type: 'xstate.init'}; }; - 'invokeSrcNameMap': { + invokeSrcNameMap: { requestCredential: 'done.invoke.AddVcModal.requestingCredential:invocation[0]'; requestOtp: | 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]' | 'done.invoke.AddVcModal.acceptingOtpInput.resendOTP:invocation[0]'; }; - 'missingImplementations': { + missingImplementations: { actions: never; delays: never; guards: never; services: never; }; - 'eventsCausingActions': { + eventsCausingActions: { clearId: 'SELECT_ID_TYPE'; clearIdError: 'INPUT_ID' | 'SELECT_ID_TYPE' | 'VALIDATE_INPUT'; clearOtp: @@ -79,17 +79,17 @@ export interface Typegen0 { | 'error.platform.AddVcModal.requestingCredential:invocation[0]' | 'xstate.init'; }; - 'eventsCausingDelays': {}; - 'eventsCausingGuards': { + eventsCausingDelays: {}; + eventsCausingGuards: { isEmptyId: 'VALIDATE_INPUT'; isIdInvalid: 'error.platform.AddVcModal.requestingCredential:invocation[0]'; isWrongIdFormat: 'VALIDATE_INPUT'; }; - 'eventsCausingServices': { + eventsCausingServices: { requestCredential: 'INPUT_OTP'; requestOtp: 'RESEND_OTP' | 'VALIDATE_INPUT'; }; - 'matchesStates': + matchesStates: | 'acceptingIdInput' | 'acceptingIdInput.focusing' | 'acceptingIdInput.idle' @@ -111,8 +111,8 @@ export interface Typegen0 { | 'invalid' | 'rendering' | 'requestingOtp' - | { invalid?: 'backend' | 'empty' | 'format' }; + | {invalid?: 'backend' | 'empty' | 'format'}; acceptingOtpInput?: 'idle' | 'resendOTP'; }; - 'tags': never; + tags: never; } diff --git a/screens/Home/MyVcs/HistoryTab.tsx b/screens/Home/MyVcs/HistoryTab.tsx index 12d65e99..5d86ad57 100644 --- a/screens/Home/MyVcs/HistoryTab.tsx +++ b/screens/Home/MyVcs/HistoryTab.tsx @@ -8,7 +8,7 @@ import {ActorRefFrom} from 'xstate'; import {vcItemMachine} from '../../../machines/vcItem'; import {useKebabPopUp} from '../../../components/KebabPopUpController'; import {Theme} from '../../../components/ui/styleUtils'; -import {isSameVC} from '../../../shared/constants'; +import {VCMetadata} from '../../../shared/VCMetadata'; import testIDProps from '../../../shared/commonUtil'; export const HistoryTab: React.FC = props => { @@ -28,21 +28,18 @@ export const HistoryTab: React.FC = props => { - {controller.activities.map(activity => { - const vcKeyMatch = isSameVC(activity._vcKey, props.vcKey); - if (vcKeyMatch) { - return ( - - ); - } - })} + {controller.activities + .filter(activity => activity._vcKey === props.vcMetadata.getVcKey()) + .map(activity => ( + + ))} {controller.activities.length === 0 && ( = props => { export interface HistoryTabProps { testID?: string; label: string; - vcKey: string; + vcMetadata: VCMetadata; service: ActorRefFrom; } diff --git a/screens/Home/MyVcs/IdInputModal.tsx b/screens/Home/MyVcs/IdInputModal.tsx index b2d5a139..86699085 100644 --- a/screens/Home/MyVcs/IdInputModal.tsx +++ b/screens/Home/MyVcs/IdInputModal.tsx @@ -1,25 +1,25 @@ import React from 'react'; -import { Icon, Input } from 'react-native-elements'; -import { Picker } from '@react-native-picker/picker'; -import { Button, Column, Row, Text } from '../../../components/ui'; -import { Modal } from '../../../components/ui/Modal'; -import { Theme } from '../../../components/ui/styleUtils'; -import { IdInputModalProps, useIdInputModal } from './IdInputModalController'; -import { useTranslation } from 'react-i18next'; +import {Icon, Input} from 'react-native-elements'; +import {Picker} from '@react-native-picker/picker'; +import {Button, Column, Row, Text} from '../../../components/ui'; +import {Modal} from '../../../components/ui/Modal'; +import {Theme} from '../../../components/ui/styleUtils'; +import {IdInputModalProps, useIdInputModal} from './IdInputModalController'; +import {useTranslation} from 'react-i18next'; import { I18nManager, KeyboardAvoidingView, Platform, TextInput, } from 'react-native'; -import { TouchableOpacity } from 'react-native'; -import { individualId } from '../../../shared/constants'; -import { GET_INDIVIDUAL_ID } from '../../../shared/constants'; -import { MessageOverlay } from '../../../components/MessageOverlay'; +import {TouchableOpacity} from 'react-native'; +import {individualId} from '../../../shared/constants'; +import {GET_INDIVIDUAL_ID} from '../../../shared/constants'; +import {MessageOverlay} from '../../../components/MessageOverlay'; import testIDProps from '../../../shared/commonUtil'; -export const IdInputModal: React.FC = (props) => { - const { t } = useTranslation('IdInputModal'); +export const IdInputModal: React.FC = props => { + const {t} = useTranslation('IdInputModal'); const controller = useIdInputModal(props); const setIndividualID = () => { @@ -31,7 +31,7 @@ export const IdInputModal: React.FC = (props) => { GET_INDIVIDUAL_ID(''); }; - const inputLabel = t('enterId', { idType: controller.idType }); + const inputLabel = t('enterId', {idType: controller.idType}); const setIdInputRef = (node: TextInput) => !controller.idInputRef && controller.READY(node); @@ -44,7 +44,7 @@ export const IdInputModal: React.FC = (props) => { headerTitle={t('header')} headerElevation={2}> @@ -54,7 +54,7 @@ export const IdInputModal: React.FC = (props) => { style={Theme.TextStyles.retrieveIdLabel}> {t('guideLabel')} - + = props => { const {t} = useTranslation('MyVcsTab'); const controller = useMyVcsTab(props); const storeErrorTranslationPath = 'errors.savingFailed'; + const [pinned, unpinned] = groupBy( + controller.vcMetadatas, + vcMetadata => vcMetadata.isPinned, + ); + const vcMetadataOrderedByPinStatus = pinned.concat(unpinned); const getId = () => { controller.DISMISS(); @@ -64,7 +72,7 @@ export const MyVcsTab: React.FC = props => { {controller.isRequestSuccessful && } - {controller.vcKeys.length > 0 && ( + {vcMetadataOrderedByPinStatus.length > 0 && ( = props => { onRefresh={controller.REFRESH} /> }> - {controller.vcKeys.map((vcKey, index) => { - if (vcKey.split(':')[4] === 'true') { - return ( - - ); - } - })} - {controller.vcKeys.map((vcKey, index) => { - if (vcKey.split(':')[4] === 'false') { - return ( - - ); - } + {vcMetadataOrderedByPinStatus.map((vcMetadata, index) => { + const iconProps = vcMetadata.isPinned ? pinIconProps : {}; + return ( + + ); })}