mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-09 13:38:01 -05:00
[INJI-725]: Resolved merge conflicts in PR
Signed-off-by: anil_majji <majjianilkumar050@gmail.com>
This commit is contained in:
5
.env
5
.env
@@ -13,10 +13,9 @@ APPLICATION_THEME=orange
|
||||
|
||||
#environment can be changed if it is toggled
|
||||
CREDENTIAL_REGISTRY_EDIT=true
|
||||
#DataBackup can enable if it is toggled
|
||||
DATA_BACKUP=true
|
||||
DEBUG_MODE=false
|
||||
|
||||
#supported languages( en, fil, ar, hi, kn, ta)
|
||||
APPLICATION_LANGUAGE=en
|
||||
|
||||
#Card Templatization - Render Claims Dynamically.
|
||||
CARD_TEMPLATIZATION=false
|
||||
|
||||
64
.talismanrc
64
.talismanrc
@@ -2,7 +2,7 @@ fileignoreconfig:
|
||||
- filename: package.json
|
||||
checksum: 4770aabfda162fbc0b9a8c53d7dee483ce29b82c6cd3e17e81e3e628d93dbadc
|
||||
- filename: package-lock.json
|
||||
checksum: 82c06097d5f030255c1aed47bae1f2df241759612fdd0715b4f66b0e1535cbb9
|
||||
checksum: e4569b3ec846749ae56bd9ad907c6cd05c27ab111475ceb501e667ec38367609
|
||||
- filename: lib/jsonld-signatures/suites/ed255192018/ed25519.ts
|
||||
checksum: 493b6e31144116cb612c24d98b97d8adcad5609c0a52c865a6847ced0a0ddc3a
|
||||
- filename: components/PasscodeVerify.tsx
|
||||
@@ -36,17 +36,17 @@ fileignoreconfig:
|
||||
- filename: shared/telemetry/TelemetryUtils.js
|
||||
checksum: ffe9aac2dcc590b98b0d588885c088eff189504ade653a77f74b67312bfd27ad
|
||||
- filename: shared/fileStorage.ts
|
||||
checksum: f86dc7aa4a69e7109310e7ab5529a8599f38f15eb79f3f4da545aceaaf90d731
|
||||
checksum: 5c4c9bc78446e6b25bf6e6ac276918757d6275a27ba93d253338ca629ebc0240
|
||||
- filename: shared/storage.ts
|
||||
checksum: c31270346f2ef717a31168a93d0311ce6f925434eb613ec7cf86553222630cdb
|
||||
checksum: 837dda0fd0a3e160e62f10df44003d29e29dfce897550a85a8e74fe77a2a06db
|
||||
- filename: screens/Issuers/IssuersScreen.tsx
|
||||
checksum: 9c53e3770dbefe26e0de67ee4b7d5cc9c52d9823cbb136a1a5104dcb0a101071
|
||||
- filename: ios/Podfile.lock
|
||||
checksum: 2487c4e11fb1bd95032cc4511435d9420fc0dfc62f3c015d177213fa089df7f2
|
||||
checksum: 1da328d6edcd284799be962d59281b120a5bb2a6873ca6a01da321857e59a7d3
|
||||
- filename: screens/Home/IntroSlidersScreen.tsx
|
||||
checksum: 72ef913857448ef05763e52e32356faa2d1f3de8130a1c638d1897f44823031f
|
||||
- filename: shared/commonUtil.ts
|
||||
checksum: b9ff87d627c74ba1cf2f1d0bfab6c11192573c45a4c581c3beadb3c612bfe1ab
|
||||
checksum: 4a53bb615f2ea0fbf687bd7027c4c246e819dd88bc273941ed611e763d9d2356
|
||||
- filename: screens/Home/MyVcs/GetIdInputModal.tsx
|
||||
checksum: 5c736ed79a372d0ffa7c02eb33d0dc06edbbb08d120978ff287f5f06cd6c7746
|
||||
- filename: shared/openId4VCI/Utils.ts
|
||||
@@ -82,9 +82,11 @@ fileignoreconfig:
|
||||
- filename: assets/fingerprint_icon.svg
|
||||
checksum: b2d3a50ca1336f60123d96a8cc8ea663c3316ed2d8c31833bce7e393ca51695b
|
||||
- filename: machines/store.ts
|
||||
checksum: ca2328c39d2757ffebf85c0b6663da1073eb58349f2630c9418b2f16c350cbd1
|
||||
checksum: 9970da0ea685018a90f7306fb88945d135dd019439099dd56b6109d915a8a24f
|
||||
- filename: assets/Flip_Camera_Icon.svg
|
||||
checksum: 736b5a7ddb86bd4376229ce198dbf8a663e7ac89fc3311bd4f19afd4a2b36ffd
|
||||
- filename: ios/fastlane/Fastfile
|
||||
checksum: 086080bc7a04accf5094c457b5acf84d9fec5d7dfa72eaaaf02e433ecf4f996b
|
||||
- filename: assets/Finger_Print_Icon.svg
|
||||
checksum: 776d4fe4fc4b54d185ccf97daf0511b9fe2c0e0f7c1a809047020e5e8a100db6
|
||||
- filename: android/app/build.gradle
|
||||
@@ -100,7 +102,33 @@ fileignoreconfig:
|
||||
- filename: assets/Issuer_search_clearing_button.svg
|
||||
checksum: f4e8a054fc4168e08bc9e9fe3e644cebabacdfc31ef0cbe36dd281766f47df5e
|
||||
- filename: screens/Home/MyVcs/IdInputModal.tsx
|
||||
checksum: 7ee46d8ef4761c0e9b59f3e602e6e30be5f47221817c819e91ab10ca2203089f
|
||||
checksum: 7ee46d8ef4761c0e9b59f3e602e6e30be5f47221817c819e91ab10ca2203089f
|
||||
- filename: .env
|
||||
checksum: d3023fb22734e6c7bd626f24007ba7b93e791cb0bcb8a086cb8e99f04bc31c7c
|
||||
- filename: machines/backup.ts
|
||||
checksum: d4b95b075ce39ed80b119014188f8903dbb46d46dd19a3757eea2ae8a06ba7ad
|
||||
- filename: machines/backup.typegen.ts
|
||||
checksum: 4ae5dbf36353cb7568a278628256e68caded071f1e91d6e1b25ac9aefb1f81cf
|
||||
- filename: screens/Settings/BackupController.tsx
|
||||
checksum: 054665c377b4a8e246ae1bfb419ab86d0cb1c120ae92cdc27ceed9cf1f39679b
|
||||
- filename: screens/Settings/BackupViaPassword.tsx
|
||||
checksum: 5fb2f98fb8a4efeb6b691b8a29e62eba64a99e9b2129bc0af1d11bc56bdfb374
|
||||
- filename: screens/Settings/DataBackup.tsx
|
||||
checksum: 6f3eaf26a58712b3ddd36dfa49d0d608eb873455ca91374545148c23e36fd43b
|
||||
- filename: shared/constants.ts
|
||||
checksum: 062fb9bc4ba7dc7c91558caee5a4fd41ba748b0dcd108c62b50a2fdd06eaa289
|
||||
- filename: shared/commonUtil.ts
|
||||
checksum: 4f353f525ce0d2c9c7caf734aa9ce4a7a5ce8d526528a491974052256b8cbe76
|
||||
- filename: screens/Settings/BackupController.tsx
|
||||
checksum: d2a355356bcaf8f7ef3b53ba93710cec15fefd0fdf31efd779eebd2bfab61c19
|
||||
- filename: shared/constants.ts
|
||||
checksum: 3961940d2df158b6c287cb689b8841d278eae273a4e5553ae2ae612340f8b8c8
|
||||
- filename: shared/api.ts
|
||||
checksum: 96a87e2128c30d16526cb1cb91b7da58f266d8c32d830adff11ca4d04e8c459f
|
||||
- filename: machines/backupWithEncryption.ts
|
||||
checksum: 038c12d30b2312fcbd9230a1c6ddb494d2e561fe0d09741335fa80ab67e2c550
|
||||
- filename: machines/backupWithEncryption.typegen.ts
|
||||
checksum: f5f9a71082e3f30f89e98a7913535327b31b942709ee8b5efb40e18c73ddee2a
|
||||
- filename: screens/Home/MyVcs/OtpVerificationModal.tsx
|
||||
checksum: 1db1f39701019383e1e40e6ed5278177e6c9bb3d28def0935cf6d4bd9e41e63a
|
||||
- filename: screens/Home/MyVcs/GetIdInputModal.tsx
|
||||
@@ -109,4 +137,24 @@ fileignoreconfig:
|
||||
checksum: 4d155c6e5468effb0b82e62f6878f2bd7ff7e7c9540e16bdd28cb81b80672ac3
|
||||
- filename: .github/workflows/push-triggers.yml
|
||||
checksum: 4a031b46646aa982c8f40e4c7fe0bd3e05a76a6af1ff1c2de7350ba6ebf9a839
|
||||
version: ""
|
||||
- filename: screens/Home/IntroSlidersScreen.tsx
|
||||
checksum: 24b993e0406a49ace5b95dd05f7c4702220ec3d3f3fc2e774f20c023ef73822a
|
||||
- filename: .env
|
||||
checksum: 4259c7c600ef49bfca798a7807a03afe9fefb1a04efd3adf224397c18117a8c1
|
||||
- filename: ios/Podfile.lock
|
||||
checksum: f32d1580e34e354c0eb27d53d2d1c2b766b0902fc6366c407d84c24d6d8d38e3
|
||||
- filename: package-lock.json
|
||||
checksum: 2fb6ce0541ab33a7a981213911d7af60cf4c8144bf0d1587522ce22a48b104d6
|
||||
- filename: components/VC/Views/VCCardViewContent.tsx
|
||||
checksum: 02655ff3d7f8a8ea4f3664485f98c961802c598242ec44408594a2ddb721fa5e
|
||||
- filename: screens/Home/IntroSlidersScreen.tsx
|
||||
checksum: 99aad6f5e474f4d5dfa563245d7b627f5fed28a1a4fe8879cc8b8cc538a66f4e
|
||||
- filename: shared/storage.ts
|
||||
checksum: fa967759d765432926475a5534c26adfa61727c8e3334c795d7ee1e5b2dd0e1f
|
||||
- filename: shared/constants.ts
|
||||
checksum: ae922a7acafab9c42746bd54e194c44532af81122837594c397713cef6922ca0
|
||||
- filename: shared/fileStorage.ts
|
||||
checksum: 451e8873a635d955fe426d9a5fcf191f2d4fd74984a1a4518760e2d10e9e1817
|
||||
- filename: screens/Settings/BackupController.tsx
|
||||
checksum: daf682b5616e45bd7b9a7c76becc617fea56be83124c3c4db838c2046e977481
|
||||
version: ""
|
||||
@@ -89,28 +89,6 @@ public class MainActivity extends ReactActivity {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles user acceptance (or denial) of our permission request.
|
||||
*/
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
if (requestCode != REQUEST_CODE_REQUIRED_PERMISSIONS) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int grantResult : grantResults) {
|
||||
if (grantResult == PackageManager.PERMISSION_DENIED) {
|
||||
// Toast.makeText(this, R.string.error_missing_permissions, Toast.LENGTH_LONG).show();
|
||||
// connectButton.setEnabled(false);
|
||||
Log.d("Main", "Denied");
|
||||
return;
|
||||
}
|
||||
}
|
||||
recreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
|
||||
|
||||
46
components/CloudSignOn.tsx
Normal file
46
components/CloudSignOn.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import {Button, Platform, Pressable} from 'react-native';
|
||||
import {CloudStorage} from 'react-native-cloud-storage';
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import * as Google from 'expo-auth-session/providers/google';
|
||||
import {GOOGLE_ANDROID_CLIENT_ID} from 'react-native-dotenv';
|
||||
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
|
||||
// This component needs to be modified as the token is received and may need to move these logic to backup screen
|
||||
export const CloudSignOn: React.FC<CloudSignOnProps> = props => {
|
||||
const [request, response, promptAsync] = Google.useAuthRequest({
|
||||
androidClientId: GOOGLE_ANDROID_CLIENT_ID,
|
||||
scopes: ['https://www.googleapis.com/auth/drive.appdata'],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (response.type == 'success') {
|
||||
CloudStorage.setGoogleDriveAccessToken(
|
||||
response.authentication.accessToken,
|
||||
);
|
||||
}
|
||||
});
|
||||
return (
|
||||
<Button
|
||||
type="gradient"
|
||||
title={props.title}
|
||||
onPress={() => {
|
||||
CloudStorage.isCloudAvailable().then(value => {
|
||||
if (!value) {
|
||||
if (Platform.OS == 'android') {
|
||||
promptAsync();
|
||||
}
|
||||
if (Platform.OS == 'ios') {
|
||||
//todo: ask to sign in into to icloud
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export interface CloudSignOnProps {
|
||||
title: string;
|
||||
}
|
||||
22
components/ProfileIcon.tsx
Normal file
22
components/ProfileIcon.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import {Theme} from './ui/styleUtils';
|
||||
import {Icon} from 'react-native-elements';
|
||||
import {View} from 'react-native';
|
||||
import React from 'react';
|
||||
|
||||
export const ProfileIcon: React.FC = () => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 90,
|
||||
height: 90,
|
||||
borderRadius: 15,
|
||||
borderWidth: 0.3,
|
||||
borderColor: Theme.Colors.Icon,
|
||||
backgroundColor: Theme.Colors.whiteBackgroundColor,
|
||||
}}>
|
||||
<Icon name="person" color={Theme.Colors.Icon} size={40} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -15,6 +15,7 @@ import {VCMetadata} from '../../../shared/VCMetadata';
|
||||
import {format} from 'date-fns';
|
||||
import {EsignetMosipVCItemMachine} from '../../../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
|
||||
import {useVcItemController} from './VcItemController';
|
||||
import {VCCardSkeleton} from '../common/VCCardSkeleton';
|
||||
|
||||
export const MosipVCItem: React.FC<
|
||||
ExistingMosipVCItemProps | EsignetMosipVCItemProps
|
||||
@@ -42,6 +43,10 @@ export const MosipVCItem: React.FC<
|
||||
);
|
||||
}, [props.vcMetadata]);
|
||||
|
||||
if (!verifiableCredential) {
|
||||
return <VCCardSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Pressable
|
||||
|
||||
@@ -5,6 +5,7 @@ import {VerifiableCredential} from '../../../types/VC/ExistingMosipVC/vc';
|
||||
import {Row, Text} from '../../ui';
|
||||
import {Theme} from '../../ui/styleUtils';
|
||||
import {View} from 'react-native';
|
||||
import {ACTIVATION_NOT_NEEDED} from '../../../shared/openId4VCI/Utils';
|
||||
|
||||
const WalletUnverifiedIcon: React.FC = () => {
|
||||
return (
|
||||
@@ -92,7 +93,8 @@ export const MosipVCItemActivationStatus: React.FC<
|
||||
> = props => {
|
||||
return (
|
||||
<Row style={Theme.Styles.vcActivationStatusContainer}>
|
||||
{props.emptyWalletBindingId ? (
|
||||
{props.emptyWalletBindingId &&
|
||||
ACTIVATION_NOT_NEEDED.indexOf(props?.vcMetadata.issuer) === -1 ? (
|
||||
<WalletUnverifiedActivationDetails
|
||||
verifiableCredential={props.verifiableCredential}
|
||||
/>
|
||||
|
||||
@@ -4,13 +4,13 @@ import {
|
||||
ExistingMosipVCItemDetailsProps,
|
||||
MosipVCItemDetails,
|
||||
} from './MosipVCItem/MosipVCItemDetails';
|
||||
|
||||
import {CARD_TEMPLATIZATION} from 'react-native-dotenv';
|
||||
import {VCDetailView} from './Views/VCDetailView';
|
||||
import {Issuers} from '../../shared/openId4VCI/Utils';
|
||||
|
||||
export const VcDetailsContainer: React.FC<
|
||||
ExistingMosipVCItemDetailsProps | EsignetMosipVCItemDetailsProps
|
||||
> = props => {
|
||||
if (CARD_TEMPLATIZATION === 'true') return <VCDetailView {...props} />;
|
||||
if (props.vc.vcMetadata.issuer === Issuers.Sunbird)
|
||||
return <VCDetailView {...props} />;
|
||||
return <MosipVCItemDetails {...props} />;
|
||||
};
|
||||
|
||||
@@ -4,12 +4,13 @@ import {
|
||||
ExistingMosipVCItemProps,
|
||||
MosipVCItem,
|
||||
} from './MosipVCItem/MosipVCItem';
|
||||
import {CARD_TEMPLATIZATION} from 'react-native-dotenv';
|
||||
import {VCCardView} from './Views/VCCardView';
|
||||
import {Issuers} from '../../shared/openId4VCI/Utils';
|
||||
|
||||
export const VcItemContainer: React.FC<
|
||||
ExistingMosipVCItemProps | EsignetMosipVCItemProps
|
||||
> = props => {
|
||||
if (CARD_TEMPLATIZATION === 'true') return <VCCardView {...props} />;
|
||||
if (props.vcMetadata.issuer === Issuers.Sunbird)
|
||||
return <VCCardView {...props} />;
|
||||
return <MosipVCItem {...props} />;
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ export const VCCardView: React.FC<
|
||||
CARD_VIEW_DEFAULT_FIELDS,
|
||||
).then(response => {
|
||||
setWellknown(response.wellknown);
|
||||
setFields(response.fields.slice(0, 1).concat(CARD_VIEW_ADD_ON_FIELDS));
|
||||
setFields(response.fields.slice(0, 3).concat(CARD_VIEW_ADD_ON_FIELDS));
|
||||
});
|
||||
}, [verifiableCredential?.wellKnown]);
|
||||
|
||||
|
||||
@@ -11,9 +11,10 @@ import {
|
||||
isVCLoaded,
|
||||
setBackgroundColour,
|
||||
} from '../common/VCUtils';
|
||||
import VerifiedIcon from '../../VerifiedIcon';
|
||||
import {VCItemField} from '../common/VCItemField';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {getIDType} from '../../../shared/openId4VCI/Utils';
|
||||
import {VCVerification} from '../../VCVerification';
|
||||
|
||||
export const VCCardViewContent: React.FC<
|
||||
ExistingMosipVCItemContentProps | EsignetMosipVCItemContentProps
|
||||
@@ -47,7 +48,7 @@ export const VCCardViewContent: React.FC<
|
||||
]}>
|
||||
<Column>
|
||||
<Row align="space-between">
|
||||
<Row margin="5 0 0 5">
|
||||
<Row margin="0 0 0 5">
|
||||
{SvgImage.VcItemContainerProfileImage(props, props.credential)}
|
||||
<Column margin={'0 0 0 20'}>
|
||||
{fieldItemIterator(
|
||||
@@ -60,24 +61,30 @@ export const VCCardViewContent: React.FC<
|
||||
</Row>
|
||||
<Column>{props.credential ? selectableOrCheck : null}</Column>
|
||||
</Row>
|
||||
{fieldItemIterator(
|
||||
props.fields.slice(2),
|
||||
props.credential,
|
||||
props.wellknown,
|
||||
props,
|
||||
)}
|
||||
<Column margin="0 0 0 8">
|
||||
{fieldItemIterator(
|
||||
props.fields.slice(2),
|
||||
props.credential,
|
||||
props.wellknown,
|
||||
props,
|
||||
)}
|
||||
</Column>
|
||||
<Row align={'space-between'} margin="0 8 5 8">
|
||||
<VCItemField
|
||||
key={'status'}
|
||||
fieldName={t('status')}
|
||||
fieldValue={
|
||||
!isVCLoaded(props.credential, props.fields) ? null : (
|
||||
<VerifiedIcon />
|
||||
)
|
||||
}
|
||||
key={'idType'}
|
||||
fieldName={t('idType')}
|
||||
fieldValue={getIDType(props.credential)}
|
||||
wellknown={props.wellknown}
|
||||
verifiableCredential={props.credential}
|
||||
/>
|
||||
<VCItemField
|
||||
key={'status'}
|
||||
fieldName={t('status')}
|
||||
fieldValue={<VCVerification wellknown={props.wellknown} />}
|
||||
wellknown={props.wellknown}
|
||||
verifiableCredential={props.credential}
|
||||
/>
|
||||
|
||||
<Column
|
||||
testID="logo"
|
||||
style={{
|
||||
|
||||
@@ -19,7 +19,10 @@ import {
|
||||
import {WalletBindingResponse} from '../../../shared/cryptoutil/cryptoUtil';
|
||||
import {logoType} from '../../../machines/issuersMachine';
|
||||
import {SvgImage} from '../../ui/svg';
|
||||
import {getCredentialIssuersWellKnownConfig} from '../../../shared/openId4VCI/Utils';
|
||||
import {
|
||||
ACTIVATION_NOT_NEEDED,
|
||||
getCredentialIssuersWellKnownConfig,
|
||||
} from '../../../shared/openId4VCI/Utils';
|
||||
import {
|
||||
DETAIL_VIEW_ADD_ON_FIELDS,
|
||||
DETAIL_VIEW_DEFAULT_FIELDS,
|
||||
@@ -30,6 +33,7 @@ import {
|
||||
setBackgroundColour,
|
||||
} from '../common/VCUtils';
|
||||
import {ActivityIndicator} from '../../ui/ActivityIndicator';
|
||||
import {ProfileIcon} from '../../ProfileIcon';
|
||||
|
||||
const getIssuerLogo = (isOpenId4VCI: boolean, issuerLogo: logoType) => {
|
||||
if (isOpenId4VCI) {
|
||||
@@ -67,7 +71,7 @@ const getProfileImage = (
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <Icon name="person" color={Theme.Colors.Icon} size={88} />;
|
||||
return <ProfileIcon />;
|
||||
};
|
||||
|
||||
export const VCDetailView: React.FC<
|
||||
@@ -116,7 +120,6 @@ export const VCDetailView: React.FC<
|
||||
<Column crossAlign="center">
|
||||
{getProfileImage(props, verifiableCredential, isOpenId4VCI)}
|
||||
<QrCodeOverlay qrCodeDetails={String(verifiableCredential)} />
|
||||
|
||||
<Column margin="20 0 0 0">{issuerLogo}</Column>
|
||||
</Column>
|
||||
<Column align="space-evenly" margin={'0 0 0 10'} style={{flex: 1}}>
|
||||
@@ -148,7 +151,8 @@ export const VCDetailView: React.FC<
|
||||
))}
|
||||
|
||||
{props.activeTab !== 1 ? (
|
||||
props.isBindingPending ? (
|
||||
props.isBindingPending &&
|
||||
ACTIVATION_NOT_NEEDED.indexOf(props.vc.vcMetadata.issuer) === -1 ? (
|
||||
<Column style={Theme.Styles.openCardBgContainer} padding="10">
|
||||
<Column margin={'0 0 4 0'} crossAlign={'flex-start'}>
|
||||
<Icon
|
||||
|
||||
@@ -3,7 +3,6 @@ import {Column, Row} from '../../ui';
|
||||
import {ImageBackground} from 'react-native';
|
||||
import {VCItemField} from './VCItemField';
|
||||
import React from 'react';
|
||||
import {SvgImage} from '../../ui/svg';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import ShimmerPlaceHolder from 'react-native-shimmer-placeholder';
|
||||
|
||||
@@ -16,7 +15,12 @@ export const VCCardInnerSkeleton = () => {
|
||||
<Column>
|
||||
<Row margin={'0 20 10 10'}>
|
||||
<Column style={{marginTop: 10}}>
|
||||
{SvgImage.VcItemContainerProfileImage(undefined, null)}
|
||||
<ShimmerPlaceHolder
|
||||
LinearGradient={LinearGradient}
|
||||
width={80}
|
||||
height={80}
|
||||
style={{borderRadius: 5}}
|
||||
/>
|
||||
</Column>
|
||||
<Column margin={'0 0 0 20'}>
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
CredentialSubject,
|
||||
VerifiableCredential,
|
||||
} from '../../../types/VC/ExistingMosipVC/vc';
|
||||
import VerifiedIcon from '../../VerifiedIcon';
|
||||
import i18n, {getLocalizedField} from '../../../i18n';
|
||||
import {Row} from '../../ui';
|
||||
import {VCItemField} from './VCItemField';
|
||||
@@ -13,19 +12,26 @@ import {Image} from 'react-native';
|
||||
import {Theme} from '../../ui/styleUtils';
|
||||
import {SvgImage} from '../../ui/svg';
|
||||
import {CREDENTIAL_REGISTRY_EDIT} from 'react-native-dotenv';
|
||||
import {getIDType} from '../../../shared/openId4VCI/Utils';
|
||||
import {VCVerification} from '../../VCVerification';
|
||||
|
||||
export const getFieldValue = (
|
||||
verifiableCredential: VerifiableCredential,
|
||||
field: string,
|
||||
wellknown: any,
|
||||
props: any,
|
||||
) => {
|
||||
switch (field) {
|
||||
case 'status':
|
||||
return <VerifiedIcon />;
|
||||
return <VCVerification wellknown={wellknown} />;
|
||||
case 'idType':
|
||||
return i18n.t('VcDetails:nationalCard');
|
||||
return getIDType(verifiableCredential);
|
||||
case 'dateOfBirth':
|
||||
return formattedDateOfBirth(verifiableCredential);
|
||||
case 'expiresOn':
|
||||
return formattedDateTime(
|
||||
verifiableCredential?.credentialSubject.expiresOn,
|
||||
);
|
||||
case 'credentialRegistry':
|
||||
return props?.vc?.credentialRegistry;
|
||||
case 'address':
|
||||
@@ -64,9 +70,9 @@ export const setBackgroundColour = (wellknown: any) => {
|
||||
};
|
||||
|
||||
export const setTextColor = (wellknown: any) => {
|
||||
if (wellknown && wellknown.credentials_supported[0]?.display) {
|
||||
if (wellknown && wellknown?.credentials_supported[0]?.display) {
|
||||
return {
|
||||
color: wellknown.credentials_supported[0].display[0]?.text_color
|
||||
color: wellknown.credentials_supported[0]?.display[0]?.text_color
|
||||
? wellknown.credentials_supported[0].display[0].text_color
|
||||
: Theme.Colors.textValue,
|
||||
};
|
||||
@@ -105,6 +111,13 @@ function formattedDateOfBirth(verifiableCredential: any) {
|
||||
return dateOfBirth;
|
||||
}
|
||||
|
||||
function formattedDateTime(timeStamp: any) {
|
||||
if (timeStamp) {
|
||||
return new Date(timeStamp).toLocaleDateString();
|
||||
}
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
export const fieldItemIterator = (
|
||||
fields: any[],
|
||||
verifiableCredential: any,
|
||||
@@ -113,7 +126,12 @@ export const fieldItemIterator = (
|
||||
) => {
|
||||
return fields.map(field => {
|
||||
const fieldName = getFieldName(field, wellknown);
|
||||
const fieldValue = getFieldValue(verifiableCredential, field, props);
|
||||
const fieldValue = getFieldValue(
|
||||
verifiableCredential,
|
||||
field,
|
||||
wellknown,
|
||||
props,
|
||||
);
|
||||
if (
|
||||
(field === 'credentialRegistry' &&
|
||||
CREDENTIAL_REGISTRY_EDIT === 'false') ||
|
||||
@@ -126,7 +144,7 @@ export const fieldItemIterator = (
|
||||
style={{flexDirection: 'row', flex: 1}}
|
||||
align="space-between"
|
||||
let
|
||||
margin="0 8 5 8">
|
||||
margin="0 8 5 0">
|
||||
<VCItemField
|
||||
key={field}
|
||||
fieldName={fieldName}
|
||||
|
||||
29
components/VCVerification.tsx
Normal file
29
components/VCVerification.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import VerifiedIcon from './VerifiedIcon';
|
||||
import {Row, Text} from './ui';
|
||||
import {Theme} from './ui/styleUtils';
|
||||
import React from 'react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {setTextColor} from './VC/common/VCUtils';
|
||||
|
||||
export const VCVerification: React.FC = ({wellknown}: any) => {
|
||||
const {t} = useTranslation('VcDetails');
|
||||
return (
|
||||
<Row
|
||||
style={{
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginLeft: 9,
|
||||
}}>
|
||||
<VerifiedIcon />
|
||||
<Text
|
||||
testID="valid"
|
||||
numLines={1}
|
||||
color={Theme.Colors.Details}
|
||||
weight="semibold"
|
||||
size="smaller"
|
||||
style={[Theme.Styles.detailsValue, setTextColor(wellknown)]}>
|
||||
{t('valid')}
|
||||
</Text>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import {Pressable} from 'react-native';
|
||||
import {Pressable, View} from 'react-native';
|
||||
import {Theme} from '../ui/styleUtils';
|
||||
import testIDProps from '../../shared/commonUtil';
|
||||
import {Text} from '../ui';
|
||||
@@ -22,18 +22,21 @@ export const Issuer: React.FC<IssuerProps> = (props: IssuerProps) => {
|
||||
Theme.Styles.boxShadow,
|
||||
]
|
||||
}>
|
||||
{SvgImage.IssuerIcon(props)}
|
||||
|
||||
<Text
|
||||
testID={`issuerHeading-${props.testID}`}
|
||||
style={Theme.IssuersScreenStyles.issuerHeading}>
|
||||
{props.displayDetails.title}
|
||||
</Text>
|
||||
<Text
|
||||
testID={`issuerDescription-${props.testID}`}
|
||||
style={Theme.IssuersScreenStyles.issuerDescription}>
|
||||
{props.displayDetails.description}
|
||||
</Text>
|
||||
<View style={Theme.IssuersScreenStyles.issuerBoxIconContainer}>
|
||||
{SvgImage.IssuerIcon(props)}
|
||||
</View>
|
||||
<View style={Theme.IssuersScreenStyles.issuerBoxContent}>
|
||||
<Text
|
||||
testID={`issuerHeading-${props.testID}`}
|
||||
style={Theme.IssuersScreenStyles.issuerHeading}>
|
||||
{props.displayDetails.title}
|
||||
</Text>
|
||||
<Text
|
||||
testID={`issuerDescription-${props.testID}`}
|
||||
style={Theme.IssuersScreenStyles.issuerDescription}>
|
||||
{props.displayDetails.description}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import Svg, {Image} from 'react-native-svg';
|
||||
import {Theme} from './styleUtils';
|
||||
import {Icon} from 'react-native-elements';
|
||||
import {ImageBackground} from 'react-native';
|
||||
import Home from '../../assets/Home_tab_icon.svg';
|
||||
import History from '../../assets/History_tab_icon.svg';
|
||||
@@ -32,6 +31,7 @@ import {
|
||||
} from '../VC/MosipVCItem/MosipVCItemContent';
|
||||
import {VCMetadata} from '../../shared/VCMetadata';
|
||||
import {VerifiableCredential} from '../../types/VC/ExistingMosipVC/vc';
|
||||
import {ProfileIcon} from '../ProfileIcon';
|
||||
|
||||
export class SvgImage {
|
||||
static MosipLogo(props: LogoProps) {
|
||||
@@ -157,19 +157,20 @@ export class SvgImage {
|
||||
static IssuerIcon(issuer: IssuerProps) {
|
||||
return (
|
||||
<Svg
|
||||
width="78"
|
||||
height="35"
|
||||
width="40"
|
||||
height="40"
|
||||
{...testIDProps(`issuerIcon-${issuer.testID}`)}>
|
||||
<Image
|
||||
href={getIssuerLogo(issuer.displayDetails)}
|
||||
x="0"
|
||||
y="0"
|
||||
height="32"
|
||||
width="32"
|
||||
height="40"
|
||||
width="40"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
|
||||
static WarningLogo() {
|
||||
return (
|
||||
<WarningLogo
|
||||
@@ -178,6 +179,7 @@ export class SvgImage {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
static OtpVerificationIcon() {
|
||||
return (
|
||||
<OtpVerificationIcon
|
||||
@@ -186,6 +188,7 @@ export class SvgImage {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
static VcItemContainerProfileImage(
|
||||
props: ExistingMosipVCItemContentProps | EsignetMosipVCItemContentProps,
|
||||
verifiableCredential: VerifiableCredential,
|
||||
@@ -196,10 +199,12 @@ export class SvgImage {
|
||||
imageStyle={Theme.Styles.faceImage}
|
||||
source={{uri: imageUri}}
|
||||
style={Theme.Styles.closeCardImage}>
|
||||
{props.isPinned && SvgImage.pinIcon()}
|
||||
{props?.isPinned && SvgImage.pinIcon()}
|
||||
</ImageBackground>
|
||||
) : (
|
||||
<Icon name="person" color={Theme.Colors.Icon} size={88} />
|
||||
<>
|
||||
<ProfileIcon />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -215,6 +220,7 @@ export class SvgImage {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
static CameraCaptureIcon() {
|
||||
return (
|
||||
<CameraCaptureIcon
|
||||
@@ -223,9 +229,11 @@ export class SvgImage {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
static SuccessLogo() {
|
||||
return <SuccessLogo {...testIDProps('SuccessLogo')} />;
|
||||
}
|
||||
|
||||
static NoInternetConnection() {
|
||||
return (
|
||||
<NoInternetConnection {...testIDProps('noInternetConnectionImage')} />
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
import {Dimensions, I18nManager, StyleSheet, ViewStyle} from 'react-native';
|
||||
import {
|
||||
Dimensions,
|
||||
I18nManager,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
import {Spacing} from '../styleUtils';
|
||||
import {isIOS} from '../../../shared/constants';
|
||||
import Constants from 'expo-constants';
|
||||
|
||||
const Colors = {
|
||||
Black: '#000000',
|
||||
@@ -196,7 +203,6 @@ export const DefaultTheme = {
|
||||
backgroundColor: Colors.Grey6,
|
||||
borderRadius: 4,
|
||||
},
|
||||
cardDetailsContainer: {},
|
||||
bottomTabIconStyle: {
|
||||
padding: 4,
|
||||
width: Dimensions.get('window').width * 0.12,
|
||||
@@ -619,6 +625,18 @@ export const DefaultTheme = {
|
||||
borderBottomWidth: 1,
|
||||
marginTop: 10,
|
||||
},
|
||||
introSliderHeader: {
|
||||
marginTop: isIOS()
|
||||
? Constants.statusBarHeight + 40
|
||||
: StatusBar.currentHeight + 40,
|
||||
width: '100%',
|
||||
marginBottom: 50,
|
||||
},
|
||||
introSliderButton: {
|
||||
borderRadius: 10,
|
||||
height: 50,
|
||||
marginTop: -10,
|
||||
},
|
||||
}),
|
||||
QrCodeStyles: StyleSheet.create({
|
||||
magnifierZoom: {
|
||||
@@ -1272,27 +1290,40 @@ export const DefaultTheme = {
|
||||
issuerBoxContainer: {
|
||||
margin: 5,
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
borderRadius: 6,
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-evenly',
|
||||
flexDirection: 'column',
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 10,
|
||||
backgroundColor: Colors.White,
|
||||
},
|
||||
issuerBoxContainerPressed: {
|
||||
margin: 5,
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
borderRadius: 6,
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-evenly',
|
||||
flexDirection: 'column',
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 10,
|
||||
backgroundColor: Colors.Grey,
|
||||
},
|
||||
issuerBoxContent: {
|
||||
flex: 1,
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingLeft: 15,
|
||||
},
|
||||
issuerBoxIconContainer: {
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
issuersSearchSubText: {
|
||||
marginBottom: 14,
|
||||
fontSize: 12,
|
||||
marginHorizontal: 9,
|
||||
},
|
||||
issuerHeading: {
|
||||
fontFamily: 'Inter_600SemiBold',
|
||||
fontSize: 14,
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
import {Dimensions, I18nManager, StyleSheet, ViewStyle} from 'react-native';
|
||||
import {
|
||||
Dimensions,
|
||||
I18nManager,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
import {Spacing} from '../styleUtils';
|
||||
import {isIOS} from '../../../shared/constants';
|
||||
import Constants from 'expo-constants';
|
||||
|
||||
const Colors = {
|
||||
Black: '#231F20',
|
||||
@@ -622,6 +629,18 @@ export const PurpleTheme = {
|
||||
borderBottomWidth: 1,
|
||||
marginTop: 10,
|
||||
},
|
||||
introSliderHeader: {
|
||||
marginTop: isIOS()
|
||||
? Constants.statusBarHeight + 40
|
||||
: StatusBar.currentHeight + 40,
|
||||
width: '100%',
|
||||
marginBottom: 50,
|
||||
},
|
||||
introSliderButton: {
|
||||
borderRadius: 10,
|
||||
height: 50,
|
||||
marginTop: -10,
|
||||
},
|
||||
}),
|
||||
QrCodeStyles: StyleSheet.create({
|
||||
magnifierZoom: {
|
||||
@@ -1276,27 +1295,41 @@ export const PurpleTheme = {
|
||||
issuerBoxContainer: {
|
||||
margin: 5,
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
borderRadius: 6,
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-evenly',
|
||||
flexDirection: 'column',
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 10,
|
||||
backgroundColor: Colors.White,
|
||||
},
|
||||
issuerBoxContainerPressed: {
|
||||
margin: 5,
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
|
||||
borderRadius: 6,
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-evenly',
|
||||
flexDirection: 'column',
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 10,
|
||||
backgroundColor: Colors.Grey,
|
||||
},
|
||||
issuerBoxContent: {
|
||||
flex: 1,
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingLeft: 15,
|
||||
},
|
||||
issuerBoxIconContainer: {
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
issuersSearchSubText: {
|
||||
marginBottom: 14,
|
||||
fontSize: 12,
|
||||
marginHorizontal: 9,
|
||||
},
|
||||
issuerHeading: {
|
||||
fontFamily: 'Inter_600SemiBold',
|
||||
fontSize: 14,
|
||||
|
||||
@@ -12,7 +12,7 @@ PODS:
|
||||
- BiometricSdk (0.5.9):
|
||||
- TensorFlowLiteObjC (= 2.12.0)
|
||||
- boost (1.76.0)
|
||||
- BVLinearGradient (2.8.2):
|
||||
- BVLinearGradient (2.8.3):
|
||||
- React-Core
|
||||
- CatCrypto (0.3.2)
|
||||
- CrcSwift (0.0.3)
|
||||
@@ -483,8 +483,16 @@ PODS:
|
||||
- React
|
||||
- RNSVG (13.4.0):
|
||||
- React-Core
|
||||
- RNZipArchive (6.1.0):
|
||||
- React-Core
|
||||
- RNZipArchive/Core (= 6.1.0)
|
||||
- SSZipArchive (~> 2.2)
|
||||
- RNZipArchive/Core (6.1.0):
|
||||
- React-Core
|
||||
- SSZipArchive (~> 2.2)
|
||||
- secure-keystore (0.1.5):
|
||||
- React-Core
|
||||
- SSZipArchive (2.4.3)
|
||||
- TensorFlowLiteC (2.12.0):
|
||||
- TensorFlowLiteC/Core (= 2.12.0)
|
||||
- TensorFlowLiteC/Core (2.12.0)
|
||||
@@ -586,6 +594,7 @@ DEPENDENCIES:
|
||||
- RNScreens (from `../node_modules/react-native-screens`)
|
||||
- RNSecureRandom (from `../node_modules/react-native-securerandom`)
|
||||
- RNSVG (from `../node_modules/react-native-svg`)
|
||||
- RNZipArchive (from `../node_modules/react-native-zip-archive`)
|
||||
- "secure-keystore (from `../node_modules/@mosip/secure-keystore`)"
|
||||
- "tuvali (from `../node_modules/@mosip/tuvali`)"
|
||||
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
||||
@@ -603,6 +612,7 @@ SPEC REPOS:
|
||||
- MMKV
|
||||
- MMKVCore
|
||||
- ReachabilitySwift
|
||||
- SSZipArchive
|
||||
- TensorFlowLiteC
|
||||
- TensorFlowLiteObjC
|
||||
- ZXingObjC
|
||||
@@ -768,6 +778,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-securerandom"
|
||||
RNSVG:
|
||||
:path: "../node_modules/react-native-svg"
|
||||
RNZipArchive:
|
||||
:path: "../node_modules/react-native-zip-archive"
|
||||
secure-keystore:
|
||||
:path: "../node_modules/@mosip/secure-keystore"
|
||||
tuvali:
|
||||
@@ -781,7 +793,7 @@ SPEC CHECKSUMS:
|
||||
biometric-sdk-react-native: d2a3a1279013cc4a7514a1b43fe557eb76e4e4c1
|
||||
BiometricSdk: 303e7329404ea4d922dc14108449d10d21574f77
|
||||
boost: 64032b9e9b938fda23325e68a3771f0fabf414dc
|
||||
BVLinearGradient: 916632041121a658c704df89d99f04acb038de0f
|
||||
BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3
|
||||
CatCrypto: a477899b6be4954e75be4897e732da098cc0a5a8
|
||||
CrcSwift: f85dea6b41dddb5f98bb3743fd777ce58b77bc2e
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
@@ -867,7 +879,9 @@ SPEC CHECKSUMS:
|
||||
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
|
||||
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
|
||||
RNSVG: 07dbd870b0dcdecc99b3a202fa37c8ca163caec2
|
||||
RNZipArchive: ef9451b849c45a29509bf44e65b788829ab07801
|
||||
secure-keystore: 21c03ba81520aefa99621383770ce00b3e306c72
|
||||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
TensorFlowLiteC: 20785a69299185a379ba9852b6625f00afd7984a
|
||||
TensorFlowLiteObjC: 9a46a29a76661c513172cfffd3bf712b11ef25c3
|
||||
tuvali: 9c3aad61844f6fcbd48ec7967cd6805418c3f8da
|
||||
@@ -876,4 +890,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 01f58b130fa221dabb14b2d82d981ef24dcaba53
|
||||
|
||||
COCOAPODS: 1.14.3
|
||||
COCOAPODS: 1.12.1
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"id": "بطاقة تعريف",
|
||||
"qrCodeHeader": "رمز الاستجابة السريعة",
|
||||
"nationalCard": "البطاقة الوطنية",
|
||||
"insuranceCard": "بطاقة التأمين",
|
||||
"uin": "UIN",
|
||||
"vid": "VID",
|
||||
"enableVerification": "تفعيل",
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"id": "ID",
|
||||
"qrCodeHeader": "QR Code",
|
||||
"nationalCard": "National Card",
|
||||
"insuranceCard": "Insurance Card",
|
||||
"uin": "UIN",
|
||||
"vid": "VID",
|
||||
"enableVerification": "Activate",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"id": "Id",
|
||||
"qrCodeHeader": "QR Code",
|
||||
"nationalCard": "Pambansang Kard",
|
||||
"insuranceCard": "Insurance Card",
|
||||
"uin": "UIN",
|
||||
"vid": "VID",
|
||||
"enableVerification": "I-activate",
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"id": "पहचान",
|
||||
"qrCodeHeader": "क्यू आर संहिता",
|
||||
"nationalCard": "राष्ट्रीय कार्ड",
|
||||
"insuranceCard": "बीमा कार्ड",
|
||||
"uin": "UIN",
|
||||
"vid": "VID",
|
||||
"enableVerification": "सक्रिय",
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"id": "ಐಡಿ",
|
||||
"qrCodeHeader": "QR ಕೋಡ್",
|
||||
"nationalCard": "ರಾಷ್ಟ್ರೀಯ ಕಾರ್ಡ್",
|
||||
"insuranceCard": "ವಿಮಾ ಕಾರ್ಡ್",
|
||||
"uin": "UIN",
|
||||
"vid": "VID",
|
||||
"enableVerification": "ಸಕ್ರಿಯಗೊಳಿಸಿ",
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"id": "ஐடி",
|
||||
"qrCodeHeader": "க்யு ஆர் குறியீடு",
|
||||
"nationalCard": "தேசிய அட்டை",
|
||||
"insuranceCard": "காப்பீட்டு அட்டை",
|
||||
"uin": "UIN",
|
||||
"vid": "VID",
|
||||
"enableVerification": "செயல்படுத்த",
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
SETTINGS_STORE_KEY,
|
||||
} from '../shared/constants';
|
||||
import {logState} from '../shared/commonUtil';
|
||||
import {backupMachine, createBackupMachine} from './backup';
|
||||
|
||||
const model = createModel(
|
||||
{
|
||||
@@ -260,6 +261,11 @@ export const appMachine = model.createMachine(
|
||||
settingsMachine.id,
|
||||
);
|
||||
|
||||
serviceRefs.backup = spawn(
|
||||
createBackupMachine(serviceRefs),
|
||||
backupMachine.id,
|
||||
);
|
||||
|
||||
serviceRefs.activityLog = spawn(
|
||||
createActivityLogMachine(serviceRefs),
|
||||
activityLogMachine.id,
|
||||
@@ -293,6 +299,7 @@ export const appMachine = model.createMachine(
|
||||
context.serviceRefs.settings.subscribe(logState);
|
||||
context.serviceRefs.activityLog.subscribe(logState);
|
||||
context.serviceRefs.scan.subscribe(logState);
|
||||
context.serviceRefs.backup.subscribe(logState);
|
||||
|
||||
if (isAndroid()) {
|
||||
context.serviceRefs.request.subscribe(logState);
|
||||
|
||||
219
machines/backup.ts
Normal file
219
machines/backup.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import {EventFrom, StateFrom, send} from 'xstate';
|
||||
import {createModel} from 'xstate/lib/model';
|
||||
import {AppServices} from '../shared/GlobalContext';
|
||||
import {StoreEvents} from './store';
|
||||
import Storage, {
|
||||
isMinimumLimitForBackupReached,
|
||||
writeToBackupFile,
|
||||
} from '../shared/storage';
|
||||
import {compressAndRemoveFile} from '../shared/fileStorage';
|
||||
import {
|
||||
getEndEventData,
|
||||
getImpressionEventData,
|
||||
getStartEventData,
|
||||
sendEndEvent,
|
||||
sendImpressionEvent,
|
||||
sendStartEvent,
|
||||
} from '../shared/telemetry/TelemetryUtils';
|
||||
import {TelemetryConstants} from '../shared/telemetry/TelemetryConstants';
|
||||
|
||||
const model = createModel(
|
||||
{
|
||||
serviceRefs: {} as AppServices,
|
||||
dataFromStorage: {},
|
||||
fileName: '',
|
||||
},
|
||||
{
|
||||
events: {
|
||||
DATA_BACKUP: () => ({}),
|
||||
OK: () => ({}),
|
||||
FETCH_DATA: () => ({}),
|
||||
DISMISS: () => ({}),
|
||||
STORE_RESPONSE: (response: unknown) => ({response}),
|
||||
FILE_NAME: (filename: string) => ({filename}),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const BackupEvents = model.events;
|
||||
|
||||
export const backupMachine = model.createMachine(
|
||||
{
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
tsTypes: {} as import('./backup.typegen').Typegen0,
|
||||
schema: {
|
||||
context: model.initialContext,
|
||||
events: {} as EventFrom<typeof model>,
|
||||
},
|
||||
id: 'backup',
|
||||
initial: 'init',
|
||||
states: {
|
||||
init: {
|
||||
on: {
|
||||
DATA_BACKUP: [
|
||||
{
|
||||
target: 'backingUp',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
backingUp: {
|
||||
initial: 'idle',
|
||||
states: {
|
||||
idle: {},
|
||||
checkStorageAvailability: {
|
||||
entry: ['sendDataBackupStartEvent'],
|
||||
invoke: {
|
||||
src: 'checkStorageAvailability',
|
||||
onDone: [
|
||||
{
|
||||
cond: 'isMinimumStorageRequiredForBackupReached',
|
||||
target: 'failure',
|
||||
},
|
||||
{
|
||||
target: 'fetchDataFromDB',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fetchDataFromDB: {
|
||||
entry: ['fetchAllDataFromDB'],
|
||||
on: {
|
||||
STORE_RESPONSE: {
|
||||
actions: 'setDataFromStorage',
|
||||
target: 'writeDataToFile',
|
||||
},
|
||||
},
|
||||
},
|
||||
writeDataToFile: {
|
||||
invoke: {
|
||||
src: 'writeDataToFile',
|
||||
},
|
||||
on: {
|
||||
FILE_NAME: {
|
||||
actions: 'setFileName',
|
||||
target: 'zipBackupFile',
|
||||
},
|
||||
},
|
||||
},
|
||||
zipBackupFile: {
|
||||
invoke: {
|
||||
src: 'zipBackupFile',
|
||||
onDone: {
|
||||
target: 'success',
|
||||
},
|
||||
onError: {
|
||||
target: 'failure',
|
||||
},
|
||||
},
|
||||
},
|
||||
success: {
|
||||
entry: 'sendDataBackupSuccessEvent',
|
||||
},
|
||||
failure: {
|
||||
entry: 'sendDataBackupFailureEvent',
|
||||
},
|
||||
},
|
||||
on: {
|
||||
FETCH_DATA: {
|
||||
target: '.checkStorageAvailability',
|
||||
},
|
||||
OK: {
|
||||
target: '.idle',
|
||||
},
|
||||
DISMISS: {
|
||||
target: 'init',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
setDataFromStorage: model.assign({
|
||||
dataFromStorage: (_context, event) => {
|
||||
return event.response;
|
||||
},
|
||||
}),
|
||||
|
||||
setFileName: model.assign({
|
||||
fileName: (_context, event) => {
|
||||
return event.filename;
|
||||
},
|
||||
}),
|
||||
|
||||
fetchAllDataFromDB: send(StoreEvents.EXPORT(), {
|
||||
to: context => context.serviceRefs.store,
|
||||
}),
|
||||
|
||||
sendDataBackupStartEvent: () => {
|
||||
sendStartEvent(
|
||||
getStartEventData(TelemetryConstants.FlowType.dataBackup),
|
||||
);
|
||||
sendImpressionEvent(
|
||||
getImpressionEventData(
|
||||
TelemetryConstants.FlowType.dataBackup,
|
||||
TelemetryConstants.Screens.dataBackupScreen,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
sendDataBackupSuccessEvent: () => {
|
||||
sendEndEvent(
|
||||
getEndEventData(
|
||||
TelemetryConstants.FlowType.dataBackup,
|
||||
TelemetryConstants.EndEventStatus.success,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
sendDataBackupFailureEvent: () => {
|
||||
sendEndEvent(
|
||||
getEndEventData(
|
||||
TelemetryConstants.FlowType.dataBackup,
|
||||
TelemetryConstants.EndEventStatus.failure,
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
services: {
|
||||
checkStorageAvailability: () => async () => {
|
||||
return Promise.resolve(isMinimumLimitForBackupReached());
|
||||
},
|
||||
|
||||
writeDataToFile: context => async callack => {
|
||||
const fileName = await writeToBackupFile(context.dataFromStorage);
|
||||
callack(model.events.FILE_NAME(fileName));
|
||||
},
|
||||
|
||||
zipBackupFile: context => async () => {
|
||||
const result = await compressAndRemoveFile(context.fileName);
|
||||
return result;
|
||||
},
|
||||
},
|
||||
|
||||
guards: {
|
||||
isMinimumStorageRequiredForBackupReached: (_context, event) =>
|
||||
Boolean(event.data),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export function createBackupMachine(serviceRefs: AppServices) {
|
||||
return backupMachine.withContext({
|
||||
...backupMachine.context,
|
||||
serviceRefs,
|
||||
});
|
||||
}
|
||||
export function selectIsBackingUp(state: State) {
|
||||
return state.matches('backingUp');
|
||||
}
|
||||
export function selectIsBackingUpSuccess(state: State) {
|
||||
return state.matches('backingUp.success');
|
||||
}
|
||||
export function selectIsBackingUpSFailure(state: State) {
|
||||
return state.matches('backingUp.failure');
|
||||
}
|
||||
type State = StateFrom<typeof backupMachine>;
|
||||
73
machines/backup.typegen.ts
Normal file
73
machines/backup.typegen.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
internalEvents: {
|
||||
'done.invoke.backup.backingUp.checkStorageAvailability:invocation[0]': {
|
||||
type: 'done.invoke.backup.backingUp.checkStorageAvailability:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.backup.backingUp.zipBackupFile:invocation[0]': {
|
||||
type: 'done.invoke.backup.backingUp.zipBackupFile:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'error.platform.backup.backingUp.zipBackupFile:invocation[0]': {
|
||||
type: 'error.platform.backup.backingUp.zipBackupFile:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'xstate.init': {type: 'xstate.init'};
|
||||
};
|
||||
invokeSrcNameMap: {
|
||||
checkStorageAvailability: 'done.invoke.backup.backingUp.checkStorageAvailability:invocation[0]';
|
||||
writeDataToFile: 'done.invoke.backup.backingUp.writeDataToFile:invocation[0]';
|
||||
zipBackupFile: 'done.invoke.backup.backingUp.zipBackupFile:invocation[0]';
|
||||
};
|
||||
missingImplementations: {
|
||||
actions: never;
|
||||
delays: never;
|
||||
guards: never;
|
||||
services: never;
|
||||
};
|
||||
eventsCausingActions: {
|
||||
fetchAllDataFromDB: 'done.invoke.backup.backingUp.checkStorageAvailability:invocation[0]';
|
||||
sendDataBackupFailureEvent:
|
||||
| 'done.invoke.backup.backingUp.checkStorageAvailability:invocation[0]'
|
||||
| 'error.platform.backup.backingUp.zipBackupFile:invocation[0]';
|
||||
sendDataBackupStartEvent: 'FETCH_DATA';
|
||||
sendDataBackupSuccessEvent: 'done.invoke.backup.backingUp.zipBackupFile:invocation[0]';
|
||||
setDataFromStorage: 'STORE_RESPONSE';
|
||||
setFileName: 'FILE_NAME';
|
||||
};
|
||||
eventsCausingDelays: {};
|
||||
eventsCausingGuards: {
|
||||
isMinimumStorageRequiredForBackupReached: 'done.invoke.backup.backingUp.checkStorageAvailability:invocation[0]';
|
||||
};
|
||||
eventsCausingServices: {
|
||||
checkStorageAvailability: 'FETCH_DATA';
|
||||
writeDataToFile: 'STORE_RESPONSE';
|
||||
zipBackupFile: 'FILE_NAME';
|
||||
};
|
||||
matchesStates:
|
||||
| 'backingUp'
|
||||
| 'backingUp.checkStorageAvailability'
|
||||
| 'backingUp.failure'
|
||||
| 'backingUp.fetchDataFromDB'
|
||||
| 'backingUp.idle'
|
||||
| 'backingUp.success'
|
||||
| 'backingUp.writeDataToFile'
|
||||
| 'backingUp.zipBackupFile'
|
||||
| 'init'
|
||||
| {
|
||||
backingUp?:
|
||||
| 'checkStorageAvailability'
|
||||
| 'failure'
|
||||
| 'fetchDataFromDB'
|
||||
| 'idle'
|
||||
| 'success'
|
||||
| 'writeDataToFile'
|
||||
| 'zipBackupFile';
|
||||
};
|
||||
tags: never;
|
||||
}
|
||||
305
machines/backupWithEncryption.ts
Normal file
305
machines/backupWithEncryption.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import {DoneInvokeEvent, EventFrom, StateFrom, send} from 'xstate';
|
||||
import {createModel} from 'xstate/lib/model';
|
||||
import {AppServices} from '../shared/GlobalContext';
|
||||
import {
|
||||
BACKUP_ENC_KEY,
|
||||
BACKUP_ENC_KEY_TYPE,
|
||||
BACKUP_ENC_TYPE_VAL_PASSWORD,
|
||||
BACKUP_ENC_TYPE_VAL_PHONE,
|
||||
argon2iConfigForPasswordAndPhoneNumber,
|
||||
argon2iSalt,
|
||||
} from '../shared/constants';
|
||||
import {hashData} from '../shared/commonUtil';
|
||||
import {StoreEvents} from './store';
|
||||
import Storage from '../shared/storage';
|
||||
import {compressData} from '../shared/cryptoutil/cryptoUtil';
|
||||
|
||||
const model = createModel(
|
||||
{
|
||||
serviceRefs: {} as AppServices,
|
||||
otp: '',
|
||||
baseEncKey: '',
|
||||
dataFromStorage: {},
|
||||
hashedEncKey: '',
|
||||
fileName: '',
|
||||
},
|
||||
{
|
||||
events: {
|
||||
DATA_BACKUP: () => ({}),
|
||||
YES: () => ({}),
|
||||
PASSWORD: () => ({}),
|
||||
SET_BASE_ENC_KEY: (baseEncKey: string) => ({baseEncKey}),
|
||||
FILE_NAME: (filename: string) => ({filename}),
|
||||
PHONE_NUMBER: () => ({}),
|
||||
SEND_OTP: () => ({}),
|
||||
INPUT_OTP: (otp: string) => ({otp}),
|
||||
BACK: () => ({}),
|
||||
CANCEL: () => ({}),
|
||||
WAIT: () => ({}),
|
||||
CANCEL_DOWNLOAD: () => ({}),
|
||||
STORE_RESPONSE: (response: unknown) => ({response}),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const BackupWithEncryptionEvents = model.events;
|
||||
|
||||
export const backupWithEncryptionMachine = model.createMachine(
|
||||
{
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
tsTypes: {} as import('./backupWithEncryption.typegen').Typegen0,
|
||||
schema: {
|
||||
context: model.initialContext,
|
||||
events: {} as EventFrom<typeof model>,
|
||||
},
|
||||
id: 'WithEncryption',
|
||||
initial: 'init',
|
||||
states: {
|
||||
init: {
|
||||
on: {
|
||||
DATA_BACKUP: [
|
||||
{
|
||||
target: 'backUp',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
backUp: {
|
||||
on: {
|
||||
YES: {
|
||||
target: 'selectPref',
|
||||
},
|
||||
},
|
||||
},
|
||||
selectPref: {
|
||||
on: {
|
||||
PASSWORD: {
|
||||
target: 'passwordBackup',
|
||||
},
|
||||
PHONE_NUMBER: {
|
||||
target: 'phoneNumberBackup',
|
||||
},
|
||||
},
|
||||
},
|
||||
passwordBackup: {
|
||||
on: {
|
||||
SET_BASE_ENC_KEY: {
|
||||
actions: ['setBaseEncKey', 'storePasswordKeyType'],
|
||||
},
|
||||
STORE_RESPONSE: {
|
||||
target: 'hashKey',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
phoneNumberBackup: {
|
||||
on: {
|
||||
SET_BASE_ENC_KEY: {
|
||||
actions: 'setBaseEncKey',
|
||||
},
|
||||
SEND_OTP: {
|
||||
target: 'requestOtp',
|
||||
},
|
||||
},
|
||||
},
|
||||
requestOtp: {
|
||||
on: {
|
||||
WAIT: {},
|
||||
CANCEL: {},
|
||||
CANCEL_DOWNLOAD: {},
|
||||
INPUT_OTP: {
|
||||
actions: ['setOtp', 'storePhoneNumberKeyType'], // TODO: we should also do the otp Verification here
|
||||
target: 'hashKey',
|
||||
},
|
||||
},
|
||||
invoke: {
|
||||
src: 'requestOtp',
|
||||
onDone: [
|
||||
{
|
||||
target: '',
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: '',
|
||||
target: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
hashKey: {
|
||||
invoke: {
|
||||
src: 'hashEncKey',
|
||||
onDone: {
|
||||
actions: ['setHashedKey', 'storeHashedEncKey'],
|
||||
},
|
||||
},
|
||||
on: {
|
||||
STORE_RESPONSE: {
|
||||
target: 'backingUp',
|
||||
},
|
||||
},
|
||||
},
|
||||
backingUp: {
|
||||
initial: 'checkStorageAvailability',
|
||||
states: {
|
||||
idle: {},
|
||||
checkStorageAvailability: {
|
||||
entry: ['sendDataBackupStartEvent'],
|
||||
invoke: {
|
||||
src: 'checkStorageAvailability',
|
||||
onDone: [
|
||||
{
|
||||
cond: 'isMinimumStorageRequiredForBackupReached',
|
||||
target: 'failure',
|
||||
},
|
||||
{
|
||||
target: 'fetchDataFromDB',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fetchDataFromDB: {
|
||||
entry: ['fetchAllDataFromDB'],
|
||||
on: {
|
||||
STORE_RESPONSE: {
|
||||
actions: 'setDataFromStorage',
|
||||
target: 'writeDataToFile',
|
||||
},
|
||||
},
|
||||
},
|
||||
writeDataToFile: {
|
||||
invoke: {
|
||||
src: 'writeDataToFile',
|
||||
},
|
||||
on: {
|
||||
FILE_NAME: {
|
||||
actions: 'setFileName',
|
||||
target: 'zipBackupFile',
|
||||
},
|
||||
},
|
||||
},
|
||||
zipBackupFile: {
|
||||
invoke: {
|
||||
src: 'zipBackupFile',
|
||||
onDone: {
|
||||
target: 'success',
|
||||
},
|
||||
onError: {
|
||||
target: 'failure',
|
||||
},
|
||||
},
|
||||
},
|
||||
success: {
|
||||
entry: 'sendDataBackupSuccessEvent',
|
||||
},
|
||||
failure: {
|
||||
entry: 'sendDataBackupFailureEvent',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
setOtp: model.assign({
|
||||
otp: (_context, event) => {
|
||||
return event.otp;
|
||||
},
|
||||
}),
|
||||
|
||||
setDataFromStorage: model.assign({
|
||||
dataFromStorage: (_context, event) => {
|
||||
return event.response;
|
||||
},
|
||||
}),
|
||||
|
||||
setBaseEncKey: model.assign({
|
||||
baseEncKey: (_context, event) => {
|
||||
return event.baseEncKey;
|
||||
},
|
||||
}),
|
||||
|
||||
setHashedKey: model.assign({
|
||||
hashedEncKey: (_context, event) =>
|
||||
(event as DoneInvokeEvent<string>).data,
|
||||
}),
|
||||
|
||||
storeHashedEncKey: send(
|
||||
context => StoreEvents.SET(BACKUP_ENC_KEY, context.hashedEncKey),
|
||||
{
|
||||
to: context => context.serviceRefs.store,
|
||||
},
|
||||
),
|
||||
|
||||
storePasswordKeyType: send(
|
||||
() =>
|
||||
StoreEvents.SET(BACKUP_ENC_KEY_TYPE, BACKUP_ENC_TYPE_VAL_PASSWORD),
|
||||
{
|
||||
to: context => context.serviceRefs.store,
|
||||
},
|
||||
),
|
||||
storePhoneNumberKeyType: send(
|
||||
() => StoreEvents.SET(BACKUP_ENC_KEY_TYPE, BACKUP_ENC_TYPE_VAL_PHONE),
|
||||
{
|
||||
to: context => context.serviceRefs.store,
|
||||
},
|
||||
),
|
||||
fetchAllDataFromDB: send(StoreEvents.EXPORT(), {
|
||||
to: context => context.serviceRefs.store,
|
||||
}),
|
||||
},
|
||||
|
||||
services: {
|
||||
hashEncKey: async context => {
|
||||
return await hashData(
|
||||
context.baseEncKey,
|
||||
argon2iSalt,
|
||||
argon2iConfigForPasswordAndPhoneNumber,
|
||||
).then(value => value);
|
||||
},
|
||||
|
||||
writeDataToFile: context => async callack => {
|
||||
await Storage.writeToBackupFile(context.dataFromStorage);
|
||||
},
|
||||
|
||||
zipBackupFile: context => async callback => {
|
||||
const result = await compressData(context.fileName);
|
||||
return result;
|
||||
},
|
||||
},
|
||||
|
||||
guards: {},
|
||||
},
|
||||
);
|
||||
|
||||
export function createBackupMachine(serviceRefs: AppServices) {
|
||||
return backupWithEncryptionMachine.withContext({
|
||||
...backupWithEncryptionMachine.context,
|
||||
serviceRefs,
|
||||
});
|
||||
}
|
||||
export function selectIsEnableBackup(state: State) {
|
||||
return state.matches('backUp');
|
||||
}
|
||||
export function selectIsBackupPref(state: State) {
|
||||
return state.matches('selectPref');
|
||||
}
|
||||
export function selectIsBackupViaPassword(state: State) {
|
||||
return state.matches('passwordBackup');
|
||||
}
|
||||
export function selectIsBackupViaPhoneNumber(state: State) {
|
||||
return state.matches('phoneNumberBackup');
|
||||
}
|
||||
export function selectIsRequestOtp(state: State) {
|
||||
return state.matches('requestOtp');
|
||||
}
|
||||
export function selectIsBackingUp(state: State) {
|
||||
return state.matches('backingUp');
|
||||
}
|
||||
export function selectIsCancellingDownload(state: State) {
|
||||
// TODO: check cancelDownload based on state
|
||||
return false;
|
||||
}
|
||||
type State = StateFrom<typeof backupWithEncryptionMachine>;
|
||||
104
machines/backupWithEncryption.typegen.ts
Normal file
104
machines/backupWithEncryption.typegen.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
internalEvents: {
|
||||
'done.invoke.WithEncryption.backingUp.checkStorageAvailability:invocation[0]': {
|
||||
type: 'done.invoke.WithEncryption.backingUp.checkStorageAvailability:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.WithEncryption.backingUp.zipBackupFile:invocation[0]': {
|
||||
type: 'done.invoke.WithEncryption.backingUp.zipBackupFile:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.WithEncryption.hashKey:invocation[0]': {
|
||||
type: 'done.invoke.WithEncryption.hashKey:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'error.platform.WithEncryption.backingUp.zipBackupFile:invocation[0]': {
|
||||
type: 'error.platform.WithEncryption.backingUp.zipBackupFile:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'error.platform.WithEncryption.requestOtp:invocation[0]': {
|
||||
type: 'error.platform.WithEncryption.requestOtp:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'xstate.init': {type: 'xstate.init'};
|
||||
};
|
||||
invokeSrcNameMap: {
|
||||
checkStorageAvailability: 'done.invoke.WithEncryption.backingUp.checkStorageAvailability:invocation[0]';
|
||||
hashEncKey: 'done.invoke.WithEncryption.hashKey:invocation[0]';
|
||||
requestOtp: 'done.invoke.WithEncryption.requestOtp:invocation[0]';
|
||||
writeDataToFile: 'done.invoke.WithEncryption.backingUp.writeDataToFile:invocation[0]';
|
||||
zipBackupFile: 'done.invoke.WithEncryption.backingUp.zipBackupFile:invocation[0]';
|
||||
};
|
||||
missingImplementations: {
|
||||
actions:
|
||||
| ''
|
||||
| 'sendDataBackupFailureEvent'
|
||||
| 'sendDataBackupStartEvent'
|
||||
| 'sendDataBackupSuccessEvent'
|
||||
| 'setFileName';
|
||||
delays: never;
|
||||
guards: 'isMinimumStorageRequiredForBackupReached';
|
||||
services: 'checkStorageAvailability' | 'requestOtp';
|
||||
};
|
||||
eventsCausingActions: {
|
||||
'': 'error.platform.WithEncryption.requestOtp:invocation[0]';
|
||||
fetchAllDataFromDB: 'done.invoke.WithEncryption.backingUp.checkStorageAvailability:invocation[0]';
|
||||
sendDataBackupFailureEvent:
|
||||
| 'done.invoke.WithEncryption.backingUp.checkStorageAvailability:invocation[0]'
|
||||
| 'error.platform.WithEncryption.backingUp.zipBackupFile:invocation[0]';
|
||||
sendDataBackupStartEvent: 'STORE_RESPONSE';
|
||||
sendDataBackupSuccessEvent: 'done.invoke.WithEncryption.backingUp.zipBackupFile:invocation[0]';
|
||||
setBaseEncKey: 'SET_BASE_ENC_KEY';
|
||||
setDataFromStorage: 'STORE_RESPONSE';
|
||||
setFileName: 'FILE_NAME';
|
||||
setHashedKey: 'done.invoke.WithEncryption.hashKey:invocation[0]';
|
||||
setOtp: 'INPUT_OTP';
|
||||
storeHashedEncKey: 'done.invoke.WithEncryption.hashKey:invocation[0]';
|
||||
storePasswordKeyType: 'SET_BASE_ENC_KEY';
|
||||
storePhoneNumberKeyType: 'INPUT_OTP';
|
||||
};
|
||||
eventsCausingDelays: {};
|
||||
eventsCausingGuards: {
|
||||
isMinimumStorageRequiredForBackupReached: 'done.invoke.WithEncryption.backingUp.checkStorageAvailability:invocation[0]';
|
||||
};
|
||||
eventsCausingServices: {
|
||||
checkStorageAvailability: 'STORE_RESPONSE';
|
||||
hashEncKey: 'INPUT_OTP' | 'STORE_RESPONSE';
|
||||
requestOtp: 'SEND_OTP';
|
||||
writeDataToFile: 'STORE_RESPONSE';
|
||||
zipBackupFile: 'FILE_NAME';
|
||||
};
|
||||
matchesStates:
|
||||
| 'backUp'
|
||||
| 'backingUp'
|
||||
| 'backingUp.checkStorageAvailability'
|
||||
| 'backingUp.failure'
|
||||
| 'backingUp.fetchDataFromDB'
|
||||
| 'backingUp.idle'
|
||||
| 'backingUp.success'
|
||||
| 'backingUp.writeDataToFile'
|
||||
| 'backingUp.zipBackupFile'
|
||||
| 'hashKey'
|
||||
| 'init'
|
||||
| 'passwordBackup'
|
||||
| 'phoneNumberBackup'
|
||||
| 'requestOtp'
|
||||
| 'selectPref'
|
||||
| {
|
||||
backingUp?:
|
||||
| 'checkStorageAvailability'
|
||||
| 'failure'
|
||||
| 'fetchDataFromDB'
|
||||
| 'idle'
|
||||
| 'success'
|
||||
| 'writeDataToFile'
|
||||
| 'zipBackupFile';
|
||||
};
|
||||
tags: never;
|
||||
}
|
||||
@@ -19,14 +19,15 @@ import {ActivityLogEvents} from './activityLog';
|
||||
import {log} from 'xstate/lib/actions';
|
||||
import {verifyCredential} from '../shared/vcjs/verifyCredential';
|
||||
import {
|
||||
getBody,
|
||||
vcDownloadTimeout,
|
||||
OIDCErrors,
|
||||
ErrorMessage,
|
||||
updateCredentialInformation,
|
||||
constructAuthorizationConfiguration,
|
||||
ErrorMessage,
|
||||
getBody,
|
||||
getVCMetadata,
|
||||
Issuers,
|
||||
Issuers_Key_Ref,
|
||||
OIDCErrors,
|
||||
updateCredentialInformation,
|
||||
vcDownloadTimeout,
|
||||
} from '../shared/openId4VCI/Utils';
|
||||
import {
|
||||
getEndEventData,
|
||||
@@ -43,6 +44,7 @@ import {
|
||||
import {CACHED_API} from '../shared/api';
|
||||
import {request} from '../shared/request';
|
||||
import {BiometricCancellationError} from '../shared/error/BiometricCancellationError';
|
||||
import {VCMetadata} from '../shared/VCMetadata';
|
||||
|
||||
const model = createModel(
|
||||
{
|
||||
@@ -631,6 +633,13 @@ export const IssuersMachine = model.createMachine(
|
||||
);
|
||||
},
|
||||
verifyCredential: async context => {
|
||||
//this issuer specific check has to be removed once vc validation is done.
|
||||
if (
|
||||
VCMetadata.fromVcMetadataString(getVCMetadata(context)).issuer ===
|
||||
Issuers.Sunbird
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return verifyCredential(context.verifiableCredential?.credential);
|
||||
},
|
||||
},
|
||||
@@ -722,6 +731,7 @@ export interface displayType {
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface issuerType {
|
||||
credential_issuer: string;
|
||||
protocol: string;
|
||||
|
||||
@@ -48,6 +48,7 @@ const model = createModel(
|
||||
TRY_AGAIN: () => ({}),
|
||||
IGNORE: () => ({}),
|
||||
GET: (key: string) => ({key}),
|
||||
EXPORT: () => ({}),
|
||||
DECRYPT_ERROR: () => ({}),
|
||||
KEY_INVALIDATE_ERROR: () => ({}),
|
||||
BIOMETRIC_CANCELLED: (requester?: string) => ({requester}),
|
||||
@@ -189,6 +190,9 @@ export const storeMachine =
|
||||
GET: {
|
||||
actions: 'forwardStoreRequest',
|
||||
},
|
||||
EXPORT: {
|
||||
actions: 'forwardStoreRequest',
|
||||
},
|
||||
SET: {
|
||||
actions: 'forwardStoreRequest',
|
||||
},
|
||||
@@ -345,6 +349,10 @@ export const storeMachine =
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'EXPORT': {
|
||||
response = await exportData(context.encryptionKey);
|
||||
break;
|
||||
}
|
||||
case 'SET': {
|
||||
await setItem(event.key, event.value, context.encryptionKey);
|
||||
response = event.value;
|
||||
@@ -547,6 +555,10 @@ export async function setItem(
|
||||
}
|
||||
}
|
||||
|
||||
export async function exportData(encryptionKey: string) {
|
||||
return Storage.exportData(encryptionKey);
|
||||
}
|
||||
|
||||
export async function getItem(
|
||||
key: string,
|
||||
defaultValue: unknown,
|
||||
|
||||
1054
package-lock.json
generated
1054
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@
|
||||
"buffer": "^6.0.3",
|
||||
"date-fns": "^2.26.0",
|
||||
"expo": "^49.0.16",
|
||||
"expo-auth-session": "^5.2.0",
|
||||
"expo-barcode-scanner": "~12.3.2",
|
||||
"expo-camera": "^13.6.0",
|
||||
"expo-constants": "^14.4.2",
|
||||
@@ -41,6 +42,7 @@
|
||||
"expo-modules-autolinking": "^1.5.1",
|
||||
"expo-splash-screen": "^0.22.0",
|
||||
"expo-updates": "^0.18.17",
|
||||
"expo-web-browser": "^12.5.0",
|
||||
"i18next": "^21.6.16",
|
||||
"iso-639-3": "^3.0.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
@@ -58,6 +60,7 @@
|
||||
"react-native-biometrics-changed": "^1.1.8",
|
||||
"react-native-bluetooth-state-manager": "^1.3.2",
|
||||
"react-native-cli": "^2.0.1",
|
||||
"react-native-cloud-storage": "^1.2.2",
|
||||
"react-native-device-info": "^8.4.8",
|
||||
"react-native-dotenv": "^3.3.1",
|
||||
"react-native-elements": "3.4.3",
|
||||
@@ -81,6 +84,7 @@
|
||||
"react-native-spinkit": "^1.5.1",
|
||||
"react-native-svg": "13.4.0",
|
||||
"react-native-vector-icons": "^10.0.0",
|
||||
"react-native-zip-archive": "^6.1.0",
|
||||
"short-unique-id": "^4.4.4",
|
||||
"simple-pem2jwk": "^0.2.4",
|
||||
"telemetry-sdk": "git://github.com/mosip/sunbird-telemetry-sdk.git#f762be5732ee552c0c70bdd540aa4e2701554c71",
|
||||
|
||||
@@ -7,8 +7,6 @@ import {useTranslation} from 'react-i18next';
|
||||
import {RootRouteProps} from '../../routes';
|
||||
import {useWelcomeScreen} from '../WelcomeScreenController';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import Constants from 'expo-constants';
|
||||
import {isIOS} from '../../shared/constants';
|
||||
import {SvgImage} from '../../components/ui/svg';
|
||||
|
||||
export const IntroSlidersScreen: React.FC<RootRouteProps> = props => {
|
||||
@@ -44,44 +42,24 @@ export const IntroSlidersScreen: React.FC<RootRouteProps> = props => {
|
||||
},
|
||||
];
|
||||
|
||||
const isPasscodeSet = controller.isPasscodeSet();
|
||||
|
||||
const renderItem = ({item}) => {
|
||||
return (
|
||||
<LinearGradient colors={Theme.Colors.gradientBtn}>
|
||||
<Centered>
|
||||
<Row crossAlign="center">
|
||||
<Column
|
||||
style={{
|
||||
flex: 3,
|
||||
alignItems: 'flex-end',
|
||||
marginRight: 75,
|
||||
}}>
|
||||
<Column margin="50 0">{SvgImage.InjiSmallLogo()}</Column>
|
||||
<Row align="space-between" style={Theme.Styles.introSliderHeader}>
|
||||
<Column style={{marginLeft: Dimensions.get('screen').width * 0.4}}>
|
||||
{SvgImage.InjiSmallLogo()}
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'flex-end',
|
||||
paddingTop: isIOS() ? Constants.statusBarHeight : 0,
|
||||
}}>
|
||||
{controller.isPasscodeSet() ? (
|
||||
<Button
|
||||
testID="back"
|
||||
type="plain"
|
||||
title={t('back')}
|
||||
onPress={controller.BACK}
|
||||
styles={{height: 40, maxWidth: 115}}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
testID="skip"
|
||||
type="plain"
|
||||
title={t('skip')}
|
||||
onPress={controller.NEXT}
|
||||
styles={{height: 40, width: 115}}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
<Button
|
||||
testID={isPasscodeSet ? 'back' : 'skip'}
|
||||
type="plain"
|
||||
title={isPasscodeSet ? t('back') : t('skip')}
|
||||
onPress={isPasscodeSet ? controller.BACK : controller.NEXT}
|
||||
styles={{height: 40, maxWidth: 115}}
|
||||
/>
|
||||
</Row>
|
||||
<Image
|
||||
source={item.image}
|
||||
@@ -120,7 +98,7 @@ export const IntroSlidersScreen: React.FC<RootRouteProps> = props => {
|
||||
<View>
|
||||
<LinearGradient
|
||||
colors={Theme.Colors.gradientBtn}
|
||||
style={{borderRadius: 10, height: 50, marginTop: -10}}>
|
||||
style={Theme.Styles.introSliderButton}>
|
||||
<Text
|
||||
testID="next"
|
||||
style={{paddingTop: 3}}
|
||||
@@ -139,7 +117,7 @@ export const IntroSlidersScreen: React.FC<RootRouteProps> = props => {
|
||||
<View>
|
||||
<LinearGradient
|
||||
colors={Theme.Colors.gradientBtn}
|
||||
style={{borderRadius: 10, height: 50, marginTop: -10}}>
|
||||
style={Theme.Styles.introSliderButton}>
|
||||
<Text
|
||||
testID="getStarted"
|
||||
style={{paddingTop: 3}}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
sendErrorEvent,
|
||||
} from '../../../shared/telemetry/TelemetryUtils';
|
||||
import {TelemetryConstants} from '../../../shared/telemetry/TelemetryConstants';
|
||||
import {ACTIVATION_NOT_NEEDED} from '../../../shared/openId4VCI/Utils';
|
||||
|
||||
export const WalletBinding: React.FC<WalletBindingProps> = props => {
|
||||
const controller = useKebabPopUp(props);
|
||||
@@ -57,7 +58,8 @@ export const WalletBinding: React.FC<WalletBindingProps> = props => {
|
||||
};
|
||||
const {t} = useTranslation('WalletBinding');
|
||||
|
||||
return controller.emptyWalletBindingId ? (
|
||||
return controller.emptyWalletBindingId &&
|
||||
ACTIVATION_NOT_NEEDED.indexOf(props?.vcMetadata.issuer) === -1 ? (
|
||||
<ListItem bottomDivider onPress={controller.ADD_WALLET_BINDING_ID}>
|
||||
<ListItem.Content>
|
||||
<ListItem.Title {...testIDProps('pendingActivationOrActivated')}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {useLayoutEffect, useState} from 'react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {FlatList, Image, Pressable, View} from 'react-native';
|
||||
import {FlatList, Pressable, View} from 'react-native';
|
||||
import {Issuer} from '../../components/openId4VCI/Issuer';
|
||||
import {Error} from '../../components/ui/Error';
|
||||
import {Header} from '../../components/ui/Header';
|
||||
@@ -10,7 +10,7 @@ import {RootRouteProps} from '../../routes';
|
||||
import {HomeRouteProps} from '../../routes/main';
|
||||
import {useIssuerScreenController} from './IssuerScreenController';
|
||||
import {Loader} from '../../components/ui/Loader';
|
||||
import testIDProps, {removeWhiteSpace} from '../../shared/commonUtil';
|
||||
import {removeWhiteSpace} from '../../shared/commonUtil';
|
||||
import {
|
||||
ErrorMessage,
|
||||
getDisplayObjectForCurrentLanguage,
|
||||
@@ -218,9 +218,7 @@ export const IssuersScreen: React.FC<
|
||||
testID="issuersScreenDescription"
|
||||
style={{
|
||||
...Theme.TextStyles.regularGrey,
|
||||
paddingTop: 0.5,
|
||||
marginVertical: 14,
|
||||
marginHorizontal: 9,
|
||||
...Theme.IssuersScreenStyles.issuersSearchSubText,
|
||||
}}>
|
||||
{t('description')}
|
||||
</Text>
|
||||
@@ -241,7 +239,7 @@ export const IssuersScreen: React.FC<
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
numColumns={2}
|
||||
numColumns={1}
|
||||
keyExtractor={item => item.credential_issuer}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '../../shared/telemetry/TelemetryUtils';
|
||||
import {TelemetryConstants} from '../../shared/telemetry/TelemetryConstants';
|
||||
import {getVCsOrderedByPinStatus} from '../../shared/Utils';
|
||||
import {Issuers} from '../../shared/openId4VCI/Utils';
|
||||
|
||||
export const SendVcScreen: React.FC = () => {
|
||||
const {t} = useTranslation('SendVcScreen');
|
||||
@@ -102,15 +103,18 @@ export const SendVcScreen: React.FC = () => {
|
||||
<Column
|
||||
style={Theme.SendVcScreenStyles.shareOptionButtonsContainer}
|
||||
backgroundColor={Theme.Colors.whiteBackgroundColor}>
|
||||
{!controller.selectedVc.shouldVerifyPresence && (
|
||||
<Button
|
||||
type="gradient"
|
||||
title={t('acceptRequestAndVerify')}
|
||||
styles={{marginTop: 12}}
|
||||
disabled={controller.selectedIndex == null}
|
||||
onPress={controller.VERIFY_AND_ACCEPT_REQUEST}
|
||||
/>
|
||||
)}
|
||||
{!controller.selectedVc.shouldVerifyPresence &&
|
||||
controller.selectedVc?.vcMetadata &&
|
||||
VCMetadata.fromVcMetadataString(controller.selectedVc.vcMetadata)
|
||||
.issuer != Issuers.Sunbird && (
|
||||
<Button
|
||||
type="gradient"
|
||||
title={t('acceptRequestAndVerify')}
|
||||
styles={{marginTop: 12}}
|
||||
disabled={controller.selectedIndex == null}
|
||||
onPress={controller.VERIFY_AND_ACCEPT_REQUEST}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
type="gradient"
|
||||
@@ -143,7 +147,7 @@ export const SendVcScreen: React.FC = () => {
|
||||
onBackdropPress={controller.DISMISS}>
|
||||
<Row>
|
||||
<Button
|
||||
testID='cancel'
|
||||
testID="cancel"
|
||||
fill
|
||||
type="clear"
|
||||
title={t('common:cancel')}
|
||||
@@ -151,7 +155,7 @@ export const SendVcScreen: React.FC = () => {
|
||||
margin={[0, 8, 0, 0]}
|
||||
/>
|
||||
<Button
|
||||
testID='tryAgain'
|
||||
testID="tryAgain"
|
||||
fill
|
||||
title={t('common:tryAgain')}
|
||||
onPress={controller.RETRY_VERIFICATION}
|
||||
|
||||
45
screens/Settings/BackupController.tsx
Normal file
45
screens/Settings/BackupController.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import {useSelector} from '@xstate/react';
|
||||
import {useContext} from 'react';
|
||||
import {
|
||||
BackupEvents,
|
||||
selectIsBackingUp,
|
||||
selectIsBackingUpSuccess,
|
||||
selectIsBackingUpSFailure,
|
||||
} from '../../machines/backup';
|
||||
import {GlobalContext} from '../../shared/GlobalContext';
|
||||
|
||||
export function useBackupScreen() {
|
||||
const {appService} = useContext(GlobalContext);
|
||||
const backupService = appService.children.get('backup');
|
||||
|
||||
return {
|
||||
isBackingUp: useSelector(backupService, selectIsBackingUp),
|
||||
isBackingUpSuccess: useSelector(backupService, selectIsBackingUpSuccess),
|
||||
isBackingUpFailure: useSelector(backupService, selectIsBackingUpSFailure),
|
||||
DATA_BACKUP: () => {
|
||||
backupService.send(BackupEvents.DATA_BACKUP());
|
||||
},
|
||||
OK: () => {
|
||||
backupService.send(BackupEvents.OK());
|
||||
},
|
||||
DISMISS: () => {
|
||||
backupService.send(BackupEvents.DISMISS());
|
||||
},
|
||||
FETCH_DATA: () => {
|
||||
backupService.send(BackupEvents.FETCH_DATA());
|
||||
},
|
||||
PASSWORD: () => {
|
||||
backupService.send(BackupEvents.PASSWORD());
|
||||
},
|
||||
SET_BASE_ENC_KEY: (key: string) => {
|
||||
backupService.send(BackupEvents.SET_BASE_ENC_KEY(key));
|
||||
},
|
||||
PHONE_NUMBER: () => {
|
||||
backupService.send(BackupEvents.PHONE_NUMBER());
|
||||
},
|
||||
SEND_OTP: () => {
|
||||
backupService.send(BackupEvents.SEND_OTP());
|
||||
},
|
||||
INPUT_OTP: (otp: string) => backupService.send(BackupEvents.INPUT_OTP(otp)),
|
||||
};
|
||||
}
|
||||
67
screens/Settings/BackupToggle.tsx
Normal file
67
screens/Settings/BackupToggle.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import React, {useState} from 'react';
|
||||
import {Switch} from 'react-native-elements';
|
||||
import {Text} from '../../components/ui';
|
||||
import {Modal} from '../../components/ui/Modal';
|
||||
import {Theme} from '../../components/ui/styleUtils';
|
||||
import {useBackupScreen} from './BackupController';
|
||||
import {Platform} from 'react-native';
|
||||
import {MessageOverlay} from '../../components/MessageOverlay';
|
||||
|
||||
export const BackupToggle: React.FC<BackupToggleProps> = props => {
|
||||
const [dataBackup, setDataBackup] = useState(false);
|
||||
|
||||
const controller = useBackupScreen(props);
|
||||
|
||||
const toggleSwitch = () => {
|
||||
setDataBackup(!dataBackup);
|
||||
if (!dataBackup) {
|
||||
controller.FETCH_DATA();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Modal
|
||||
isVisible={props.isVisible}
|
||||
headerTitle={'Data Backup Toggle'}
|
||||
headerElevation={2}
|
||||
arrowLeft={true}
|
||||
onDismiss={props.onDismiss}>
|
||||
<Text> Enable Data backup</Text>
|
||||
<Switch
|
||||
value={dataBackup}
|
||||
onValueChange={toggleSwitch}
|
||||
trackColor={{
|
||||
false: Theme.Colors.switchTrackFalse,
|
||||
true:
|
||||
Platform.OS == 'ios'
|
||||
? Theme.Colors.switchHead
|
||||
: Theme.Colors.switchTrackTrue,
|
||||
}}
|
||||
color={Theme.Colors.switchHead}
|
||||
/>
|
||||
</Modal>
|
||||
<MessageOverlay
|
||||
isVisible={controller.isBackingUpSuccess}
|
||||
onButtonPress={() => {
|
||||
controller.OK(), setDataBackup(false);
|
||||
}}
|
||||
buttonText="OK"
|
||||
title={'Backup Successful'}
|
||||
/>
|
||||
<MessageOverlay
|
||||
isVisible={controller.isBackingUpFailure}
|
||||
onButtonPress={() => {
|
||||
controller.OK(), setDataBackup(false);
|
||||
}}
|
||||
buttonText="OK"
|
||||
title={'Backup Failed'}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
interface BackupToggleProps {
|
||||
isVisible: boolean;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
45
screens/Settings/DataBackup.tsx
Normal file
45
screens/Settings/DataBackup.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, {useState} from 'react';
|
||||
import {Pressable} from 'react-native';
|
||||
import {Icon, ListItem} from 'react-native-elements';
|
||||
import {Text} from '../../components/ui';
|
||||
import {Theme} from '../../components/ui/styleUtils';
|
||||
import {useBackupScreen} from './BackupController';
|
||||
import {BackupToggle} from './BackupToggle';
|
||||
|
||||
export const DataBackup: React.FC = ({} = props => {
|
||||
const controller = useBackupScreen(props);
|
||||
|
||||
// TODO : Check if the setup is already done
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
controller.DATA_BACKUP();
|
||||
}}>
|
||||
<ListItem topDivider bottomDivider>
|
||||
<Icon
|
||||
type={'feather'}
|
||||
name={'file'}
|
||||
color={Theme.Colors.Icon}
|
||||
size={25}
|
||||
/>
|
||||
<ListItem.Content>
|
||||
<ListItem.Title style={{paddingTop: 3}}>
|
||||
<Text weight="semibold" color={Theme.Colors.settingsLabel}>
|
||||
Data Backup
|
||||
</Text>
|
||||
</ListItem.Title>
|
||||
</ListItem.Content>
|
||||
</ListItem>
|
||||
</Pressable>
|
||||
|
||||
{controller.isBackingUp && (
|
||||
<BackupToggle
|
||||
isVisible={controller.isBackingUp}
|
||||
onDismiss={() => controller.DISMISS()}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
@@ -10,13 +10,14 @@ import {useTranslation} from 'react-i18next';
|
||||
import {LanguageSelector} from '../../components/LanguageSelector';
|
||||
import {ScrollView} from 'react-native-gesture-handler';
|
||||
import {Modal} from '../../components/ui/Modal';
|
||||
import {CREDENTIAL_REGISTRY_EDIT} from 'react-native-dotenv';
|
||||
import {CREDENTIAL_REGISTRY_EDIT, DATA_BACKUP} from 'react-native-dotenv';
|
||||
import {AboutInji} from './AboutInji';
|
||||
import {EditableListItem} from '../../components/EditableListItem';
|
||||
import {RequestRouteProps, RootRouteProps} from '../../routes';
|
||||
import {ReceivedCards} from './ReceivedCards';
|
||||
import testIDProps from '../../shared/commonUtil';
|
||||
import {SvgImage} from '../../components/ui/svg';
|
||||
import {DataBackup} from './DataBackup';
|
||||
|
||||
const LanguageSetting: React.FC = () => {
|
||||
const {t} = useTranslation('SettingScreen');
|
||||
@@ -159,6 +160,8 @@ export const SettingScreen: React.FC<
|
||||
|
||||
<AboutInji appId={controller.appId} />
|
||||
|
||||
{DATA_BACKUP === 'true' && <DataBackup />}
|
||||
|
||||
{CREDENTIAL_REGISTRY_EDIT === 'true' && (
|
||||
<EditableListItem
|
||||
testID="credentialRegistry"
|
||||
|
||||
@@ -9,6 +9,7 @@ import {settingsMachine} from '../machines/settings';
|
||||
import {storeMachine} from '../machines/store';
|
||||
import {vcMachine} from '../machines/vc';
|
||||
import {revokeVidsMachine} from '../machines/revoke';
|
||||
import {backupMachine} from '../machines/backup';
|
||||
|
||||
export const GlobalContext = createContext({} as GlobalServices);
|
||||
|
||||
@@ -25,4 +26,5 @@ export interface AppServices {
|
||||
request: ActorRefFrom<typeof requestMachine>;
|
||||
scan: ActorRefFrom<typeof scanMachine>;
|
||||
revoke: ActorRefFrom<typeof revokeVidsMachine>;
|
||||
backup: ActorRefFrom<typeof backupMachine>;
|
||||
}
|
||||
|
||||
@@ -178,21 +178,19 @@ async function generateCacheAPIFunctionWithCachePreference(
|
||||
) {
|
||||
const existingCredentials = await Keychain.getGenericPassword();
|
||||
try {
|
||||
const response = (await getItem(
|
||||
const response = await getItem(
|
||||
cacheKey,
|
||||
null,
|
||||
existingCredentials?.password,
|
||||
)) as string;
|
||||
);
|
||||
|
||||
if (response) {
|
||||
return JSON.parse(response);
|
||||
return response;
|
||||
} else {
|
||||
const response = await fetchCall();
|
||||
setItem(
|
||||
cacheKey,
|
||||
JSON.stringify(response),
|
||||
existingCredentials?.password,
|
||||
).then(() => console.log('Cached response for ' + cacheKey));
|
||||
setItem(cacheKey, response, existingCredentials?.password).then(() =>
|
||||
console.log('Cached response for ' + cacheKey),
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -219,11 +217,9 @@ async function generateCacheAPIFunctionWithAPIPreference(
|
||||
const existingCredentials = await Keychain.getGenericPassword();
|
||||
try {
|
||||
const response = await fetchCall();
|
||||
setItem(
|
||||
cacheKey,
|
||||
JSON.stringify(response),
|
||||
existingCredentials.password,
|
||||
).then(() => console.log('Cached response for ' + cacheKey));
|
||||
setItem(cacheKey, response, existingCredentials.password).then(() =>
|
||||
console.log('Cached response for ' + cacheKey),
|
||||
);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load due to network issue in API preferred api call.
|
||||
@@ -233,14 +229,14 @@ async function generateCacheAPIFunctionWithAPIPreference(
|
||||
|
||||
console.log(error);
|
||||
|
||||
const response = (await getItem(
|
||||
const response = await getItem(
|
||||
cacheKey,
|
||||
null,
|
||||
existingCredentials.password,
|
||||
)) as string;
|
||||
);
|
||||
|
||||
if (response) {
|
||||
return JSON.parse(response);
|
||||
return response;
|
||||
} else {
|
||||
if (response == null) {
|
||||
throw error;
|
||||
|
||||
@@ -2,6 +2,8 @@ import argon2 from 'react-native-argon2';
|
||||
import {AnyState} from 'xstate';
|
||||
import {getDeviceNameSync} from 'react-native-device-info';
|
||||
import {isAndroid} from './constants';
|
||||
import {generateSecureRandom} from 'react-native-securerandom';
|
||||
import forge from 'node-forge';
|
||||
|
||||
export const hashData = async (
|
||||
data: string,
|
||||
@@ -12,6 +14,21 @@ export const hashData = async (
|
||||
return result.rawHash as string;
|
||||
};
|
||||
|
||||
export const generateRandomString = async () => {
|
||||
const randomBytes = await generateSecureRandom(64);
|
||||
const randomString = randomBytes.reduce(
|
||||
(acc, byte) => acc + byte.toString(16).padStart(2, '0'),
|
||||
'',
|
||||
);
|
||||
return randomString;
|
||||
};
|
||||
export const generateBackupEncryptionKey = (
|
||||
password: string,
|
||||
salt: string,
|
||||
iterations: number,
|
||||
length: number,
|
||||
) => forge.pkcs5.pbkdf2(password, salt, iterations, length);
|
||||
|
||||
export interface Argon2iConfig {
|
||||
iterations: number;
|
||||
memory: number;
|
||||
@@ -75,3 +92,7 @@ export const faceMatchConfig = (resp: string) => {
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getBackupFileName = () => {
|
||||
return `backup_${Date.now()}`;
|
||||
};
|
||||
|
||||
@@ -16,6 +16,14 @@ export const RECEIVED_VCS_STORE_KEY = 'receivedVCs';
|
||||
|
||||
export const MY_LOGIN_STORE_KEY = 'myLogins';
|
||||
|
||||
export const BACKUP_ENC_KEY = 'backupEncKey';
|
||||
|
||||
export const BACKUP_ENC_KEY_TYPE = 'backupEncKeyType';
|
||||
|
||||
export const BACKUP_ENC_TYPE_VAL_PASSWORD = 'password';
|
||||
|
||||
export const BACKUP_ENC_TYPE_VAL_PHONE = 'phone';
|
||||
|
||||
export let individualId = {id: '', idType: 'UIN' as VcIdType};
|
||||
|
||||
export const GET_INDIVIDUAL_ID = (currentIndividualId: IndividualId) => {
|
||||
@@ -63,6 +71,22 @@ export const argon2iConfigForUinVid: Argon2iConfig = {
|
||||
mode: 'argon2i',
|
||||
};
|
||||
|
||||
export const argon2iConfigForBackupFileName: Argon2iConfig = {
|
||||
iterations: 5,
|
||||
memory: 16 * 1024,
|
||||
parallelism: 2,
|
||||
hashLength: 8,
|
||||
mode: 'argon2id',
|
||||
};
|
||||
export const argon2iConfigForPasswordAndPhoneNumber: Argon2iConfig = {
|
||||
// TODO: expected iterations for hashing password and phone Number is 600000
|
||||
iterations: 500,
|
||||
memory: 16 * 1024,
|
||||
parallelism: 2,
|
||||
hashLength: 30,
|
||||
mode: 'argon2id',
|
||||
};
|
||||
|
||||
export const argon2iSalt =
|
||||
'1234567891011121314151617181920212223242526272829303132333435363';
|
||||
|
||||
@@ -87,10 +111,11 @@ export const DETAIL_VIEW_DEFAULT_FIELDS = [
|
||||
];
|
||||
|
||||
//todo UIN & VID to be removed once we get the fields in the wellknown endpoint
|
||||
export const CARD_VIEW_ADD_ON_FIELDS = ['idType', 'UIN', 'VID'];
|
||||
export const CARD_VIEW_ADD_ON_FIELDS = ['UIN', 'VID'];
|
||||
export const DETAIL_VIEW_ADD_ON_FIELDS = [
|
||||
'UIN',
|
||||
'VID',
|
||||
'status',
|
||||
'credentialRegistry',
|
||||
'idType',
|
||||
];
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
stat,
|
||||
unlink,
|
||||
writeFile,
|
||||
readDir,
|
||||
ReadDirItem,
|
||||
} from 'react-native-fs';
|
||||
|
||||
interface CacheData {
|
||||
@@ -17,6 +19,7 @@ interface Cache {
|
||||
[key: string]: CacheData;
|
||||
}
|
||||
|
||||
import * as RNZipArchive from 'react-native-zip-archive';
|
||||
class FileStorage {
|
||||
cache: Cache = {};
|
||||
|
||||
@@ -24,6 +27,10 @@ class FileStorage {
|
||||
return await readFile(path, 'utf8');
|
||||
}
|
||||
|
||||
async getAllFilesInDirectory(path: string) {
|
||||
return await readDir(path);
|
||||
}
|
||||
|
||||
async writeFile(path: string, data: string) {
|
||||
return await writeFile(path, data, 'utf8');
|
||||
}
|
||||
@@ -52,8 +59,41 @@ export default new FileStorage();
|
||||
* android: /data/user/0/io.mosip.residentapp/files/inji/VC/<filename>
|
||||
* These paths are coming from DocumentDirectoryPath in react-native-fs.
|
||||
*/
|
||||
|
||||
export const vcDirectoryPath = `${DocumentDirectoryPath}/inji/VC`;
|
||||
export const backupDirectoryPath = `${DocumentDirectoryPath}/inji/backup`;
|
||||
export const zipFilePath = (filename: string) =>
|
||||
`${DocumentDirectoryPath}/inji/backup/${filename}.zip`;
|
||||
|
||||
export const getFilePath = (key: string) => {
|
||||
return `${vcDirectoryPath}/${key}.txt`;
|
||||
};
|
||||
|
||||
export const vcDirectoryPath = `${DocumentDirectoryPath}/inji/VC`;
|
||||
export const getBackupFilePath = (key: string) => {
|
||||
return `${backupDirectoryPath}/${key}.injibackup`;
|
||||
};
|
||||
|
||||
export async function compressAndRemoveFile(fileName: string): Promise<string> {
|
||||
const result = await compressFile(fileName);
|
||||
await removeFile(fileName);
|
||||
return result;
|
||||
}
|
||||
async function compressFile(fileName: string): Promise<string> {
|
||||
return await RNZipArchive.zip(backupDirectoryPath, zipFilePath(fileName));
|
||||
}
|
||||
|
||||
async function removeFile(fileName: string) {
|
||||
await new FileStorage().removeItem(getBackupFilePath(fileName));
|
||||
}
|
||||
export async function getDirectorySize(path: string) {
|
||||
const directorySize = await new FileStorage()
|
||||
.getAllFilesInDirectory(path)
|
||||
.then((result: ReadDirItem[]) => {
|
||||
let folderEntriesSizeInBytes = 0;
|
||||
result.forEach(fileItem => {
|
||||
folderEntriesSizeInBytes += Number(fileItem.size);
|
||||
});
|
||||
return folderEntriesSizeInBytes;
|
||||
});
|
||||
return directorySize;
|
||||
}
|
||||
|
||||
@@ -4,21 +4,43 @@ import {isIOS} from '../constants';
|
||||
import pem2jwk from 'simple-pem2jwk';
|
||||
import {displayType, issuerType} from '../../machines/issuersMachine';
|
||||
import getAllConfigurations from '../commonprops/commonProps';
|
||||
import {CredentialWrapper} from '../../types/VC/EsignetMosipVC/vc';
|
||||
|
||||
import {VCMetadata} from '../VCMetadata';
|
||||
import i18next from 'i18next';
|
||||
import {getJWT} from '../cryptoutil/cryptoUtil';
|
||||
import {CACHED_API} from '../api';
|
||||
import i18n from '../../i18n';
|
||||
import {VerifiableCredential} from '../../types/VC/ExistingMosipVC/vc';
|
||||
import {CredentialWrapper} from '../../types/VC/EsignetMosipVC/vc';
|
||||
|
||||
export const Protocols = {
|
||||
OpenId4VCI: 'OpenId4VCI',
|
||||
OTP: 'OTP',
|
||||
};
|
||||
|
||||
export const Issuers = {
|
||||
Sunbird: 'Sunbird',
|
||||
ESignet: 'ESignet',
|
||||
};
|
||||
|
||||
export const ID_TYPE = {
|
||||
MOSIPVerifiableCredential: i18n.t('VcDetails:nationalCard'),
|
||||
InsuranceCredential: i18n.t('VcDetails:insuranceCard'),
|
||||
};
|
||||
|
||||
export const getIDType = (verifiableCredential: VerifiableCredential) => {
|
||||
return ID_TYPE[verifiableCredential.type[1]];
|
||||
};
|
||||
|
||||
export const ACTIVATION_NOT_NEEDED = [Issuers.Sunbird];
|
||||
|
||||
export const Issuers_Key_Ref = 'OpenId4VCI_KeyPair';
|
||||
|
||||
export const getIdentifier = (context, credential) => {
|
||||
const credId = credential.credential.id.split('/');
|
||||
const credentialIdentifier = credential.credential.id;
|
||||
const credId = credentialIdentifier.startsWith('did')
|
||||
? credentialIdentifier.split(':')
|
||||
: credentialIdentifier.split('/');
|
||||
return (
|
||||
context.selectedIssuer.credential_issuer +
|
||||
':' +
|
||||
@@ -151,12 +173,17 @@ export const getCredentialIssuersWellKnownConfig = async (
|
||||
let response = null;
|
||||
if (wellknown) {
|
||||
response = await CACHED_API.fetchIssuerWellknownConfig(issuer, wellknown);
|
||||
fields = !response
|
||||
? []
|
||||
: Object.keys(
|
||||
response?.credentials_supported[0].credential_definition
|
||||
.credentialSubject,
|
||||
);
|
||||
if (!response) {
|
||||
fields = [];
|
||||
} else if (response?.credentials_supported[0].order) {
|
||||
fields = response?.credentials_supported[0].order;
|
||||
} else {
|
||||
fields = Object.keys(
|
||||
response?.credentials_supported[0].credential_definition
|
||||
.credentialSubject,
|
||||
);
|
||||
console.log('fields => ', fields);
|
||||
}
|
||||
}
|
||||
return {
|
||||
wellknown: response,
|
||||
|
||||
@@ -13,27 +13,24 @@ import {
|
||||
isHardwareKeystoreExists,
|
||||
} from './cryptoutil/cryptoUtil';
|
||||
import {VCMetadata} from './VCMetadata';
|
||||
import {ENOENT, getItem} from '../machines/store';
|
||||
import {ENOENT} from '../machines/store';
|
||||
import {
|
||||
androidVersion,
|
||||
isAndroid,
|
||||
MY_VCS_STORE_KEY,
|
||||
RECEIVED_VCS_STORE_KEY,
|
||||
SETTINGS_STORE_KEY,
|
||||
} from './constants';
|
||||
import FileStorage, {
|
||||
backupDirectoryPath,
|
||||
getBackupFilePath,
|
||||
getFilePath,
|
||||
getFilePathOfEncryptedHmac,
|
||||
getDirectorySize,
|
||||
vcDirectoryPath,
|
||||
} from './fileStorage';
|
||||
import {__AppId} from './GlobalVariables';
|
||||
import {
|
||||
getErrorEventData,
|
||||
getImpressionEventData,
|
||||
sendErrorEvent,
|
||||
sendImpressionEvent,
|
||||
} from './telemetry/TelemetryUtils';
|
||||
import {getErrorEventData, sendErrorEvent} from './telemetry/TelemetryUtils';
|
||||
import {TelemetryConstants} from './telemetry/TelemetryConstants';
|
||||
import {getBackupFileName} from './commonUtil';
|
||||
|
||||
export const MMKV = new MMKVLoader().initialize();
|
||||
|
||||
@@ -56,6 +53,45 @@ async function generateHmac(
|
||||
}
|
||||
|
||||
class Storage {
|
||||
static exportData = async (encryptionKey: string) => {
|
||||
const completeBackupData = {};
|
||||
const dataFromDB: Record<string, any> = {};
|
||||
|
||||
const allKeysInDB = await MMKV.indexer.strings.getKeys();
|
||||
const keysToBeExported = allKeysInDB.filter(key =>
|
||||
key.includes('CACHE_FETCH_ISSUER_WELLKNOWN_CONFIG_'),
|
||||
);
|
||||
keysToBeExported.push(MY_VCS_STORE_KEY);
|
||||
|
||||
const encryptedDataPromises = keysToBeExported.map(key =>
|
||||
MMKV.getItem(key),
|
||||
);
|
||||
|
||||
Promise.all(encryptedDataPromises).then(encryptedDataList => {
|
||||
keysToBeExported.forEach(async (key, index) => {
|
||||
let encryptedData = encryptedDataList[index];
|
||||
if (encryptedData != null) {
|
||||
const decryptedData = await decryptJson(encryptionKey, encryptedData);
|
||||
dataFromDB[key] = JSON.parse(decryptedData);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
completeBackupData['dataFromDB'] = dataFromDB;
|
||||
completeBackupData['VC_Records'] = {};
|
||||
|
||||
let vcKeys = allKeysInDB.filter(key => key.indexOf('VC_') === 0);
|
||||
for (let ind in vcKeys) {
|
||||
const key = vcKeys[ind];
|
||||
const vc = await Storage.readVCFromFile(key);
|
||||
const decryptedVCData = await decryptJson(encryptionKey, vc);
|
||||
const deactivatedVC =
|
||||
removeWalletBindingDataBeforeBackup(decryptedVCData);
|
||||
completeBackupData['VC_Records'][key] = deactivatedVC;
|
||||
}
|
||||
return completeBackupData;
|
||||
};
|
||||
|
||||
static isVCStorageInitialised = async (): Promise<boolean> => {
|
||||
try {
|
||||
const res = await FileStorage.getInfo(vcDirectoryPath);
|
||||
@@ -252,3 +288,33 @@ class Storage {
|
||||
}
|
||||
|
||||
export default Storage;
|
||||
|
||||
export async function writeToBackupFile(data): Promise<string> {
|
||||
const fileName = getBackupFileName();
|
||||
const isDirectoryExists = await FileStorage.exists(backupDirectoryPath);
|
||||
if (isDirectoryExists) {
|
||||
await FileStorage.removeItem(backupDirectoryPath);
|
||||
}
|
||||
await FileStorage.createDirectory(backupDirectoryPath);
|
||||
const path = getBackupFilePath(fileName);
|
||||
await FileStorage.writeFile(path, JSON.stringify(data));
|
||||
return fileName;
|
||||
}
|
||||
|
||||
function removeWalletBindingDataBeforeBackup(data: string) {
|
||||
const vcData = JSON.parse(data);
|
||||
vcData.walletBindingResponse = null;
|
||||
vcData.publicKey = null;
|
||||
vcData.privateKey = null;
|
||||
return vcData;
|
||||
}
|
||||
|
||||
export async function isMinimumLimitForBackupReached() {
|
||||
const directorySize = await getDirectorySize(vcDirectoryPath);
|
||||
const freeDiskStorageInBytes =
|
||||
isAndroid() && androidVersion < 29
|
||||
? getFreeDiskStorageOldSync()
|
||||
: getFreeDiskStorageSync();
|
||||
|
||||
return freeDiskStorageInBytes <= 2 * directorySize;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export const TelemetryConstants = {
|
||||
vcLockOrRevoke: 'VC Lock / VC Revoke',
|
||||
getVcUsingAid: 'Get VC using AID',
|
||||
fetchData: 'Fetch Data',
|
||||
dataBackup: 'Data Backup',
|
||||
}),
|
||||
|
||||
EndEventStatus: Object.freeze({
|
||||
@@ -62,5 +63,6 @@ export const TelemetryConstants = {
|
||||
vcList: 'VC List',
|
||||
vcShareSuccessPage: 'VC Successfully Shared Page',
|
||||
vcReceivedSuccessPage: 'VC Successfully Received Page',
|
||||
dataBackupScreen: 'Data Backup Screen',
|
||||
}),
|
||||
};
|
||||
|
||||
1
types/react-native-dotenv/index.d.ts
vendored
1
types/react-native-dotenv/index.d.ts
vendored
@@ -38,4 +38,5 @@ declare module 'react-native-dotenv' {
|
||||
* Flag for Toggling debug mode
|
||||
*/
|
||||
export const DEBUG_MODE: string;
|
||||
export const GOOGLE_ANDROID_CLIENT_ID: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user