Merge remote-tracking branch 'mosip/qa-develop' into develop

This commit is contained in:
Tilak Puli
2023-01-19 16:01:27 +05:30
87 changed files with 3145 additions and 488 deletions

2
.env
View File

@@ -1,7 +1,7 @@
# after making changes to the env file, ensure to start the bundler (or the project) with a --reset-cache
# eg . npm build android:newlogic --reset-cache
MIMOTO_HOST=https://api.qa4.mosip.net/residentmobileapp
MIMOTO_HOST=https://api.qa-121.mosip.net/residentmobileapp
#MIMOTO_HOST=http://mock.mimoto.newlogic.dev
GOOGLE_NEARBY_MESSAGES_API_KEY=

View File

@@ -1,7 +1,7 @@
name: ID PASS - MOSIP Resident Application Custom build
env:
backendServiceDefaultUrl: https://api-internal.qa4.mosip.net/residentmobileapp
backendServiceDefaultUrl: https://api.qa-121.mosip.net/residentmobileapp
on:
workflow_dispatch:
@@ -9,7 +9,7 @@ on:
backendServiceUrl:
description: 'Backend service URL'
required: true
default: 'https://api-internal.qa4.mosip.net/residentmobileapp'
default: 'https://api.qa-121.mosip.net/residentmobileapp'
type: string
jobs:

View File

@@ -6,6 +6,7 @@ on:
- main
- develop
- demobranch
- qa-develop
tags:
- '*'

View File

@@ -3,7 +3,7 @@
buildscript {
ext {
buildToolsVersion = "29.0.3"
minSdkVersion = 21
minSdkVersion = 23
compileSdkVersion = 30
targetSdkVersion = 30
}
@@ -14,7 +14,7 @@ buildscript {
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:4.1.0")
classpath("com.android.tools.build:gradle:4.2.2")
classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
@@ -43,6 +43,7 @@ allprojects {
}
}
allprojects { repositories { maven { url "$rootDir/../node_modules/expo-camera/android/maven" } } }

BIN
assets/domain-warning.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
assets/otp-mobile-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
assets/success-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
assets/warningLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -1,6 +1,7 @@
{
"VC_SHARED": "shared",
"VC_RECEIVED": "received",
"VC_RECEIVED_NOT_SAVED": "received was not saved",
"VC_DELETED": "deleted",
"VC_DOWNLOADED": "downloaded",
"VC_REVOKED": "revoked",
@@ -8,5 +9,6 @@
"VC_RECEIVED_WITH_PRESENCE_VERIFIED": "received. Presence verified",
"VC_RECEIVED_BUT_PRESENCE_VERIFICATION_FAILED": "received. Presence verification failed",
"PRESENCE_VERIFIED_AND_VC_SHARED": "verified and shared",
"PRESENCE_VERIFICATION_FAILED": "verification failed"
"PRESENCE_VERIFICATION_FAILED": "verification failed",
"QRLOGIN_SUCCESFULL": "QRLogin sucessfull"
}

View File

@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { Dimensions } from 'react-native';
import { ListItem, Overlay, Input } from 'react-native-elements';
import { Icon, ListItem, Overlay, Input } from 'react-native-elements';
import { Text, Column, Row, Button } from './ui';
import { Theme } from './ui/styleUtils';
import { useTranslation } from 'react-i18next';
@@ -12,6 +12,13 @@ export const EditableListItem: React.FC<EditableListItemProps> = (props) => {
return (
<ListItem bottomDivider onPress={() => setIsEditing(true)}>
<Icon
name={props.Icon}
type="antdesign"
size={20}
style={Theme.Styles.profileIconBg}
color={Theme.Colors.Icon}
/>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileLabel}>{props.label}</Text>
@@ -48,5 +55,6 @@ export const EditableListItem: React.FC<EditableListItemProps> = (props) => {
interface EditableListItemProps {
label: string;
value: string;
Icon: string;
onEdit: (newValue: string) => void;
}

View File

@@ -49,7 +49,7 @@ export const Message: React.FC<MessageProps> = (props) => {
const Progress: React.FC<Pick<MessageProps, 'progress'>> = (props) => {
return typeof props.progress === 'boolean' ? (
props.progress && (
<LinearProgress variant="indeterminate" color={Colors.Orange} />
<LinearProgress variant="indeterminate" color={Theme.Colors.Icon} />
)
) : (
<LinearProgress variant="determinate" value={props.progress} />

View File

@@ -46,7 +46,7 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
if (hasPermission === false) {
return (
<Column fill align="space-between">
<Text align="center" margin="16 0" color={Theme.Colors.errorMessage}>
<Text align="center" color={Theme.Colors.errorMessage}>
{t('missingPermissionText')}
</Text>
<Button title={t('allowCameraButton')} onPress={openSettings} />
@@ -56,6 +56,11 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
return (
<View>
{props.title && (
<Text align="center" margin="16 0" color={Theme.Colors.Details}>
{props.title}
</Text>
)}
<View style={Theme.Styles.scannerContainer}>
<Camera
style={Theme.Styles.scanner}
@@ -94,4 +99,5 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
interface QrScannerProps {
onQrFound: (data: string) => void;
title?: string;
}

View File

@@ -75,7 +75,7 @@ const getDetails = (arg1, arg2, verifiableCredential) => {
}
return (
<Column>
<Text color={Theme.Colors.DetailsLabel} size="smaller">
<Text color={Theme.Colors.DetailsLabel} size="smaller" align="left">
{arg1}
</Text>
<Text
@@ -162,7 +162,7 @@ export const SingleVcItem: React.FC<VcItemProps> = (props) => {
style={Theme.Styles.closeCardImage}
/>
<Column margin="0 0 0 10">
<Column margin="0 0 0 10" style={{ alignItems: 'flex-start' }}>
{getDetails(t('fullName'), fullName, verifiableCredential)}
{getDetails(t('uin'), uin, verifiableCredential)}
{getDetails(t('generatedOn'), generatedOn, verifiableCredential)}

View File

@@ -14,5 +14,9 @@
"id": "Id",
"nationalCard": "National Card",
"uin": "UIN",
"vid": "VID"
}
"vid": "VID",
"enableVerification": "Enable Verification",
"profileAuthenticated": "Profile is authenticated!",
"offlineAuthDisabledHeader": "Offline Authentication disabled!",
"offlineAuthDisabledMessage": "Click 'Enable Authentication' to enable this credentials to be used for offline authentication."
}

View File

@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { Image, ImageBackground } from 'react-native';
import { Icon } from 'react-native-elements';
import { VC, CredentialSubject, LocalizedField } from '../types/vc';
import { Column, Row, Text } from './ui';
import { Button, Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils';
import { TextItem } from './ui/TextItem';
import { VcItemTags } from './VcItemTags';
@@ -255,11 +255,13 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
</Row>
<VcItemTags tag={props.vc?.tag} />
</ImageBackground>
{props.vc?.reason?.length > 0 && (
<Text margin="24 24 16 24" weight="semibold">
{t('reasonForSharing')}
</Text>
)}
{props.vc?.reason?.map((reason, index) => (
<TextItem
key={index}
@@ -271,12 +273,68 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
text={reason.message}
/>
))}
{props.isBindingPending ? (
<Column style={Theme.Styles.openCardBgContainer}>
<Row margin={'0 0 5 0'}>
<Icon
name="shield-alert"
color={Theme.Colors.Icon}
size={30}
type="material-community"
/>
</Row>
<Text
style={{ flex: 1 }}
weight="semibold"
size="small"
margin={'0 0 5 0'}
color={Theme.Colors.Details}>
{t('offlineAuthDisabledHeader')}
</Text>
<Text
style={{ flex: 1 }}
weight="regular"
size="small"
margin={'0 0 5 0'}
color={Theme.Colors.Details}>
{t('offlineAuthDisabledMessage')}
</Text>
<Button
title={t('enableVerification')}
onPress={props.onBinding}
type="radius"
/>
</Column>
) : (
<Column style={Theme.Styles.openCardBgContainer}>
<Row crossAlign="center">
<Icon
name="verified-user"
color={Theme.Colors.VerifiedIcon}
size={28}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
<Text
numLines={1}
color={Theme.Colors.Details}
weight="bold"
size="smaller"
margin="10 10 10 10"
children={t('profileAuthenticated')}></Text>
</Row>
</Column>
)}
</Column>
);
};
interface VcDetailsProps {
vc: VC;
isBindingPending: boolean;
onBinding?: () => void;
}
function getFullAddress(credential: CredentialSubject) {

View File

@@ -1,6 +1,6 @@
import React, { useContext, useRef } from 'react';
import { useInterpret, useSelector } from '@xstate/react';
import { Pressable, Image, ImageBackground } from 'react-native';
import { Pressable, Image, ImageBackground, Dimensions } from 'react-native';
import { CheckBox, Icon } from 'react-native-elements';
import { ActorRefFrom } from 'xstate';
import {
@@ -10,6 +10,7 @@ import {
vcItemMachine,
selectContext,
selectTag,
selectEmptyWalletBindingId,
} from '../machines/vcItem';
import { Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils';
@@ -81,6 +82,7 @@ const getDetails = (arg1, arg2, verifiableCredential) => {
color={Theme.Colors.Details}
weight="bold"
size="smaller"
align="left"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
@@ -93,6 +95,29 @@ const getDetails = (arg1, arg2, verifiableCredential) => {
}
};
const WalletVerified: React.FC = () => {
return (
<Icon
name="verified-user"
color={Theme.Colors.VerifiedIcon}
size={28}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};
const WalletUnverified: React.FC = () => {
return (
<Icon
name="shield-alert"
color={Theme.Colors.Icon}
size={28}
type="material-community"
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};
export const VcItem: React.FC<VcItemProps> = (props) => {
const { appService } = useContext(GlobalContext);
const { t } = useTranslation('VcDetails');
@@ -106,6 +131,7 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
const service = useInterpret(machine.current, { devTools: __DEV__ });
const context = useSelector(service, selectContext);
const verifiableCredential = useSelector(service, selectVerifiableCredential);
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
//Assigning the UIN and VID from the VC details to display the idtype label
const uin = verifiableCredential?.credentialSubject.UIN;
@@ -131,7 +157,11 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
<Pressable
onPress={() => props.onPress(service)}
disabled={!verifiableCredential}
style={Theme.Styles.closeCardBgContainer}>
style={
props.selected
? Theme.Styles.selectedBindedVc
: Theme.Styles.closeCardBgContainer
}>
<ImageBackground
source={!verifiableCredential ? null : Theme.CloseCard}
resizeMode="stretch"
@@ -211,6 +241,67 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
</Row>
<VcItemTags tag={tag} />
</ImageBackground>
<Row>
{emptyWalletBindingId ? (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row crossAlign="center" style={{ flex: 1 }}>
{verifiableCredential && <WalletUnverified />}
<Text
color={Theme.Colors.Details}
weight="semibold"
size="small"
margin="10 33 10 10"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}
children={t('offlineAuthDisabledHeader')}></Text>
</Row>
<Pressable>
<Icon
name="dots-three-horizontal"
type="entypo"
color={Theme.Colors.GrayIcon}
/>
</Pressable>
</Row>
) : (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row crossAlign="center" style={{ flex: 1 }}>
<WalletVerified />
<Text
color={Theme.Colors.Details}
weight="semibold"
size="smaller"
margin="10 10 10 10"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}
children={t('profileAuthenticated')}></Text>
</Row>
{props.showOnlyBindedVc ? null : (
<Pressable>
<Icon
name="dots-three-horizontal"
type="entypo"
color={Theme.Colors.GrayIcon}
/>
</Pressable>
)}
</Row>
)}
</Row>
</Pressable>
);
};
@@ -220,6 +311,7 @@ interface VcItemProps {
margin?: string;
selectable?: boolean;
selected?: boolean;
showOnlyBindedVc?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
}

View File

@@ -42,7 +42,7 @@ export const Button: React.FC<ButtonProps> = (props) => {
weight="semibold"
align="center"
color={
type === 'solid' || type === 'addId'
type === 'solid' || type === 'addId' || type === 'radius'
? Theme.Colors.whiteText
: Theme.Colors.AddIdBtnTxt
}>

View File

@@ -12,13 +12,14 @@ export const Modal: React.FC<ModalProps> = (props) => {
visible={props.isVisible}
onShow={props.onShow}
onRequestClose={props.onDismiss}>
<Column fill safe>
<Column fill safe align="center">
<Row elevation={props.headerElevation}>
<View
style={{
flex: 1,
flexDirection: 'row',
marginHorizontal: 16,
alignItems: 'center',
marginHorizontal: 21,
marginVertical: 16,
}}>
{props.headerRight ? (
@@ -28,10 +29,18 @@ export const Modal: React.FC<ModalProps> = (props) => {
color={Theme.Colors.Icon}
/>
) : null}
<Row fill align="center">
{props.arrowLeft ? (
<Icon
name="arrow-left"
type="material-community"
onPress={props.onDismiss}
color={Theme.Colors.Details}
/>
) : null}
<Row fill align="center" margin={'5 30 0 0'}>
<Text weight="semibold">{props.headerTitle}</Text>
</Row>
{props.headerRight || (
{props.headerRight || props.arrowLeft || (
<Icon
name="close"
onPress={props.onDismiss}
@@ -52,5 +61,6 @@ export interface ModalProps {
headerTitle?: string;
headerElevation?: ElevationLevel;
headerRight?: React.ReactElement;
arrowLeft?: React.ReactElement;
onShow?: () => void;
}

View File

@@ -8,11 +8,13 @@ const Colors = {
Grey5: '#E0E0E0',
Grey6: '#F2F2F2',
Orange: '#F2811D',
LightGrey: '#FAF9FF',
LightGrey: '#f7f5f0',
White: '#FFFFFF',
Red: '#EB5757',
Green: '#219653',
Transparent: 'transparent',
Warning: '#f0ad4e',
LightOrange: '#fce7e3',
};
export type ElevationLevel = 0 | 1 | 2 | 3 | 4 | 5;
@@ -30,6 +32,7 @@ export const DefaultTheme = {
noUinText: Colors.Orange,
IconBg: Colors.Orange,
Icon: Colors.Orange,
GrayIcon: Colors.Grey,
borderBottomColor: Colors.Grey6,
whiteBackgroundColor: Colors.White,
lightGreyBackgroundColor: Colors.LightGrey,
@@ -55,6 +58,8 @@ export const DefaultTheme = {
checkCircleIcon: Colors.White,
OnboardingCircleIcon: Colors.White,
OnboardingCloseIcon: Colors.White,
WarningIcon: Colors.Warning,
ProfileIconBg: Colors.LightOrange,
},
Styles: StyleSheet.create({
title: {
@@ -110,10 +115,40 @@ export const DefaultTheme = {
shadowRadius: 3,
elevation: 4,
},
selectedBindedVc: {
borderRadius: 10,
margin: 5,
borderWidth: 2,
borderColor: Colors.Orange,
},
labelPartContainer: {
marginLeft: 16,
flex: 1,
},
urlContainer: {
backgroundColor: Colors.White,
padding: 10,
borderRadius: 12,
fontSize: 12,
},
lockDomainContainer: {
backgroundColor: Colors.White,
alignSelf: 'center',
borderRadius: 15,
width: 100,
},
bottomButtonsContainer: {
height: 120,
borderTopLeftRadius: 27,
borderTopRightRadius: 27,
padding: 6,
backgroundColor: Colors.White,
},
consentPageTop: {
backgroundColor: Colors.White,
height: 160,
borderRadius: 6,
},
labelPart: {
marginTop: 10,
alignItems: 'flex-start',
@@ -132,6 +167,8 @@ export const DefaultTheme = {
backgroundImageContainer: {
flex: 1,
padding: 10,
borderBottomColor: Colors.Grey,
borderBottomWidth: 1,
},
successTag: {
backgroundColor: Colors.Green,
@@ -169,6 +206,23 @@ export const DefaultTheme = {
flex: 1,
padding: 10,
},
profileIconBg: {
padding: 8,
width: 40,
height: 40,
borderRadius: 6,
backgroundColor: Colors.LightOrange,
},
domainVerifiyIcon: {
padding: 20,
marginLeft: 120,
width: 130,
height: 130,
borderRadius: 60,
borderWidth: 10,
borderColor: Colors.White,
backgroundColor: Colors.LightOrange,
},
closeCardImage: {
width: 105,
height: 135,
@@ -187,6 +241,7 @@ export const DefaultTheme = {
height: 300,
width: 300,
overflow: 'hidden',
marginLeft: 18,
},
scanner: {
height: 400,
@@ -325,6 +380,10 @@ export const DefaultTheme = {
clearAddIdBtnBg: {
backgroundColor: Colors.Transparent,
},
radius: {
borderRadius: 10,
backgroundColor: Colors.Orange,
},
}),
OIDCAuthStyles: StyleSheet.create({
viewContainer: {
@@ -408,6 +467,18 @@ export const DefaultTheme = {
borderTopRightRadius: 0,
},
}),
BindingVcWarningOverlay: StyleSheet.create({
overlay: {
elevation: 5,
backgroundColor: Colors.White,
padding: 0,
borderRadius: 15,
},
button: {
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
},
}),
RevokeStyles: StyleSheet.create({
buttonContainer: {
position: 'absolute',
@@ -577,6 +648,11 @@ export const DefaultTheme = {
CloseCard: require('../../../assets/ID-closed.png'),
ProfileIcon: require('../../../assets/placeholder-photo.png'),
MosipLogo: require('../../../assets/mosip-logo.png'),
DomainWarningLogo: require('../../../assets/domain-warning.png'),
WarningLogo: require('../../../assets/warningLogo.png'),
OtpLogo: require('../../../assets/otp-mobile-logo.png'),
SuccessLogo: require('../../../assets/success-logo.png'),
elevation(level: ElevationLevel): ViewStyle {
// https://ethercreative.github.io/react-native-shadow-generator/

View File

@@ -14,6 +14,7 @@ const Colors = {
Green: '#219653',
Purple: '#70308C',
Transparent: 'transparent',
Warning: '#f0ad4e',
};
export type ElevationLevel = 0 | 1 | 2 | 3 | 4 | 5;
@@ -30,6 +31,7 @@ export const PurpleTheme = {
noUinText: Colors.Purple,
IconBg: Colors.Purple,
Icon: Colors.Purple,
GrayIcon: Colors.Grey,
Loading: Colors.Purple,
borderBottomColor: Colors.Grey6,
whiteBackgroundColor: Colors.White,
@@ -56,6 +58,7 @@ export const PurpleTheme = {
checkCircleIcon: Colors.White,
OnboardingCircleIcon: Colors.White,
OnboardingCloseIcon: Colors.White,
WarningIcon: Colors.Warning,
},
Styles: StyleSheet.create({
title: {
@@ -133,6 +136,8 @@ export const PurpleTheme = {
backgroundImageContainer: {
flex: 1,
padding: 10,
borderBottomColor: Colors.Grey,
borderBottomWidth: 1,
},
successTag: {
backgroundColor: Colors.Green,
@@ -326,6 +331,11 @@ export const PurpleTheme = {
clearAddIdBtnBg: {
backgroundColor: Colors.Transparent,
},
radius: {
flex: 1,
borderRadius: 10,
backgroundColor: Colors.Orange,
},
}),
OIDCAuthStyles: StyleSheet.create({
viewContainer: {
@@ -574,10 +584,25 @@ export const PurpleTheme = {
zIndex: 1,
},
}),
BindingVcWarningOverlay: StyleSheet.create({
overlay: {
elevation: 5,
backgroundColor: Colors.White,
padding: 0,
borderRadius: 15,
},
button: {
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
},
}),
OpenCard: require('../../../purpleAassets/bg_cart_one.png'),
CloseCard: require('../../../purpleAassets/cart_unsel.png'),
ProfileIcon: require('../../../purpleAassets/profile_icon_unsel.png'),
MosipLogo: require('../../../purpleAassets/logo.png'),
WarningLogo: require('../../../assets/warningLogo.png'),
OtpLogo: require('../../../assets/otp-mobile-logo.png'),
SuccessLogo: require('../../../assets/success-logo.png'),
elevation(level: ElevationLevel): ViewStyle {
// https://ethercreative.github.io/react-native-shadow-generator/

View File

@@ -302,7 +302,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MOSIPResidentApp/MOSIPResidentApp.entitlements;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_TEAM = 9L83VVTX8B;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -335,7 +335,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MOSIPResidentApp/MOSIPResidentApp.entitlements;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_TEAM = 9L83VVTX8B;
INFOPLIST_FILE = MOSIPResidentApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;

View File

@@ -62,7 +62,7 @@ PODS:
- GoogleSymbolUtilities (1.1.2)
- GoogleUtilitiesLegacy (1.3.2):
- GoogleSymbolUtilities (~> 1.1)
- mosip-inji-face-sdk (0.1.1):
- mosip-inji-face-sdk (0.1.7):
- React-Core
- NearbyMessages (1.1.1):
- GoogleInterchangeUtilities (~> 1.2)
@@ -348,6 +348,8 @@ PODS:
- React-Core
- RNDeviceInfo (8.7.1):
- React-Core
- RNFS (2.20.0):
- React-Core
- RNGestureHandler (2.1.3):
- React-Core
- RNKeychain (8.0.0):
@@ -434,6 +436,7 @@ DEPENDENCIES:
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCPicker (from `../node_modules/@react-native-picker/picker`)"
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
- RNFS (from `../node_modules/react-native-fs`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNKeychain (from `../node_modules/react-native-keychain`)
- RNPermissions (from `../node_modules/react-native-permissions`)
@@ -571,6 +574,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-picker/picker"
RNDeviceInfo:
:path: "../node_modules/react-native-device-info"
RNFS:
:path: "../node_modules/react-native-fs"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNKeychain:
@@ -613,13 +618,13 @@ SPEC CHECKSUMS:
EXUpdates: a83e036243b0f6ece53a8c1cb883b6751c88a5f8
EXUpdatesInterface: a9814f422d3cd6e7cfd260d13c27786148ece20e
FBLazyVector: fa8275d5086566e22a26ddc385ab5772e7f9b1bd
FBReactNativeSpec: 7ead992e0bbaf608b93d456361caa6ccf6745df5
FBReactNativeSpec: c3dafd68550f3c95f009beee5c20ab07949ec4e4
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
GoogleInterchangeUtilities: d5bc4d88d5b661ab72f9d70c58d02ca8c27ad1f7
GoogleNetworkingUtilities: 3edd3a8161347494f2da60ea0deddc8a472d94cb
GoogleSymbolUtilities: 631ee17048aa5e9ab133470d768ea997a5ef9b96
GoogleUtilitiesLegacy: 5501bedec1646bd284286eb5fc9453f7e23a12f4
mosip-inji-face-sdk: f0e765373b50324243d904e45eb3ce899db951ac
mosip-inji-face-sdk: a1355473a393f2cdd6d927c51af4363be6d97d9f
NearbyMessages: bd9e88f2df7fbab78be58fed58580d5d5bd62cbf
Permission-BluetoothPeripheral: 67708853584bb9208c76d36d0e0ea4eafb97ea5b
Permission-Camera: bf6791b17c7f614b6826019fcfdcc286d3a107f6
@@ -655,6 +660,7 @@ SPEC CHECKSUMS:
RNCAsyncStorage: 6bd5a7ba3dde1c3facba418aa273f449bdc5437a
RNCPicker: cb57c823d5ce8d2d0b5dfb45ad97b737260dc59e
RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: e1099204721a17a89c81fcd1cc2e92143dc040fb
RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94
RNPermissions: dcdb7b99796bbeda6975a6e79ad519c41b251b1c
@@ -666,6 +672,6 @@ SPEC CHECKSUMS:
Yoga: d1fc3575b8b68891ff5ef3c276daa855e841eb32
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 24c315d126fc2b5fef6a2828f16ec02b341154fe
PODFILE CHECKSUM: aca728a65db3db54edf138095b290280c97a5389
COCOAPODS: 1.11.3

View File

@@ -34,7 +34,18 @@
"id": "Id",
"nationalCard": "البطاقة الوطنية",
"uin": "UIN",
"vid": "VID"
"enableVerification": "تفعيل",
"profileAuthenticated": "تم تنشيطه لتسجيل الدخول عبر الإنترنت",
"offlineAuthDisabledHeader": "التنشيط معلق لتسجيل الدخول عبر الإنترنت",
"offlineAuthDisabledMessage": "الرجاء النقر فوق الزر أدناه لتفعيل بيانات الاعتماد هذه لاستخدامها في تسجيل الدخول عبر الإنترنت.",
"vid": "VID",
"verificationEnabledSuccess": "تم تنشيطه لتسجيل الدخول عبر الإنترنت",
"goback": "عُد",
"BindingWarning": "لقد قمت بالفعل بتنشيط تسجيل الدخول عبر الإنترنت لبيانات الاعتماد هذه على جهاز آخر. لن تتمكن بعد الآن من استخدام هذا الجهاز لتسجيل الدخول إذا قمت بتنشيطه مرة أخرى على هذا الجهاز.",
"yes_confirm": "نعم ، أنا أؤكد",
"no": "رقم",
"Alert": "انذار",
"ok": "تمام"
},
"AuthScreen": {
"header": "هل ترغب في استخدام المقاييس الحيوية لفتح التطبيق؟",
@@ -98,7 +109,8 @@
"requestingOTP": "طلب OTP..."
},
"OtpVerificationModal": {
"enterOtp": "أدخل رمز التحقق المكون من 6 أرقام الذي أرسلناه إليك"
"enterOtp": "أدخل رمز التحقق المكون من 6 أرقام الذي أرسلناه إليك",
"header": "التحقق من OTP"
},
"MyVcsTab": {
"addVcButton": "{{vcLabel}} إضافة",
@@ -129,6 +141,7 @@
"requestingOtp": "جارٍ طلب OTP...",
"editTag": "टتحرير العلامة",
"redirecting": "إعادة توجيه...",
"inProgress": "جار التحميل...",
"success": {
"unlocked": "تم إلغاء قفل {{vcLabel}} بنجاح",
"locked": "تم قفل {{vcLabel}} بنجاح",
@@ -166,6 +179,26 @@
"revokeSuccessful": "تم إبطال VID بنجاح",
"version": "الإصدار"
},
"QrScreen": {
"title": "QR تسجيل الدخول",
"alignQr": "قم بمحاذاة رمز الاستجابة السريعة داخل الإطار للمسح الضوئي",
"confirmation": "تأكيد",
"checkDomain": "تحقق أيضًا من وجود رمز قفل على شريط العناوين.",
"domainHead": "https://",
"selectId": "حدد المعرف",
"noBindedVc": "لا يوجد {{vcLabel}} مرتبط متاح للتحقق",
"back": "عُد",
"confirm": "يتأكد",
"verify": "تحقق",
"faceAuth": "مصادقة الوجه",
"consent": "موافقة",
"loading": "جار التحميل...",
"domainWarning": "يرجى تأكيد مجال موقع الويب الذي تقوم بمسح رمز الاستجابة السريعة منه على النحو التالي",
"access": " يطلب الوصول إلى",
"status": "حالة",
"successMessage":"لقد قمت بتسجيل الدخول بنجاح ",
"okay": "تمام"
},
"ReceiveVcScreen": {
"header": "تفاصيل {{vcLabel}}",
"acceptRequest": "قبول الطلب واستلام {{vcLabel}}",

BIN
locales/bkp-12-12-22.zip Normal file

Binary file not shown.

View File

@@ -1,4 +1,18 @@
{
"ActivityLogText": {
"VC_SHARED": "shared",
"VC_RECEIVED": "received",
"VC_RECEIVED_NOT_SAVED": "received was not saved",
"VC_DELETED": "deleted",
"VC_DOWNLOADED": "downloaded",
"VC_REVOKED": "revoked",
"VC_SHARED_WITH_VERIFICATION_CONSENT": "shared. Consent is given for presence verification",
"VC_RECEIVED_WITH_PRESENCE_VERIFIED": "received. Presence verified",
"VC_RECEIVED_BUT_PRESENCE_VERIFICATION_FAILED": "received. Presence verification failed",
"PRESENCE_VERIFIED_AND_VC_SHARED": "verified and shared",
"PRESENCE_VERIFICATION_FAILED": "verification failed",
"QRLOGIN_SUCCESFULL": "QRLogin sucessfull"
},
"DeviceInfoList": {
"requestedBy": "Requested by",
"sentBy": "Sent by",
@@ -34,7 +48,18 @@
"id": "Id",
"nationalCard": "National Card",
"uin": "UIN",
"vid": "VID"
"enableVerification": "Activate",
"profileAuthenticated": "Activated for online login",
"offlineAuthDisabledHeader": "Activation pending for online login",
"offlineAuthDisabledMessage": "Please click the button below to activate this credential to be used for online login.",
"vid": "VID",
"verificationEnabledSuccess": "Activated for online login",
"goback": "GO BACK",
"BindingWarning": "You have already activated online login for this credential on another device. You will no longer be able to use that device for login if you activate it again on this device.",
"yes_confirm": "Yes, I Confirm",
"no": "No",
"Alert": "Alert",
"ok": "Okay"
},
"AuthScreen": {
"header": "Would you like to use biometrics to unlock the application?",
@@ -98,7 +123,8 @@
"requestingOTP": "Requesting OTP..."
},
"OtpVerificationModal": {
"enterOtp": "Enter the 6-digit verification code we sent you"
"enterOtp": "Enter the 6-digit verification code we sent you",
"header": "OTP Verification"
},
"MyVcsTab": {
"addVcButton": "Add {{vcLabel}}",
@@ -129,6 +155,7 @@
"requestingOtp": "Requesting OTP...",
"editTag": "Rename",
"redirecting": "Redirecting...",
"inProgress": "Loading...",
"success": {
"unlocked": "{{vcLabel}} successfully unlocked",
"locked": "{{vcLabel}} successfully locked",
@@ -168,11 +195,31 @@
"useBle": "Powered by BLE",
"useGoogleNearby": "Powered by GoogleNearby"
},
"QrScreen": {
"title": "QR Login",
"alignQr": "Align the QR code within the frame to scan",
"confirmation": "Confirmation",
"checkDomain": "Also, check that there is a lock icon on the address bar.",
"domainHead": "https://",
"selectId": "Select ID",
"noBindedVc": "No Binded {{vcLabel}} Available to Verify",
"back": "Go Back",
"confirm": "Confirm",
"verify": "Verify",
"faceAuth": "Face Authentication",
"consent": "Consent",
"loading": "Loading...",
"domainWarning": "Please confirm the domain of the website you are scanning the QR code from is as below",
"access": " is requesting access to",
"status": "Status",
"successMessage": "You Have Successfully Logged Into ",
"okay": "Okay"
},
"ReceiveVcScreen": {
"header": "{{vcLabel}} details",
"acceptRequest": "Accept request and receive {{vcLabel}}",
"acceptRequestAndVerify": "Accept request and verify",
"reject": "Reject"
"save": "Save {{vcLabel}}",
"verifyAndSave": "Verify and save",
"discard": "Discard"
},
"RequestScreen": {
"bluetoothDenied": "Please enable Bluetooth to be able to request {{vcLabel}}",
@@ -186,7 +233,7 @@
},
"rejected": {
"title": "Notice",
"message": "You rejected {{sender}}'s {{vcLabel}}"
"message": "You discarded {{sender}}'s {{vcLabel}}"
},
"disconnected": {
"title": "Disconnected",
@@ -200,6 +247,9 @@
"connected": {
"message": "Connected to the device. Waiting for {{vcLabel}}...",
"timeoutHint": "No data received yet. Is sending device still connected?"
},
"offline": {
"message": "Please connect to the internet to enable Online sharing mode"
}
},
"online": "Online",
@@ -225,7 +275,10 @@
"connectingTimeout": "It's taking a while to establish the connection. Is the other device open for connections?",
"exchangingDeviceInfo": "Exchanging device info...",
"exchangingDeviceInfoTimeout": "It's taking a while to exchange device info. You may have to reconnect.",
"invalid": "Invalid QR Code"
"invalid": "Invalid QR Code",
"offline": "Please connect to the internet to scan QR codes using Online sharing mode",
"sent": "{{ vcLabel }} has been sent...",
"sentHint": "Waiting for receiver to save or discard your {{ vcLabel }}"
}
},
"SelectVcOverlay": {
@@ -251,7 +304,7 @@
},
"rejected": {
"title": "Notice",
"message": "Your {{vcLabel}} was rejected by {{receiver}}"
"message": "Your {{vcLabel}} was discarded by {{receiver}}"
}
},
"consentToPhotoVerification": "I give consent to have my photo taken for authentication"

View File

@@ -1,4 +1,17 @@
{
"ActivityLogText": {
"VC_SHARED": "ibinahagi",
"VC_RECEIVED": "natanggap",
"VC_RECEIVED_NOT_SAVED": "natanggap ngunit hindi na-save",
"VC_DELETED": "tinanggal",
"VC_DOWNLOADED": "na-download",
"VC_REVOKED": "binawi",
"VC_SHARED_WITH_VERIFICATION_CONSENT": "ibinahagi. Ibinibigay ang pahintulot para sa pag-verify ng presensya",
"VC_RECEIVED_WITH_PRESENCE_VERIFIED": "natanggap. Na-verify ang presensya",
"VC_RECEIVED_BUT_PRESENCE_VERIFICATION_FAILED": "natanggap. Nabigo ang pag-verify ng presensya",
"PRESENCE_VERIFIED_AND_VC_SHARED": "na-verify at ibinahagi",
"PRESENCE_VERIFICATION_FAILED": "nabigo ang pag-verify"
},
"DeviceInfoList": {
"requestedBy": "Hiniling ni",
"sentBy": "Ipinadala ni",
@@ -34,7 +47,18 @@
"id": "Id",
"nationalCard": "Pambansang Kard",
"uin": "UIN",
"vid": "VID"
"enableVerification": "I-activate",
"profileAuthenticated": "Na-activate para sa online na pag-login",
"offlineAuthDisabledHeader": "Nakabinbin ang pag-activate para sa online na pag-login",
"offlineAuthDisabledMessage": "Mangyaring i-click ang pindutan sa ibaba upang i-activate ang kredensyal na ito upang magamit para sa online na pag-login.",
"vid": "VID",
"verificationEnabledSuccess": "Na-activate para sa online na pag-login",
"goback": "BUMALIK KA",
"BindingWarning": "Na-activate mo na ang isang online na pag-log in para sa kredensyal na ito sa isa pang device. Hindi mo na magagamit ang device na iyon para sa pag-login kung i-activate mo itong muli sa device na ito.",
"yes_confirm": "Oo, Kinukumpirma ko",
"no": "Hindi",
"Alert": "Alerto",
"ok": "Sige"
},
"AuthScreen": {
"header": "Gusto mo bang gumamit ng biometrics upang i-unlock ang aplikasyon?",
@@ -42,7 +66,7 @@
"usePasscode": "Gumamit na lang ng passcode",
"errors": {
"unavailable": "Hindi sinusuportahan ng device ang Biometrics",
"unenrolled": "Upang magamit ang Biometrics, mangyaring i-enroll ang iyong biometrics sa mga setting ng iyong device",
"unenrolled": "Upang magamit ang Biometrics, mangyaring i-enroll ang iyong fingerprint sa mga setting ng iyong device",
"failed": "Nabigong mag-authenticate gamit ang Biometrics",
"generic": "May problema sa Biometrics authentication"
}
@@ -83,22 +107,14 @@
"bodyText": "Maaaring tumagal ito ng ilang oras, ipapaalam namin sayo kung pwede na kunin ang iyong {{vcLabel}}",
"backButton": "Bumalik"
},
"GetIdInputModal": {
"header": "Upang makuha ang iyong UIN o VID, ilagay ang iyong Application {{vcLabel}} na numero",
"getUIN": "Kumuha ng UIN/VID",
"applicationId": "Numero ng application na {{vcLabel}}.",
"requestingOTP": "Humihiling ng OTP...",
"qstnMarkToolTip": "Ang numero ng aplikasyon {{vcLabel}} ay naka-print sa ibinigay na pagkilala pagkatapos ng pagpapatala"
},
"IdInputModal": {
"header": "Ilagay ang UIN o VID na ibinigay ng MOSIP upang matanggap ang iyong {{vcLabel}}",
"generateVc": "Gumawa ng {{vcLabel}}",
"enterId": "Ilagay ang iyong {{idType}}",
"noUIN/VID": "Wala ka bang UIN/VID? Kuhanin dito",
"requestingOTP": "Humihiling ng OTP..."
"enterId": "Ilagay ang iyong {{idType}}"
},
"OtpVerificationModal": {
"enterOtp": "Ilagay ang 6 na numerong verification code na ipinadala namin sayo"
"enterOtp": "Ilagay ang 6 na numerong verification code na ipinadala namin sayo",
"header": "Pag-verify ng OTP"
},
"MyVcsTab": {
"addVcButton": "Magdagdag ng {{vcLabel}}",
@@ -129,6 +145,7 @@
"requestingOtp": "Humihiling ng OTP...",
"editTag": "Palitan ang pangalan",
"redirecting": "Redirecting...",
"inProgress": "Naglo-load...",
"success": {
"unlocked": "Ang {{vcLabel}} ay matagumpay na na-unlock",
"locked": "Ang {{vcLabel}} ay matagumpay na na-lock",
@@ -166,10 +183,29 @@
"revokeSuccessful": "Matagumpay na nakansela ang VID",
"version": "Bersyon"
},
"QrScreen": {
"title": "QR Login",
"alignQr": "I-align ang QR code sa loob ng frame para i-scan",
"confirmation": "Kumpirmasyon",
"checkDomain": "Gayundin, tingnan kung mayroong icon ng lock sa address bar.",
"domainHead": "https://",
"selectId": "Pumili ng ID",
"noBindedVc": "Available sa Verifyct ID ang SeleNo Binded {{vcLabel}}",
"back": "Bumalik ka",
"confirm": "Kumpirmahin",
"verify": "I-verify",
"faceAuth": "Face Authentication",
"consent": "Pagpayag",
"loading": "Naglo-load...",
"domainWarning": "Pakikumpirma ang domain ng website kung saan mo ini-scan ang QR code mula sa ibaba",
"access": " ay humihiling ng access sa",
"status": "Katayuan",
"successMessage":"Ikaw ay Matagumpay na Naka-log In ",
"okay": "Sige"
},
"ReceiveVcScreen": {
"header": "Mga detalye ng {{vcLabel}}",
"acceptRequest": "Tanggapin ang kahilingan at tumanggap ng {{vcLabel}}",
"acceptRequestAndVerify": "Tanggapin ang kahilingan at magpatunay",
"reject": "Tanggihan"
},
"RequestScreen": {
@@ -184,7 +220,7 @@
},
"rejected": {
"title": "Paunawa",
"message": "Tinanggihan mo ang {{vcLabel}} ni {{sender}}"
"message": "Iwinaksi ang {{vcLabel}} ni {{sender}}"
},
"disconnected": {
"title": "Nadiskonekta",
@@ -197,7 +233,7 @@
},
"connected": {
"message": "Nakakonektang device. Naghihintay para sa {{vcLabel}}...",
"timeoutHint": "Wala pang data na natanggap. Nakakonekta pa rin ba ang nagpapadala na device?"
"timeoutHint": "Wala pang natanggap na VC. Nakakonekta pa rin ba ang pagpapadala ng device?"
}
},
"online": "Online",
@@ -223,7 +259,10 @@
"connectingTimeout": "Medyo nagtatagal bago magtatag ng koneksyon. Bukas ba ang ibang device para sa mga koneksyon?",
"exchangingDeviceInfo": "Nagpapalitan ng impormasyon ng device...",
"exchangingDeviceInfoTimeout": "Medyo nagtatagal ang paglabas ng impormasyon ng device. Bukas ba ang ibang device para sa mga koneksyon?",
"invalid": "Di-wasto ang QR Code"
"invalid": "Di-wasto ang QR Code",
"offline": "Mangyaring kumonekta sa internet upang makapag-scan ng QR codes na gumagamit ng Online sharing mode",
"sent": "Naibahagi na ang {{vcLabel}}...",
"sentHint": "Iniintay ang nakatanggap na itabi o iwaksi ang iyong {{vcLabel}}"
}
},
"SelectVcOverlay": {
@@ -235,13 +274,11 @@
"SendVcScreen": {
"reasonForSharing": "Dahilan ng pagbabahagi (opsyonal)",
"acceptRequest": "Tanggapin ang kahilingan at piliin ang {{vcLabel}}",
"acceptRequestAndVerify": "Tanggapin ang kahilingan at magpatunay",
"reject": "Tanggihan",
"status": {
"sharing": {
"title": "Pagbabahagi",
"hint": "Pakihintay na tanggapin o tanggihan ng tumatanggap na device ang pagbabahagi.",
"timeoutHint": "May katagalan ang pagbabahagi. Maaaring may problema sa koneksyon."
"timeoutHint": "Medyo natatagal ang pagbabahagi ng VC. Maaaring may problema sa koneksyon."
},
"accepted": {
"title": "Tagumpay!",
@@ -261,8 +298,7 @@
"errors": {
"invalidIdentity": {
"title": "Hindi ma-verify ang pagkakakilanlan",
"message": "May naganap na error at hindi namin ma-scan ang iyong larawan. Subukang muli, tiyaking nakikita ang iyong mukha, walang anumang mga aksesorya.",
"messageNoRetry": "May naganap na error at hindi namin ma-scan ang iyong portrait."
"message": "May naganap na error at hindi namin ma-scan ang iyong portrait. Subukang muli, tiyaking nakikita ang iyong mukha, walang anumang mga accessory."
}
}
},
@@ -274,14 +310,7 @@
"common": {
"cancel": "Kanselahin",
"save": "I-save",
"dismiss": "I-dismiss",
"editLabel": "Palitan ang {{label}}",
"tryAgain": "Subukan muli",
"camera": {
"errors": {
"missingPermission": "Ginagamit ng app na ito ang camera upang i-scan ang QR code ng isa pang device."
},
"allowAccess": "Payagan ang access sa camera"
}
"tryAgain": "Subukan muli"
}
}

View File

@@ -19,22 +19,33 @@
"allowCameraButton": "कैमरे तक पहुंच की अनुमति दें"
},
"VcDetails": {
"generatedOn": "जेनरेट ऑन",
"status": "स्थिति",
"generatedOn": "पर उत्पन्न हुआ",
"status": "दर्जा",
"valid": "वैध",
"photo": "फोटो",
"photo": "फोटो",
"fullName": "पूरा नाम",
"gender": "लिंग",
"dateOfBirth": "डेट ऑफ़ बर्थ",
"dateOfBirth": "जन्म की तारीख",
"phoneNumber": "फ़ोन नंबर",
"email": "ईमेल",
"address": "पता",
"reasonForSharing": "साझा करने का कारण",
"idType": "आईडी का प्रकार",
"id": "Id",
"idType": "पहचान का प्रकार",
"id": "पहचान",
"nationalCard": "राष्ट्रीय कार्ड",
"uin": "UIN",
"vid": "VID"
"enableVerification": "सक्रिय",
"profileAuthenticated": "ऑनलाइन लॉगिन के लिए सक्रिय",
"offlineAuthDisabledHeader": "ऑनलाइन लॉगिन के लिए सक्रियता लंबित है",
"offlineAuthDisabledMessage": "ऑनलाइन लॉगिन के लिए उपयोग किए जाने वाले इस क्रेडेंशियल को सक्रिय करने के लिए कृपया नीचे दिए गए बटन पर क्लिक करें।",
"vid": "VID",
"verificationEnabledSuccess": "ऑनलाइन लॉगिन के लिए सक्रिय",
"goback": "वापस जाओ",
"BindingWarning": "आप इस क्रेडेंशियल के लिए किसी दूसरे डिवाइस पर ऑनलाइन लॉगिन पहले ही सक्रिय कर चुके हैं. यदि आप इसे इस डिवाइस पर फिर से सक्रिय करते हैं तो आप लॉगिन के लिए उस डिवाइस का उपयोग नहीं कर पाएंगे।",
"yes_confirm": "हां, मैं पुष्टि करता हूं",
"no": "नहीं",
"Alert": "चेतावनी",
"ok": "ठीक"
},
"AuthScreen": {
"header": "क्या आप एप्लिकेशन को अनलॉक करने के लिए बायोमेट्रिक्स का उपयोग करना चाहेंगे?",
@@ -98,7 +109,8 @@
"requestingOTP": "OTP का अनुरोध..."
},
"OtpVerificationModal": {
"enterOtp": "हमारे द्वारा भेजा गया 6 अंकों का सत्यापन कोड दर्ज करें"
"enterOtp": "हमारे द्वारा भेजा गया 6 अंकों का सत्यापन कोड दर्ज करें",
"header": "ओटीपी सत्यापन"
},
"MyVcsTab": {
"addVcButton": "{{vcLabel}} जोड़ें",
@@ -129,6 +141,7 @@
"requestingOtp": "OTP का अनुरोध...",
"editTag": "टैग संपादित करें",
"redirecting": "पुन: निर्देशित...",
"inProgress": "लोड हो रहा है...",
"success": {
"unlocked": "{{vcLabel}} सफलतापूर्वक अनलॉक किया गया",
"locked": "{{vcLabel}} सफलतापूर्वक लॉक हो गया",
@@ -166,6 +179,26 @@
"revokeSuccessful": "VID को सफलतापूर्वक निरस्त कर दिया गया",
"version": "संस्करण"
},
"QrScreen": {
"title": "क्यूआर लॉगिन",
"alignQr": "स्कैन करने के लिए फ्रेम के भीतर क्यूआर कोड को संरेखित करें",
"confirmation": "पुष्टीकरण",
"checkDomain": "यह भी जांचें कि एड्रेस बार पर लॉक आइकन है।",
"domainHead": "https://",
"selectId": "आईडी चुनें",
"noBindedVc": "सत्यापित करने के लिए कोई आबद्ध {{vcLabel}} उपलब्ध नहीं है",
"back": "वापस जाओ",
"confirm": "पुष्टि करें",
"verify": "सत्यापित करना",
"faceAuth": "चेहरा प्रमाणीकरण",
"consent": "अनुमति",
"loading": "लोड हो रहा है...",
"domainWarning": "कृपया उस वेबसाइट के डोमेन की पुष्टि करें जिसे आप नीचे दिए गए क्यूआर कोड से स्कैन कर रहे हैं",
"access": " तक पहुंच का अनुरोध कर रहा है",
"status": "दर्जा",
"successMessage":"आपने सफलतापूर्वक लॉग इन कर लिया है ",
"okay": "ठीक"
},
"ReceiveVcScreen": {
"header": "{{vcLabel}} विवरण",
"acceptRequest": "अनुरोध स्वीकार करें और {{vcLabel}} प्राप्त करें",

View File

@@ -34,7 +34,18 @@
"id": "Id",
"nationalCard": "ರಾಷ್ಟ್ರೀಯ ಕಾರ್ಡ್",
"uin": "UIN",
"vid": "VID"
"enableVerification": "ಸಕ್ರಿಯಗೊಳಿಸಿ",
"profileAuthenticated": "ಆನ್‌ಲೈನ್ ಲಾಗಿನ್‌ಗಾಗಿ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ",
"offlineAuthDisabledHeader": "ಆನ್‌ಲೈನ್ ಲಾಗಿನ್‌ಗಾಗಿ ಸಕ್ರಿಯಗೊಳಿಸುವಿಕೆ ಬಾಕಿ ಉಳಿದಿದೆ",
"offlineAuthDisabledMessage": "ಆನ್‌ಲೈನ್ ಲಾಗಿನ್‌ಗಾಗಿ ಬಳಸಲು ಈ ರುಜುವಾತುಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ದಯವಿಟ್ಟು ಕೆಳಗಿನ ಬಟನ್ ಅನ್ನು ಕ್ಲಿಕ್ ಮಾಡಿ.",
"vid": "VID",
"verificationEnabledSuccess": "ಆನ್‌ಲೈನ್ ಲಾಗಿನ್‌ಗಾಗಿ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ",
"goback": "ಹಿಂದೆ ಹೋಗು",
"BindingWarning": "ನೀವು ಈಗಾಗಲೇ ಇನ್ನೊಂದು ಸಾಧನದಲ್ಲಿ ಈ ರುಜುವಾತುಗಳಿಗಾಗಿ ಆನ್‌ಲೈನ್ ಲಾಗಿನ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿದ್ದೀರಿ. ಈ ಸಾಧನದಲ್ಲಿ ನೀವು ಅದನ್ನು ಮತ್ತೆ ಸಕ್ರಿಯಗೊಳಿಸಿದರೆ ಲಾಗಿನ್‌ಗಾಗಿ ಆ ಸಾಧನವನ್ನು ಬಳಸಲು ನಿಮಗೆ ಇನ್ನು ಮುಂದೆ ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ.",
"yes_confirm": "ಹೌದು, ನಾನು ದೃಢೀಕರಿಸುತ್ತೇನೆ",
"no": "ಸಂ",
"Alert": "ಎಚ್ಚರಿಕೆ",
"ok": "ಸರಿ"
},
"AuthScreen": {
"header": "ಅಪ್ಲಿಕೇಶನ್ ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಬಯೋಮೆಟ್ರಿಕ್ಸ್ ಬಳಸಲು ನೀವು ಬಯಸುವಿರಾ?",
@@ -98,7 +109,8 @@
"requestingOTP": "OTP ಯನ್ನು ವಿನಂತಿಸಲಾಗುತ್ತಿದೆ..."
},
"OtpVerificationModal": {
"enterOtp": "ನಾವು ನಿಮಗೆ ಕಳುಹಿಸಿದ 6-ಅಂಕಿಯ ಪರಿಶೀಲನೆ ಕೋಡ್ ಅನ್ನು ನಮೂದಿಸಿ"
"enterOtp": "ನಾವು ನಿಮಗೆ ಕಳುಹಿಸಿದ 6-ಅಂಕಿಯ ಪರಿಶೀಲನೆ ಕೋಡ್ ಅನ್ನು ನಮೂದಿಸಿ",
"header": "OTP ಪರಿಶೀಲನೆ"
},
"MyVcsTab": {
"addVcButton": "ಸೇರಿಸಿ {{vcLabel}}",
@@ -129,6 +141,7 @@
"requestingOtp": "ಒಟಿಪಿಯನ್ನು ವಿನಂತಿಸಲಾಗುತ್ತಿದೆ...",
"editTag": "ಟ್ಯಾಗ್ ಸಂಪಾದಿಸು",
"redirecting": "Redirecting...",
"inProgress": "ಲೋಡ್ ಆಗುತ್ತಿದೆ...",
"success": {
"unlocked": "{{vcLabel}} ಅನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಅನ್‌ಲಾಕ್ ಮಾಡಲಾಗಿದೆ",
"locked": "{{vcLabel}} successfully locked",
@@ -166,6 +179,26 @@
"revokeSuccessful": "VID ಯಶಸ್ವಿಯಾಗಿ ಹಿಂಪಡೆಯಲಾಗಿದೆ",
"version": "ಆವೃತ್ತಿ"
},
"QrScreen": {
"title": "QR ಲಾಗಿನ್",
"alignQr": "ಸ್ಕ್ಯಾನ್ ಮಾಡಲು ಚೌಕಟ್ಟಿನೊಳಗೆ QR ಕೋಡ್ ಅನ್ನು ಹೊಂದಿಸಿ",
"confirmation": "ದೃಢೀಕರಣ",
"checkDomain": "ಅಲ್ಲದೆ, ವಿಳಾಸ ಪಟ್ಟಿಯಲ್ಲಿ ಲಾಕ್ ಐಕಾನ್ ಇದೆಯೇ ಎಂದು ಪರಿಶೀಲಿಸಿ.",
"domainHead": "https://",
"selectId": "ID ಆಯ್ಕೆಮಾಡಿ",
"noBindedVc": "ಪರಿಶೀಲಿಸಲು ಯಾವುದೇ ಬೈಂಡೆಡ್ {{vcLabel}} ಲಭ್ಯವಿಲ್ಲ",
"back": "ಹಿಂದೆ ಹೋಗು",
"confirm": "ದೃಢೀಕರಿಸಿ",
"verify": "ಪರಿಶೀಲಿಸಿ",
"faceAuth": "ಮುಖದ ದೃಢೀಕರಣ",
"consent": "ಒಪ್ಪಿಗೆ",
"loading": "ಲೋಡ್ ಆಗುತ್ತಿದೆ...",
"domainWarning": "ಕೆಳಗಿನಂತೆ ನೀವು QR ಕೋಡ್ ಅನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡುತ್ತಿರುವ ವೆಬ್‌ಸೈಟ್‌ನ ಡೊಮೇನ್ ಅನ್ನು ದಯವಿಟ್ಟು ಖಚಿತಪಡಿಸಿ",
"access": " ಗೆ ಪ್ರವೇಶವನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ",
"status": "ಸ್ಥಿತಿ",
"successMessage":"ನೀವು ಯಶಸ್ವಿಯಾಗಿ ಲಾಗ್ ಇನ್ ಆಗಿರುವಿರಿ ",
"okay": "ಸರಿ"
},
"ReceiveVcScreen": {
"header": "{{vcLabel}} ವಿವರಗಳು",
"acceptRequest": "ವಿನಂತಿಯನ್ನು ಸ್ವೀಕರಿಸಿ ಮತ್ತು {{vcLabel}} ಸ್ವೀಕರಿಸಿ",

View File

@@ -34,7 +34,18 @@
"id": "Id",
"nationalCard": "தேசிய அட்டை್",
"uin": "UIN",
"vid": "VID"
"enableVerification": "செயல்படுத்த",
"profileAuthenticated": "ஆன்லைன் உள்நுழைவுக்காக செயல்படுத்தப்பட்டது",
"offlineAuthDisabledHeader": "ஆன்லைன் உள்நுழைவுக்கான செயல்படுத்தல் நிலுவையில் உள்ளது",
"offlineAuthDisabledMessage": "இந்த நற்சான்றிதழை ஆன்லைன் உள்நுழைவுக்குப் பயன்படுத்த, கீழே உள்ள பொத்தானைக் கிளிக் செய்யவும்.",
"vid": "VID",
"verificationEnabledSuccess": "ஆன்லைன் உள்நுழைவுக்காக செயல்படுத்தப்பட்டது",
"goback": "திரும்பி செல்",
"BindingWarning": "இந்த நற்சான்றிதழுக்கான ஆன்லைன் உள்நுழைவை வேறொரு சாதனத்தில் ஏற்கனவே செயல்படுத்தியுள்ளீர்கள். இந்தச் சாதனத்தில் மீண்டும் அதைச் செயல்படுத்தினால், உள்நுழைவதற்கு அந்தச் சாதனத்தை இனி உங்களால் பயன்படுத்த முடியாது.",
"yes_confirm": "ஆம், நான் உறுதி செய்கிறேன்",
"no": "இல்லை",
"Alert": "எச்சரிக்கை",
"ok": "சரி"
},
"AuthScreen": {
"header": "பயன்பாட்டைத் திறக்க பயோமெட்ரிக்ஸைப் பயன்படுத்த விரும்புகிறீர்களா?",
@@ -98,7 +109,8 @@
"requestingOTP": "OTP ஐக் கோருகிறது..."
},
"OtpVerificationModal": {
"enterOtp": "நாங்கள் உங்களுக்கு அனுப்பிய 6 இலக்க சரிபார்ப்புக் குறியீட்டை உள்ளிடவும்"
"enterOtp": "நாங்கள் உங்களுக்கு அனுப்பிய 6 இலக்க சரிபார்ப்புக் குறியீட்டை உள்ளிடவும்",
"header": "OTP சரிபார்ப்பு"
},
"MyVcsTab": {
"addVcButton": "{{vcLabel}} சேர்",
@@ -129,6 +141,7 @@
"requestingOtp": "ஓடிபியைக் கோருகிறது...",
"editTag": "திருத்து குறி",
"redirecting": "வழிமாற்று...",
"inProgress": "ஏற்றுகிறது...",
"success": {
"unlocked": "{{vcLabel}} வெற்றிகரமாக திறக்கப்பட்டது",
"locked": "{{vcLabel}} வெற்றிகரமாக பூட்டப்பட்டது",
@@ -166,6 +179,26 @@
"revokeSuccessful": "VID வெற்றிகரமாக ரத்து செய்யப்பட்டது",
"version": "பதிப்பு"
},
"QrScreen": {
"title": "QR உள்நுழைவு",
"alignQr": "ஸ்கேன் செய்ய ஃப்ரேமுக்குள் QR குறியீட்டை சீரமைக்கவும்",
"confirmation": "உறுதிப்படுத்தல்",
"checkDomain": "மேலும், முகவரிப் பட்டியில் பூட்டு ஐகான் உள்ளதா எனச் சரிபார்க்கவும்.",
"domainHead": "https://",
"selectId": "ஐடியைத் தேர்ந்தெடுக்கவும்",
"noBindedVc": "சரிபார்க்க, பிணைக்கப்பட்ட {{vcLabel}} எதுவும் இல்லை",
"back": "திரும்பி செல்",
"confirm": "உறுதிப்படுத்தவும்",
"verify": "சரிபார்க்கவும்",
"faceAuth": "முக அங்கீகாரம்",
"consent": "சம்மதம்",
"loading": "ஏற்றுகிறது...",
"domainWarning": "நீங்கள் QR குறியீட்டை ஸ்கேன் செய்யும் இணையதளத்தின் டொமைனை கீழே உள்ளவாறு உறுதிப்படுத்தவும்",
"access": " அணுகலைக் கோருகிறது",
"status": "நிலை",
"successMessage":"நீங்கள் வெற்றிகரமாக உள்நுழைந்துள்ளீர்கள் ",
"okay": "சரி"
},
"ReceiveVcScreen": {
"header": "{{vcLabel}} விவரங்கள்",
"acceptRequest": "கோரிக்கையை ஏற்று {{vcLabel}} ஐப் பெறவும்",

421
machines/QrLoginMachine.ts Normal file
View File

@@ -0,0 +1,421 @@
import {
ActorRefFrom,
assign,
ErrorPlatformEvent,
EventFrom,
send,
StateFrom,
sendParent,
} from 'xstate';
import { createModel } from 'xstate/lib/model';
import { AppServices } from '../shared/GlobalContext';
import { MY_VCS_STORE_KEY } from '../shared/constants';
import { StoreEvents } from './store';
import { linkTransactionResponse, VC } from '../types/vc';
import { request } from '../shared/request';
import { getJwt } from '../shared/cryptoutil/cryptoUtil';
import { getPrivateKey } from '../shared/keystore/SecureKeystore';
const model = createModel(
{
serviceRefs: {} as AppServices,
selectedVc: {} as VC,
linkCode: '',
myVcs: [] as string[],
linkTransactionResponse: {} as linkTransactionResponse,
authFactors: [],
authorizeScopes: null,
clientName: '',
configs: {},
essentialClaims: [],
linkTransactionId: '',
logoUrl: '',
voluntaryClaims: [],
selectedVoluntaryClaims: [],
errorMessage: '',
consentClaims: ['name', 'picture'],
isSharing: {},
},
{
events: {
SELECT_VC: (vc: VC) => ({ vc }),
SCANNING_DONE: (params: string) => ({ params }),
STORE_RESPONSE: (response: unknown) => ({ response }),
STORE_ERROR: (error: Error) => ({ error }),
TOGGLE_CONSENT_CLAIM: (enable: boolean, claim: string) => ({
enable,
claim,
}),
DISMISS: () => ({}),
CONFIRM: () => ({}),
GET: (value: string) => ({ value }),
VERIFY: () => ({}),
CANCEL: () => ({}),
FACE_VALID: () => ({}),
FACE_INVALID: () => ({}),
RETRY_VERIFICATION: () => ({}),
},
}
);
export const QrLoginEvents = model.events;
export type QrLoginRef = ActorRefFrom<typeof qrLoginMachine>;
export const qrLoginMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QEUBOAZA9lAlgOwDocIAbMAYgGUBhAQQDl6BJegcQH0ARAeXoFEA2gAYAuolAAHTLBwAXHJjziQAD0QBGACzqATAQCs+nZv0BmABzqL50zoA0IAJ6IA7KaEFNATnPmv6oUMhHUMAX1CHNCxcQmIyck4mSgBZJMphMSQQKRl5RWU1BF1zFwIvHQrTFxdNCxcANgdnBH16j1NbdT8vfW8q9XDIjGx8AhJ8AGsAFVQAQzxYWYBjPLxyCEUwIjwAN0wJraiRwnG8abmF5dWEfD2l2dWMjOUcuQUlLMKtXQMjEwsrL5bE1EF4XOYCEJ6iEXF4ekIOppzIMQEcYmNJjN5osVu9yGBUKhMKgCBISA8AGbEgC2BDRo1O52xV3eN12mHuj1Ezyyr1WBQ0JQhmlhotMZmM+hcIIQNX0nnUhh65h06hcOhKKPphFgAAtMAB3ADqs1QeHwUHI1F4ADEmAAlZI8yTSN75T6IEIeHT+eo1Lyaeriwwy+pggiwtXw9SKjU6LXDdF6w0ms0Wq0Mah8dDO7Ku-kehBegg+9R+7yB4P6GV+dQRlwBIRqoTqjX1BPRUaUfUGviE4kZ+hZnOiF7594Coo9TSQtyqqUx9zApyINpeCNIoVaYzlUwd45jTCzCDJRwANSWsCoU249r47DvlAACrxKIJR7zx+7QIU-fLofoMaATYWjaKGLjylCOjQi4gSmJoOgNPuSY9jsSzoDgsCyFQ2Z8NQUzsGe1C5nyE6Fr4M4BkI3j6DYtEttWK4tDGEb1H6DbmEYAZschozJgaaEYVh5Bnnw9pMDaACaJFfh8P6uLRZRIoqKl0UI5gyiK9SQtobjUZu0KaLxOqoehmHYYkKRpDJuRkfJRTqRCpj1PoTYBj01QVKGPwVIBHT6AGqq1MZBAUssYC0AArrIurkDatBZoRtDoEwnA2W6cmqJ6rklr6-qVoYjHNP4kI2Jo1ENr01EBiFYVLBF0WxfFiUsGeyWpelBb2cWpbltxVahiKuVqmGXT1IG6heLV4VRTFg7Dp1dlZUWOW9flQaFTW2glrBYK0SqZaqiFtyzOMEBMBAYB4PIsiOAkSSpJQ6Qfi6tnfstf4EABQFdKYoHqKG1EECUrnmEIcJ+FC7YRKiiajCdZ0XVdN13XeUz2pJhFiRJTB0FMTC8It72FD1eUVhtIZMQF2mRjUHT1FYPTQ0MnaEKgYAAI6RXAsjUIosDI1atoOk6L15m9mW-hBX0hD9IGaGBTEIfKvTNoGOhVF4zOw6zBDs1zPN8wsgt0EO2ZE5Lq7S99ql-QrANMe4M6RqNDa6NC5hGTD2oEBseAUJZj3PZkr0ZZOAS+AQzmuZNNGefYSshAYunUdCmvaz7+vc1hFpGwL13rJs2x7AcdJw2znPZ-IeBQHnyNsncDzvE8YukcTq42GUYYtuNgFsQGMqqnWNjwSNiFeACe7e+XeuVzzuf84LBJEiSZKUjSZe61n8813X10NxyTeKC3Ifi2HhYR05LluXHrY1qYO2OanY1sciKJ4Jgl3wFk2pjhLk4AFpGhMQAfKOE4CIGQKnizA8cQwB-3PvZYoEJVT+FVCUaC-hNDeXlDYCoipwRCCIeKEKjIsSXFxO9NulsiguWdpYIMQZoK9F6DKOmX0FYQSITHHoU1p6634qmc0NcEFdWWghNh4MCBlnqGDLQ7EAxvxgeibsho+wr1EUtEmLYIQqmjvbeCKoiqrgCCWTc4JtzeA1qQo8J5zyXk0e3BAIph7jTYvBQIwQLDgWdv4Bo5UkTqjMCFfiglzKOJoX4bS4oGyIR9FUIhCdmgMz0EEqw4pApaGgTrA8dUGoxQiZOGoX0Si1CDFYAKCEkkaChKVUeHQNYNGosdXYp1iBI2unIZoocxGFCRHoKU40VT+H2n4QG8oqig3Bj4LwUIXAhW3lhPeshCmFiROucqhlYK6AXA7Zoxg6wTwgv09JHRfAhT9vAz8-9Cz+HXJUxUCIzBIiRJpaCOkRoNlMGCME8EFlzxzrvRe11Vn2RBtI1oDM-pGAsF4GsdZXJEI8n8Go41wjhCAA */
model.createMachine(
{
predictableActionArguments: true,
preserveActionOrder: true,
tsTypes: {} as import('./QrLoginMachine.typegen').Typegen0,
schema: {
context: model.initialContext,
events: {} as EventFrom<typeof model>,
},
id: 'QrLogin',
initial: 'waitingForData',
states: {
waitingForData: {
on: {
GET: {
actions: [
'setScanData',
'resetLinkTransactionId',
'resetSelectedVoluntaryClaims',
],
target: 'linkTransaction',
},
},
},
linkTransaction: {
invoke: {
src: 'linkTransaction',
onDone: [
{
actions: [
'setlinkTransactionResponse',
'expandLinkTransResp',
'setClaims',
],
target: 'showWarning',
},
],
onError: [
{
actions: 'SetErrorMessage',
target: 'ShowError',
},
],
},
},
showWarning: {
on: {
CONFIRM: {
target: 'loadMyVcs',
},
DISMISS: {
actions: 'forwardToParent',
target: 'waitingForData',
},
},
},
ShowError: {
on: {
DISMISS: {
actions: 'forwardToParent',
target: 'waitingForData',
},
},
},
loadMyVcs: {
entry: 'loadMyVcs',
on: {
STORE_RESPONSE: {
actions: 'setMyVcs',
target: 'showvcList',
},
},
},
showvcList: {
on: {
SELECT_VC: {
actions: 'setSelectedVc',
},
VERIFY: {
target: 'faceAuth',
},
DISMISS: {
actions: 'forwardToParent',
target: 'waitingForData',
},
},
},
faceAuth: {
on: {
FACE_VALID: {
target: 'requestConsent',
},
FACE_INVALID: {
target: 'invalidIdentity',
},
CANCEL: {
target: 'showvcList',
},
},
},
invalidIdentity: {
on: {
DISMISS: {
target: 'showvcList',
},
RETRY_VERIFICATION: {
target: 'faceAuth',
},
},
},
requestConsent: {
on: {
CONFIRM: {
target: 'sendingConsent',
},
TOGGLE_CONSENT_CLAIM: {
actions: 'setConsentClaims',
target: 'requestConsent',
},
DISMISS: {
actions: 'forwardToParent',
target: 'waitingForData',
},
},
},
sendingConsent: {
invoke: {
src: 'sendConsent',
onDone: {
target: 'success',
},
onError: [
{
actions: 'SetErrorMessage',
target: 'ShowError',
},
],
},
},
success: {
on: {
CONFIRM: {
target: 'done',
},
},
},
done: {
type: 'final',
},
},
},
{
actions: {
forwardToParent: sendParent('DISMISS'),
setScanData: assign({
linkCode: (context, event) => event.value,
}),
loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), {
to: (context) => context.serviceRefs.store,
}),
setMyVcs: model.assign({
myVcs: (_context, event) => (event.response || []) as string[],
}),
resetLinkTransactionId: model.assign({
linkTransactionId: () => '',
}),
resetSelectedVoluntaryClaims: model.assign({
selectedVoluntaryClaims: () => [],
}),
setSelectedVc: assign({
selectedVc: (context, event) => {
return { ...event.vc };
},
}),
setlinkTransactionResponse: assign({
linkTransactionResponse: (context, event) =>
event.data as linkTransactionResponse,
}),
expandLinkTransResp: assign({
authFactors: (context) => context.linkTransactionResponse.authFactors,
authorizeScopes: (context) =>
context.linkTransactionResponse.authorizeScopes,
clientName: (context) => context.linkTransactionResponse.clientName,
configs: (context) => context.linkTransactionResponse.configs,
essentialClaims: (context) =>
context.linkTransactionResponse.essentialClaims,
linkTransactionId: (context) =>
context.linkTransactionResponse.linkTransactionId,
logoUrl: (context) => context.linkTransactionResponse.logoUrl,
voluntaryClaims: (context) =>
context.linkTransactionResponse.voluntaryClaims,
}),
setClaims: (context) => {
context.voluntaryClaims.map((claim) => {
context.isSharing[claim] = false;
});
},
SetErrorMessage: assign({
errorMessage: (context, event) =>
(event as ErrorPlatformEvent).data.message,
}),
setConsentClaims: assign({
isSharing: (context, event) => {
context.selectedVoluntaryClaims.push(
context.isSharing[event.claim]
);
context.isSharing[event.claim] = !event.enable;
return { ...context.isSharing };
},
}),
},
services: {
linkTransaction: async (context) => {
const response = await request('POST', '/link-transaction', {
linkCode: context.linkCode,
requestTime: String(new Date().toISOString()),
});
return response.response;
},
sendConsent: async (context) => {
var privateKey = await getPrivateKey(
context.selectedVc.walletBindingResponse?.walletBindingId
);
var walletBindingResponse = context.selectedVc.walletBindingResponse;
var jwt = await getJwt(
privateKey,
context.selectedVc.id,
walletBindingResponse?.keyId,
walletBindingResponse?.thumbprint
);
const resp = await request('POST', '/idp-auth-consent', {
requestTime: String(new Date().toISOString()),
request: {
linkedTransactionId: context.linkTransactionId,
individualId: context.selectedVc.id,
acceptedClaims: context.essentialClaims.concat(
context.selectedVoluntaryClaims
),
permittedAuthorizeScopes: context.authorizeScopes,
challengeList: [
{
authFactorType: 'WLA',
challenge: jwt,
format: 'jwt',
},
],
},
});
console.log(resp.response.linkedTransactionId);
},
},
}
);
export function createQrLoginMachine(serviceRefs: AppServices) {
return qrLoginMachine.withContext({
...qrLoginMachine.context,
serviceRefs,
});
}
type State = StateFrom<typeof qrLoginMachine>;
export function selectMyVcs(state: State) {
return state.context.myVcs;
}
export function selectIsWaitingForData(state: State) {
return state.matches('waitingForData');
}
export function selectIsShowWarning(state: State) {
return state.matches('showWarning');
}
export function selectIsLinkTransaction(state: State) {
return state.matches('linkTransaction');
}
export function selectIsloadMyVcs(state: State) {
return state.matches('loadMyVcs');
}
export function selectIsShowingVcList(state: State) {
return state.matches('showvcList');
}
export function selectIsisVerifyingIdentity(state: State) {
return state.matches('faceAuth');
}
export function selectIsInvalidIdentity(state: State) {
return state.matches('invalidIdentity');
}
export function selectIsShowError(state: State) {
return state.matches('ShowError');
}
export function selectIsRequestConsent(state: State) {
return state.matches('requestConsent');
}
export function selectIsSendingConsent(state: State) {
return state.matches('sendingConsent');
}
export function selectIsVerifyingSuccesful(state: State) {
return state.matches('success');
}
export function selectSelectedVc(state: State) {
return state.context.selectedVc;
}
export function selectLinkTransactionResponse(state: State) {
return state.context.linkTransactionResponse;
}
export function selectVoluntaryClaims(state: State) {
return state.context.voluntaryClaims;
}
export function selectLogoUrl(state: State) {
return state.context.logoUrl;
}
export function selectClientName(state: State) {
return state.context.clientName;
}
export function selectErrorMessage(state: State) {
return state.context.errorMessage;
}
export function selectIsSharing(state: State) {
return state.context.isSharing;
}

View File

@@ -0,0 +1,65 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
'done.invoke.QrLogin.linkTransaction:invocation[0]': {
type: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform.QrLogin.linkTransaction:invocation[0]': {
type: 'error.platform.QrLogin.linkTransaction:invocation[0]';
data: unknown;
};
'error.platform.QrLogin.sendingConsent:invocation[0]': {
type: 'error.platform.QrLogin.sendingConsent:invocation[0]';
data: unknown;
};
'xstate.init': { type: 'xstate.init' };
};
'invokeSrcNameMap': {
linkTransaction: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
sendConsent: 'done.invoke.QrLogin.sendingConsent:invocation[0]';
};
'missingImplementations': {
actions: never;
delays: never;
guards: never;
services: never;
};
'eventsCausingActions': {
SetErrorMessage:
| 'error.platform.QrLogin.linkTransaction:invocation[0]'
| 'error.platform.QrLogin.sendingConsent:invocation[0]';
expandLinkTransResp: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
forwardToParent: 'DISMISS';
loadMyVcs: 'CONFIRM';
setClaims: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
setConsentClaims: 'TOGGLE_CONSENT_CLAIM';
setMyVcs: 'STORE_RESPONSE';
setScanData: 'GET';
setSelectedVc: 'SELECT_VC';
setlinkTransactionResponse: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
};
'eventsCausingDelays': {};
'eventsCausingGuards': {};
'eventsCausingServices': {
linkTransaction: 'GET';
sendConsent: 'CONFIRM';
};
'matchesStates':
| 'ShowError'
| 'done'
| 'faceAuth'
| 'invalidIdentity'
| 'linkTransaction'
| 'loadMyVcs'
| 'requestConsent'
| 'sendingConsent'
| 'showWarning'
| 'showvcList'
| 'success'
| 'waitingForData';
'tags': never;
}

View File

@@ -121,6 +121,7 @@ export interface ActivityLog {
export type ActivityLogType =
| 'VC_SHARED'
| 'VC_RECEIVED'
| 'VC_RECEIVED_NOT_SAVED'
| 'VC_DELETED'
| 'VC_DOWNLOADED'
| 'VC_REVOKED'
@@ -128,7 +129,8 @@ export type ActivityLogType =
| 'VC_RECEIVED_WITH_PRESENCE_VERIFIED'
| 'VC_RECEIVED_BUT_PRESENCE_VERIFICATION_FAILED'
| 'PRESENCE_VERIFIED_AND_VC_SHARED'
| 'PRESENCE_VERIFICATION_FAILED';
| 'PRESENCE_VERIFICATION_FAILED'
| 'QRLOGIN_SUCCESFULL';
type State = StateFrom<typeof activityLogMachine>;

View File

@@ -1,7 +1,7 @@
import { init } from 'mosip-inji-face-sdk';
import { ContextFrom, EventFrom, send, StateFrom } from 'xstate';
import { createModel } from 'xstate/lib/model';
import getAllProperties from '../shared/commonprops/commonProps';
import getAllConfigurations from '../shared/commonprops/commonProps';
import { AppServices } from '../shared/GlobalContext';
import { StoreEvents, StoreResponseEvent } from './store';
@@ -11,7 +11,6 @@ const model = createModel(
passcode: '',
biometrics: '',
canUseBiometrics: false,
injiAppProperties: null,
},
{
events: {
@@ -88,7 +87,7 @@ export const authMachine = model.createMachine(
invoke: {
src: 'downloadFaceSdkModel',
onDone: {
actions: ['setInjiAppProperties', 'storeContext'],
actions: ['storeContext'],
},
},
on: {
@@ -128,21 +127,13 @@ export const authMachine = model.createMachine(
setBiometrics: model.assign({
biometrics: (_, event: SetupBiometricsEvent) => event.biometrics,
}),
setInjiAppProperties: model.assign({
injiAppProperties: (_, event) => event.data as object,
}),
},
services: {
downloadFaceSdkModel: (context) => async () => {
downloadFaceSdkModel: () => async () => {
var injiProp = null;
try {
if (context.injiAppProperties == null) {
injiProp = await getAllProperties();
} else {
injiProp = context.injiAppProperties;
}
var injiProp = await getAllConfigurations();
const resp: string =
injiProp != null ? injiProp.faceSdkModelUrl : null;
if (resp != null) {
@@ -151,7 +142,6 @@ export const authMachine = model.createMachine(
} catch (error) {
console.log(error);
}
return injiProp;
},
},
@@ -162,7 +152,7 @@ export const authMachine = model.createMachine(
return context.passcode !== '';
},
hasBiometricSet: (context) => {
return context.biometrics !== '';
return context.biometrics !== '' && context.passcode !== '';
},
},
}
@@ -200,7 +190,3 @@ export function selectUnauthorized(state: State) {
export function selectSettingUp(state: State) {
return state.matches('settingUp');
}
export function selectInjiAppProps(state: State) {
return state.context.injiAppProperties;
}

View File

@@ -24,7 +24,6 @@ export interface Typegen0 {
requestStoredContext: 'xstate.init';
setBiometrics: 'SETUP_BIOMETRICS';
setContext: 'STORE_RESPONSE';
setInjiAppProperties: 'done.invoke.auth.authorized:invocation[0]';
setPasscode: 'SETUP_PASSCODE';
storeContext:
| 'SETUP_BIOMETRICS'

View File

@@ -1,6 +1,7 @@
import { createModel } from 'xstate/lib/model';
import * as LocalAuthentication from 'expo-local-authentication';
import { EventFrom, MetaObject, StateFrom } from 'xstate';
import { Platform } from 'react-native';
// ----- CREATE MODEL ---------------------------------------------------------
const model = createModel(
@@ -97,6 +98,9 @@ export const biometricsMachine = model.createMachine(
authenticating: {
invoke: {
src: () => async () => {
if (Platform.OS === 'android') {
await LocalAuthentication.cancelAuthenticate();
}
const res = await LocalAuthentication.authenticateAsync({
promptMessage: 'Biometric Authentication',
@@ -112,6 +116,9 @@ export const biometricsMachine = model.createMachine(
actions: ['setStatus'],
},
},
on: {
AUTHENTICATE: 'authenticating',
},
},
reauthenticating: {
@@ -145,6 +152,7 @@ export const biometricsMachine = model.createMachine(
SET_IS_AVAILABLE: {
target: '#biometrics.available',
},
AUTHENTICATE: 'authenticating',
},
},
@@ -186,6 +194,9 @@ export const biometricsMachine = model.createMachine(
},
},
},
on: {
AUTHENTICATE: 'authenticating',
},
},
},
},

File diff suppressed because one or more lines are too long

View File

@@ -27,6 +27,7 @@ export interface Typegen0 {
'invokeSrcNameMap': {
advertiseDevice: 'done.invoke.request.waitingForConnection:invocation[0]';
checkBluetoothService: 'done.invoke.request.checkingBluetoothService.checking:invocation[0]';
checkNetwork: 'done.invoke.request.checkingNetwork:invocation[0]';
exchangeDeviceInfo: 'done.invoke.request.exchangingDeviceInfo:invocation[0]';
monitorConnection: 'done.invoke.request:invocation[0]';
receiveVc: 'done.invoke.request.waitingForVc:invocation[0]';
@@ -34,7 +35,8 @@ export interface Typegen0 {
sendDisconnect: 'done.invoke.request.cancelling:invocation[0]';
sendVcResponse:
| 'done.invoke.request.reviewing.accepted:invocation[0]'
| 'done.invoke.request.reviewing.rejected:invocation[0]';
| 'done.invoke.request.reviewing.rejected:invocation[0]'
| 'done.invoke.request.reviewing:invocation[0]';
verifyVp: 'done.invoke.request.reviewing.verifyingVp:invocation[0]';
};
'missingImplementations': {
@@ -66,7 +68,7 @@ export interface Typegen0 {
generateConnectionParams:
| 'DISMISS'
| 'xstate.after(CLEAR_DELAY)#request.clearingConnection';
logReceived: 'STORE_RESPONSE';
logReceived: 'CANCEL' | 'REJECT' | 'STORE_RESPONSE';
mergeIncomingVc: 'STORE_RESPONSE';
openSettings: 'GOTO_SETTINGS';
prependReceivedVc: 'VC_RESPONSE';
@@ -85,8 +87,8 @@ export interface Typegen0 {
| 'FACE_VALID'
| 'done.invoke.request.reviewing.verifyingVp:invocation[0]';
requestReceiverInfo: 'CONNECTED';
sendVcReceived: 'STORE_RESPONSE';
setIncomingVc: 'VC_RECEIVED';
setReceiveLogTypeDiscarded: 'CANCEL' | 'REJECT';
setReceiveLogTypeRegular: 'ACCEPT';
setReceiveLogTypeUnverified: 'FACE_INVALID';
setReceiveLogTypeVerified: 'FACE_VALID';
@@ -94,6 +96,7 @@ export interface Typegen0 {
setSenderInfo: 'EXCHANGE_DONE';
storeVc: 'STORE_RESPONSE';
switchProtocol: 'SWITCH_PROTOCOL';
updateReceivedVcs: 'STORE_RESPONSE';
};
'eventsCausingDelays': {
CANCEL_TIMEOUT: 'CANCEL';
@@ -103,21 +106,24 @@ export interface Typegen0 {
};
'eventsCausingGuards': {
hasExistingVc: 'VC_RESPONSE';
isModeOnline: 'SCREEN_FOCUS' | 'SWITCH_PROTOCOL';
};
'eventsCausingServices': {
advertiseDevice:
| 'DISMISS'
| 'xstate.after(CLEAR_DELAY)#request.clearingConnection';
checkBluetoothService:
| 'ONLINE'
| 'SCREEN_FOCUS'
| 'SWITCH_PROTOCOL'
| 'xstate.after(CANCEL_TIMEOUT)#request.cancelling';
checkNetwork: 'APP_ACTIVE' | 'SCREEN_FOCUS' | 'SWITCH_PROTOCOL';
exchangeDeviceInfo: 'RECEIVE_DEVICE_INFO';
monitorConnection: 'xstate.init';
receiveVc: 'EXCHANGE_DONE';
requestBluetooth: 'BLUETOOTH_DISABLED';
sendDisconnect: 'CANCEL';
sendVcResponse: 'CANCEL' | 'REJECT' | 'STORE_RESPONSE';
sendVcResponse: 'CANCEL' | 'REJECT' | 'STORE_RESPONSE' | 'VC_RECEIVED';
verifyVp: never;
};
'matchesStates':
@@ -127,12 +133,14 @@ export interface Typegen0 {
| 'checkingBluetoothService.checking'
| 'checkingBluetoothService.enabled'
| 'checkingBluetoothService.requesting'
| 'checkingNetwork'
| 'clearingConnection'
| 'disconnected'
| 'exchangingDeviceInfo'
| 'exchangingDeviceInfo.inProgress'
| 'exchangingDeviceInfo.timeout'
| 'inactive'
| 'offline'
| 'preparingToExchangeInfo'
| 'reviewing'
| 'reviewing.accepted'

File diff suppressed because one or more lines are too long

View File

@@ -3,12 +3,20 @@
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
'': { type: '' };
'done.invoke.QrLogin': {
type: 'done.invoke.QrLogin';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.scan.reviewing.creatingVp:invocation[0]': {
type: 'done.invoke.scan.reviewing.creatingVp:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform.scan.reviewing.creatingVp:invocation[0]': {
type: 'error.platform.scan.reviewing.creatingVp:invocation[0]';
data: unknown;
};
'xstate.after(CANCEL_TIMEOUT)#scan.reviewing.cancelling': {
type: 'xstate.after(CANCEL_TIMEOUT)#scan.reviewing.cancelling';
};
@@ -28,14 +36,14 @@ export interface Typegen0 {
'xstate.stop': { type: 'xstate.stop' };
};
'invokeSrcNameMap': {
checkBluetoothService: 'done.invoke.scan.checkingBluetoothService.checking:invocation[0]';
checkLocationPermission: 'done.invoke.scan.checkingLocationService.checkingPermission:invocation[0]';
checkLocationStatus: 'done.invoke.scan.checkingLocationService.checkingStatus:invocation[0]';
checkNetwork: 'done.invoke.scan.checkingNetwork:invocation[0]';
createVp: 'done.invoke.scan.reviewing.creatingVp:invocation[0]';
discoverDevice: 'done.invoke.scan.connecting:invocation[0]';
exchangeDeviceInfo: 'done.invoke.scan.exchangingDeviceInfo:invocation[0]';
monitorCancellation: 'done.invoke.scan.reviewing.selectingVc:invocation[0]';
monitorConnection: 'done.invoke.scan:invocation[0]';
requestBluetooth: 'done.invoke.scan.checkingBluetoothService.requesting:invocation[0]';
sendDisconnect: 'done.invoke.scan.reviewing.cancelling:invocation[0]';
sendVc: 'done.invoke.scan.reviewing.sendingVc:invocation[0]';
};
@@ -43,7 +51,7 @@ export interface Typegen0 {
actions: never;
delays: never;
guards: never;
services: never;
services: 'QrLogin';
};
'eventsCausingActions': {
clearCreatedVp:
@@ -75,7 +83,14 @@ export interface Typegen0 {
| 'xstate.stop';
logFailedVerification: 'FACE_INVALID';
logShared: 'VC_ACCEPTED';
openBluetoothSettings: 'GOTO_SETTINGS';
onlineUnsubscribe:
| 'ACCEPT_REQUEST'
| 'CANCEL'
| 'DISCONNECT'
| 'SCREEN_BLUR'
| 'SCREEN_FOCUS'
| 'VERIFY_AND_ACCEPT_REQUEST'
| 'xstate.stop';
openSettings: 'LOCATION_REQUEST';
registerLoggers:
| 'DISCONNECT'
@@ -89,10 +104,18 @@ export interface Typegen0 {
| 'xstate.after(CANCEL_TIMEOUT)#scan.reviewing.cancelling'
| 'xstate.after(CLEAR_DELAY)#scan.clearingConnection'
| 'xstate.init';
requestSenderInfo: 'SCAN';
requestSenderInfo: 'ONLINE' | 'SCAN';
requestToEnableLocation: 'LOCATION_DISABLED' | 'LOCATION_REQUEST';
resetShouldVerifyPresence: 'CANCEL' | 'EXCHANGE_DONE';
sendScanData: 'SCAN';
setChildRef:
| 'DISCONNECT'
| 'DISMISS'
| 'xstate.after(CANCEL_TIMEOUT)#scan.reviewing.cancelling'
| 'xstate.after(CLEAR_DELAY)#scan.clearingConnection';
setConnectionParams: 'SCAN';
setCreatedVp: 'done.invoke.scan.reviewing.creatingVp:invocation[0]';
setLinkCode: 'SCAN';
setReason: 'UPDATE_REASON';
setReceiverInfo: 'EXCHANGE_DONE';
setScannedQrParams: 'SCAN';
@@ -100,6 +123,8 @@ export interface Typegen0 {
setSenderInfo: 'RECEIVE_DEVICE_INFO';
setShareLogTypeUnverified: 'ACCEPT_REQUEST';
setShareLogTypeVerified: 'FACE_VALID';
storeLoginItem: 'done.invoke.QrLogin';
storingActivityLog: 'STORE_RESPONSE';
toggleShouldVerifyPresence: 'TOGGLE_USER_CONSENT';
};
'eventsCausingDelays': {
@@ -112,41 +137,45 @@ export interface Typegen0 {
SHARING_TIMEOUT:
| 'ACCEPT_REQUEST'
| 'FACE_VALID'
| 'VC_SENT'
| 'done.invoke.scan.reviewing.creatingVp:invocation[0]';
};
'eventsCausingGuards': {
isQrLogin: 'SCAN';
isQrOffline: 'SCAN';
isQrOnline: 'SCAN';
};
'eventsCausingServices': {
checkBluetoothService: 'SCREEN_FOCUS';
QrLogin: 'SCAN';
checkLocationPermission: 'APP_ACTIVE' | 'LOCATION_ENABLED';
checkLocationStatus: '';
checkLocationStatus: 'SCREEN_FOCUS';
checkNetwork: 'SCAN';
createVp: never;
discoverDevice: 'RECEIVE_DEVICE_INFO';
exchangeDeviceInfo:
| 'CONNECTED'
| 'xstate.after(CONNECTION_TIMEOUT)#scan.exchangingDeviceInfo';
monitorCancellation:
| 'CANCEL'
| 'DISMISS'
| 'EXCHANGE_DONE'
| 'error.platform.scan.reviewing.creatingVp:invocation[0]';
monitorConnection: 'xstate.init';
requestBluetooth: 'BLUETOOTH_DISABLED';
sendDisconnect: 'CANCEL';
sendVc:
| 'ACCEPT_REQUEST'
| 'FACE_VALID'
| 'VC_SENT'
| 'done.invoke.scan.reviewing.creatingVp:invocation[0]';
};
'matchesStates':
| 'bluetoothDenied'
| 'checkingBluetoothService'
| 'checkingBluetoothService.checking'
| 'checkingBluetoothService.enabled'
| 'checkingBluetoothService.requesting'
| 'checkingLocationService'
| 'checkingLocationService.checkingPermission'
| 'checkingLocationService.checkingStatus'
| 'checkingLocationService.denied'
| 'checkingLocationService.disabled'
| 'checkingLocationService.requestingToEnable'
| 'checkingNetwork'
| 'clearingConnection'
| 'connecting'
| 'connecting.inProgress'
@@ -158,6 +187,7 @@ export interface Typegen0 {
| 'findingConnection'
| 'inactive'
| 'invalid'
| 'offline'
| 'preparingToConnect'
| 'reviewing'
| 'reviewing.accepted'
@@ -169,10 +199,14 @@ export interface Typegen0 {
| 'reviewing.selectingVc'
| 'reviewing.sendingVc'
| 'reviewing.sendingVc.inProgress'
| 'reviewing.sendingVc.sent'
| 'reviewing.sendingVc.timeout'
| 'reviewing.verifyingIdentity'
| 'showQrLogin'
| 'showQrLogin.idle'
| 'showQrLogin.navigatingToHome'
| 'showQrLogin.storing'
| {
checkingBluetoothService?: 'checking' | 'enabled' | 'requesting';
checkingLocationService?:
| 'checkingPermission'
| 'checkingStatus'
@@ -191,7 +225,8 @@ export interface Typegen0 {
| 'selectingVc'
| 'sendingVc'
| 'verifyingIdentity'
| { sendingVc?: 'inProgress' | 'timeout' };
| { sendingVc?: 'inProgress' | 'sent' | 'timeout' };
showQrLogin?: 'idle' | 'navigatingToHome' | 'storing';
};
'tags': never;
}

View File

@@ -8,9 +8,9 @@ export interface Typegen0 {
'invokeSrcNameMap': {};
'missingImplementations': {
actions: never;
services: never;
guards: never;
delays: never;
guards: never;
services: never;
};
'eventsCausingActions': {
requestStoredContext: 'xstate.init';
@@ -24,11 +24,11 @@ export interface Typegen0 {
updateName: 'UPDATE_NAME';
updateVcLabel: 'UPDATE_VC_LABEL';
};
'eventsCausingServices': {};
'eventsCausingDelays': {};
'eventsCausingGuards': {
hasData: 'STORE_RESPONSE';
};
'eventsCausingDelays': {};
'eventsCausingServices': {};
'matchesStates': 'idle' | 'init' | 'storingDefaults';
'tags': never;
}

View File

@@ -239,3 +239,14 @@ export function selectIsRefreshingMyVcs(state: State) {
export function selectIsRefreshingReceivedVcs(state: State) {
return state.matches('ready.receivedVcs.refreshing');
}
export function selectBindedVcs(state: State) {
return (Object.keys(state.context.vcs) as Array<string>).filter((key) => {
var walletBindingResponse = state.context.vcs[key].walletBindingResponse;
return (
walletBindingResponse !== null &&
walletBindingResponse.walletBindingId !== null &&
walletBindingResponse.walletBindingId !== ''
);
});
}

View File

@@ -1,54 +0,0 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
'xstate.init': { type: 'xstate.init' };
};
'invokeSrcNameMap': {};
'missingImplementations': {
actions: never;
services: never;
guards: never;
delays: never;
};
'eventsCausingActions': {
getReceivedVcsResponse: 'GET_RECEIVED_VCS';
getVcItemResponse: 'GET_VC_ITEM';
loadMyVcs: 'REFRESH_MY_VCS' | 'xstate.init';
loadReceivedVcs: 'REFRESH_RECEIVED_VCS' | 'STORE_RESPONSE';
moveExistingVcToTop: 'VC_RECEIVED';
prependToMyVcs: 'VC_ADDED';
prependToReceivedVcs: 'VC_RECEIVED';
setDownloadedVc: 'VC_DOWNLOADED';
setMyVcs: 'STORE_RESPONSE';
setReceivedVcs: 'STORE_RESPONSE';
};
'eventsCausingServices': {};
'eventsCausingGuards': {
hasExistingReceivedVc: 'VC_RECEIVED';
};
'eventsCausingDelays': {};
'matchesStates':
| 'init'
| 'init.myVcs'
| 'init.receivedVcs'
| 'ready'
| 'ready.myVcs'
| 'ready.myVcs.idle'
| 'ready.myVcs.refreshing'
| 'ready.receivedVcs'
| 'ready.receivedVcs.idle'
| 'ready.receivedVcs.refreshing'
| {
init?: 'myVcs' | 'receivedVcs';
ready?:
| 'myVcs'
| 'receivedVcs'
| {
myVcs?: 'idle' | 'refreshing';
receivedVcs?: 'idle' | 'refreshing';
};
};
'tags': never;
}

View File

@@ -13,6 +13,18 @@ import { StoreEvents } from './store';
import { ActivityLogEvents } from './activityLog';
import { verifyCredential } from '../shared/vcjs/verifyCredential';
import { log } from 'xstate/lib/actions';
import {
generateKeys,
getJwt,
WalletBindingResponse,
} from '../shared/cryptoutil/cryptoUtil';
import { KeyPair } from 'react-native-rsa-native';
import {
getBindingCertificateConstant,
getPrivateKey,
savePrivateKey,
} from '../shared/keystore/SecureKeystore';
import getAllConfigurations from '../shared/commonprops/commonProps';
const model = createModel(
{
@@ -31,11 +43,19 @@ const model = createModel(
otpError: '',
idError: '',
transactionId: '',
bindingTransactionId: '',
revoked: false,
downloadCounter: 0,
maxDownloadCount: 10,
walletBindingResponse: null as WalletBindingResponse,
walletBindingError: '',
publicKey: '',
privateKey: '',
},
{
events: {
KEY_RECEIVED: (key: string) => ({ key }),
KEY_ERROR: (error: Error) => ({ error }),
EDIT_TAG: () => ({}),
SAVE_TAG: (tag: string) => ({ tag }),
STORE_READY: () => ({}),
@@ -50,6 +70,10 @@ const model = createModel(
INPUT_OTP: (otp: string) => ({ otp }),
REFRESH: () => ({}),
REVOKE_VC: () => ({}),
ADD_WALLET_BINDING_ID: () => ({}),
BINDING_DONE: () => ({}),
CANCEL: () => ({}),
CONFIRM: () => ({}),
},
}
);
@@ -57,7 +81,7 @@ const model = createModel(
export const VcItemEvents = model.events;
export const vcItemMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QDcDGBaAlgFzAWwDpUALMVAa0wDsoA1VAYgHEBRAFQH1aBhDgJRYBlAAoB5AHKCWiUAAcA9rByZ5VGSAAeiAIwAWAAwFtANgAcAdl0BmAJwAmOxd0BWADQgAnoivHDpx6aBzubGwc6mAL4R7mhYuIQkZJQ0gtjyAE5gDIJsogL8QmKS0kggCkrYKmqlWgh6hiYWDnba4aFW7l4I5qbOBPohvdoW2jbDUTEYOPhEpBTUUIJg6cjLACIAhtgbs0kLqVsArrAMYgAyZ+rlyqrqtdrm2gRWds521vqmusZ2Nr2dOlGugIAXMb1+dn0NnCExAsWmCTmyUWy1W6U22128xS22wxwYa1EAHVxGdRABBNYFSkATSuihu1VAtQMfSsPlCumhVlGjwBdSsumBdh5Vmc1jGznszlh8PiWORSxW6y2Owg8gA7lQADbyDYQBbcTIQMBUSobbWnUQXekVKp3RA-YWtcz6YzQ4y6OzGfnaIEgwJghz2KEw6JwqbyxLYlHK9GqgjqrW6-WG42m82W7gCNYscRsACS5LOHEJJLJlJYa1tjIdCF0pmMz3Mrucgu+7R9nkQjb6Fh87Z5LcCssjM0wEG1WSrBc4bHJTBrlVuNR0bqeNk9A2Mrv0+mc4X5f3MBF0PRFPP8-kso7i48nWVoLD4BYAYnTStdl0zND3nA0rEbUx930OwQlCflTEAggbHMDkwKdfRtFvBECAnKcGDJbgAGkuG4Jd7VXOo2z6T5fDMHwbCQnpIOg2CfCgkJBjFFD5XQrIAFVSVEHC8IIldmR0axTCMYxRhsXRhmsd47H5QZ+mGHdQNGYxKNYmZIGUGg2A2KACQLQQAFkDMEfif3uYYT09XxGx6dk91k7sEGPU9z1CJD-HZKx1MITTKm03TsnJJ8OHnRdPwZb86z9VpTyosZ3TMAxHK6P1HiML1Xi9YwxIGSJwzlGZYDSdIFh0vScjyFgChECQpDM6LXjsAhTAk-RALguCrHMX1tBFAhLGGXoWj+drdB8gg0UwAAzDw00gDNMAtBh1SoMA0KoZB5HIdbCsIKbZvmk0zSW7UEGoLbUC2KoAG19AAXQaojtH0AwCClcxXihb0TAPfkXEMJDhl5d0eiFcaCrHfblhmuaaCNBaTuW5Z0gyAhZG1LZpoyfaocmmHDvh9MkbOi75Cu787seiK7QE39nNeFrnE9GxAJcSFvX5H5mvsb4+u0WxgNeibo2RWgCcwCmqgOPETiewS6hep4rDdaFglAz6eqc14myvQJ+YGfd2Qmi6LQnBgC3EYQOM4UQ2GEeX6chT1m0eA9wZVqDev-GDKJ3ST3SFfLJjvQhTe1c21gM4zBFMmna2el4bHe0xWm+F5PUA-k2yeAw7Mk8UWnMGwJsyABHQ44H8qBRGwWQVtUdayZ2ya8fLyvioWWvZHOzbyeu1Qqcd+5U6sfoHBykwQjeLWukSmCzxFODHHeCxS7ACuq67uuGBRtGMaxnHW9Dgh263mhu97y6B6oIf46i57U+BN5AlU75nF+3Rs-sAgxLBFWJKWFgnYCaGxUCoDALIau3cCxUFkIcbAFsrY2w4HbB299CIKweFyd6YEP6OHBO1NwTlYLJ3sIKewrR-zeUhifMBECoHb1kLA+BiCo5GRMsPHQ-5gQ-DFLYewcF7CmCPC0OKvwfBSihE4dem9O40DOOTcgDc1obS2i3Pap8N4d2rooigV9+6UwelwuowFk6NmsnuaEF4RFOT6rBd61gXo9FbIKWROiFh6OUXvdI6NMbYGxukXGJ8z7yKgF4gxUtB7GIwXTEeVEBrkNZAhbqX8SHFxgiKQuUppQPAmrqGM9Bsi5HyAIWqxQTGtG6kYcIfUWyhEcNCXqm4akSR+i2cUxtYRUHkCaeApRNGiwWPQExZhf6pIkl6GwYwvQdDsS2Iwe4hQ9GmQYawEMQ6oSGTiDIYATHQhBE6KigQeSTLmXPAY-RTk2WkcBYwIskT7FRCqTE2zFi4mOPsp4jwvjehcMzVSwxfQ8maiKAW0zegDA-sHCMJ83lKjRBiNUmodR6gNETRGmYTHhBEueAWrpPrBDSalFWhgwUclCEAv4Dy9gpGefGbYoyTw-Oyv8nKl5fRujHqCCiIQXAbNhahdilSHgbgPBRdODhLByUCDBOCOV9xfAlDCzRfkyq6UqVCYEm4pSOE+i0P0nLZWeRaDuRwKtzATWKhkdVUBKmQj6KpMwPQYoCy7KlX4hg2xeihMXWCXwZS0NQgdOGUAEbHSxbE8yiB6m-yJb0A8eq2z-Ryu9P0UF8VQh8CAoNUZHk0HFqVaaksb4y0+VGusHxnh6Fqf4RwZriWOndBlFsfUuR-H8CXXN45NpmwgAQeQdcTHOyeE-HoqlFWCmzn1X+fxWh7lTmJIUJte0R37QAIzATtKgEBh1G3epuV6rNMrEK6G0IwDwSLemAi9Ltmy2KronHu34B7fBckFA4U9iAJSztToEKC-DPjuPPjXIdFbnrfDIX8cUDZ-xukbN-ZqXpvgB03MMFooDwGQOgXXFhCDKnFyeJ6cCjw-SvTdHJaZ704NUR+DuJ1wGwleIIwk34TExRu0sF+xW0FrCg1ePwrJ+SlHDNQJUmeLVbCAU+LYAwoxfQEP6E4z4WVPpfB8iY6wA0pJ-PFYC2xc8TwvVen89c3wwRRCiEAA */
/** @xstate-layout N4IgpgJg5mDOIC5QDcDGBaAlgFzAWwGIAlAUQDFSBlACQG0AGAXUVAAcB7WHTdgOxZAAPRABYATABoQAT0QBGABwBWAHT0FAZjEB2bSKUBOA9rliAvmalosuPCtQALMKgDWmXlABqqAgHESACoA+p4AwkFUAAoA8gBylCQMzEggHFzYPPwpwggKBiIqcvqaAGwGSvT0SnJSsgjicio69Np5GvTFSiIWVhg4+PZOru5ePv7BYREklDHxiXLJbJzcfAI5eQVFSqXlldW1iO2qmlpKhtpnGkbdliDW-XaOzm4elNjsAE5gBJQB0aRTGZxBJJARpFZZUA5OSmA4IMRyDQFDRKDQmMRiDRyAwKG69GwDJ7DV7vL4-P4AqLA+aLVLLDKrbLyJRiBRNAxybT0MRnQwKBSSGTyXFNERGPIwhFo-k9O59WyDZ4jShgD7IVUAEQAhtgtYriVA3jqAK6wAgxAAyFtBKXBDMhQkQPO0TTORQMJRZ2hKyjhihEbLkHRaXVRcmqCll9wVRJehtV6o+2t1+rjRuwpoIGuiAHVYhbogBBDVTYsATRtS3SmTWokMhWD9HyJRKRUxfuMYjUJQRBnaii6VSj8sJQzTCc1Or1EHYAHdeAAbdhaiAjUJfCBgXgZLUL83RK2VunVxlQxAKIMqETiCVo6pnBR+hQlDSFBTaMQlEQdL8dPFyglHjHZUJyTKcVBneclxXNcNy3Hc91CUgNRIWIAgASULC0gmzPMC2LEgNSPO0ayZBAg3yQoURMGjLg9DsxAKD88gxERsQDVph0AlRMAgBdvkI9DggCQtfGI+lSLPeERBdNiShMIMtDFb8lDhT9ORUNERDKZ9rw-JQuIeHi+O+TwSCIdCyArJgwQk09HXhT8ShUd8ujyFkMUMEo1LEFoVBZbl6BhBQQzRQyFV4-iCALUIAGkQlCcSTwdHIdA0V8kW01k5BKegexaNTvUad9r206of3MW5owGSLvlITxolikgEqSiFa1yQU6lZEKXL0JRPS5AMXwMqqRzsWqCGLEscywi1AiCAAhdDYg1ZbfCCdCiJs207JS+ROv28KBkgbgPACLUoCzdDKAAWWuyhWvtdrMVknKFPoJT8iqNT0voQp9By79TB7AwjrsE6MjOi6fkLMyghEsTtqrNqyMY175JhD7GK+1ShWk6pChbREBQMDpPzBlRYFJEZzsu35-maqk5keySHP645yhbDolMUbQ1IBwnWw0EmyZKCnE0wAAzaRYMgeDMF3AgZ14MAeN4ZB2BcVXqrsCXpdlzdtwVhcEHcDXUB1TIkhZ+zUqc3q3PKDEeXovHfNbFQvwqSoUSbF9xdVKWZY8dc5aNxXVQ+T4VFYBcdUlz5dbGlQ9eDqBQ8NhDTfV9gLfta2kePFGpM85zXIDJ3PNdupsR9T3n3KFFlDYnKKdjEZPEDyXMDzzJ00zG29vI5QDBc5QuSMfqPUUJ8KjfMogp5apurb4CPE7j4pZ7y2+H7s1aAWWzkvagdR-5JQJ856fHzdxRR69gMZP5FptAps3d14ghlsiABVYJogCJEQe7UtAujyjyHQRR5JiD7HCEQVxVBP2MHlPq-tRrcXfguT+q1br3WAWRUB3YIEmG0h+WBeNhZflFMYVsYoSGVXxEZL4ABHY0cBIZQGiNgVgSs+CqzNprbWycWFsKpiMLhrBs7mx3rwAutISK23kDCV8MDfLpX6pUD6cCuTOT7NoShbEL75ApiI9h4juEEEjtHWO8dE4p2EWAVhZiPASKkbnGRcij7FwcjCRE7I1Eolyj7OBQsVDGFJp6fQMCigUy1KgVAYBWAcIkehXgrBjTYC-rEX+-9AH4KkmUVQ4C3S4lKTiOBzQVDMQFEDC+SJYnxMSck7hqT0mZJwXdSgD1C4KKHqYJs9cQrtHKHoRicINAY38iTTQ1waIaAaQkpJIwiBgA1lrVpGSsk5KCAAoBPTdonzeoUIov1tBfRgbjGuiJjjpS0O0TQLIRqMIVHExZHCVlrLABs9p11OndPkQcsiRhGimH0GKAUXIsZwjIVebYPIfSIkfq-dBTDHGiI4RaXOLheEqzVp8+x3FTFiI8Ji1wbje58E8TtY+ZEfRFJ7CUx+eQb51CuM+KpXJzj3MUGLFFCoiUYqxZYj4UcPgxzjtgBOHwk6ErRc4qApKXDko8UwfJDlClEMZWUllog2L3w+n2KUDyhx8oGEuA03hyQM0BLMEE+yaVSV8SogwASNHBLxt+PyRgPTvhyuoF1Ji5XEqgB8wROL+E5y1gS1FTjg2hq1sq-Oqr7XeNSqTJo6g0TekxBeE54yfaC17PJF86hA2xveassNVixU2MlXYnWKgBXLMrQmgRFLZHJoBQ6hyhDimQNITAjQcCYSqACmKPKTt0qBrWR3Hw9NKTTFtYkFNT1aVnJcuUAUFQrifj0PmtEntGKtmbPJb8vLnlmvYFAKAzbPlXVwV0tV0IX5Xh9N6xEaJWQHQQO0A9PZtLsRbHoPKZb0UjAWu4VcLiLHKwjfihtTaPAQd4FBzh3DE1W07V41dUlf1hMgQ8oMRjv3A1UEGM9T8ijiCeQBGNYGkOQfMTw6t4rbHSujfyoNHDkOodcW2lVjAn2HA+vhkwhGqhnO-RURogVi1C36vAimsAHBzhICKz4BBQiFliKEEg1oV2sxyJ6ZypgKjPm9HkPKOqEAeWod6eBjFFJyAWU08DjHoM8O-n-HZeSDOKIQBcWSrQZk5UoeGP0S8qnRPDF+i4wsXNLIYyhpj96-lCYCxfK8wWrihefOFvGF8ux6G9byZ0sSIAQFimAaQkQtSYA+OGvFgiOMDBXJV6rtX6sYcpVh6lqalHyWOfA9oZzvwXIiyKLkJM2ItBkvo8r7Wat1Yayx2tUqZVGTa1VpbXX+NJsE35vpRyYTDZaOc8ofoya9TFIDdK4TkUXrsG1kYOZdz8WwDxkY6EICNYEVGhtz2PCvYXO9z7HhvvdY7Qdrt-Wf0aT7SQ6B5DWWFc0noeS6V9H9Xqaap7FWXtvbAB99zUBvvCtFaxut7GAf46B4T4nyXwcQEh1S5GOGe1+QR1AshQ6KFYk2IxVEAomwohow240rAICWw8JETeyAdRgG279yNQjuIS6lxw2XmB5e4G2yz3rbPDOIFaGRlETKZKYhfOMupmlyiDkChcEDuOVDq+l1ALXOvFfVfJ9YiV62Wt2Fd5ruXCu9d7cw9D7DRuAvKCom5JFlveesv0CZj6ehwxIlMA92jCplNzjB4aXUGYzRLRWmtHCcRl0w-Z9CXy99n7DIvuIEQ4yewyZJhMowPIQojVuLwdgm54ApB1lH-z6AJlwnQP0tQmjTBIgFFnhhOfRxKnXqgUfQ9bPexyuUD0YpDDjNj8V5sQUX4nFXqvwvnwwAb5PgiAomi-YIlZMUfmzlj-FFaF+zEF+DQqjVJOLqLfmRCdn6GUM5LlN+M+O0PJE7o9qmCBAAWBCmO3CSCaEPobv5jlCRvkKPOAr5OoKUoYGLsnKgfGEgcmNOHOIuMuKhhnPLLuMAQUnXEiFmnUqiLoDgVQlsMLPkK0L4uesvuNCZEwQ5NjA-pKKiEYPoroIVDyNdkNPkCOq3M7hDDTBdKIc+t+p+AMmKMYFiO+B6KDM7lTJ8OoVAJoYcLHuEkFMLJ+FiC6t5G7PoH9O5DoB6J+M+BiAHJvPrCHHBOHAuJYfCNyEVtiBiKwb5J+GpIDLbl0F+kYBMkvg2mQRvFvO2nvMEYiINufDRB+BZloDEcLC5KYHXopNsLiG-OrB-BAMEWxFeO+pUDApUB+nAkiH9MUHlNiPoiFMkcnJgrxCoOwNwsEXkA0cCk0aTLYUnrqjoGEtsFnl7MVlUfLlghACoAAEZxJawobBEupwJDSwpnJ8FVD8jZ4IZcZMbBGshUK5QsgTJBQXgi4hIDJTYIjchGB3EJbNKsDfKjHrpfhCzwJ5b6EVKaBhKhYuq6R0IkHcSvKuYeDxpfJpIZLBGBZZb8g5Yvh5aXJKKVBxE95iY3aRjO6IYKpYp1EfiFA8iIichcgnbfrwLlAZq5q+SYxBQUzmpxjeD-F-ThIyQfQwh6B5DjI4gFDeqFLcgfgwiCEXHlq3qCLBHyTDqmBRYN475jbTqayzp1EcgEl6IBjVC8z5pFCHr6BohJF5QdCclXo3qIkto359Y17njrriBiYupOxVC4k-otFmn9S6C4ifioigbyoF4SJ1EkaMR-RTZojTynHGImEqazhqairBFej+QtGFJ7675wi2Yf6-gCjySwmbaNKJZQBhkjFOnR4shsgnCmDKCVAmDlJ4ymZ-S3LNDBiwHGHwFbYdbLZ7FIiaRC6fh0Kfj7EtnZQQmejKDhjigmo9m05QDA6g4k7fZpl8weoQLzEL55TaTzLO5B4jAe6h7VZpn6ANh6ruHwIyTelNyvgnBVAejqBfgknwF56zgF6ZFVlYEFpQKKQchvStDW44hqDTaGl5AWAWBAA */
model.createMachine(
{
predictableActionArguments: true,
@@ -112,8 +136,20 @@ export const vcItemMachine =
checkingServerData: {
description:
"Download VC data from the server. Uses polling method to check when it's available.",
initial: 'checkingStatus',
initial: 'verifyingDownloadLimitExpiry',
states: {
verifyingDownloadLimitExpiry: {
invoke: {
src: 'checkDownloadExpiryLimit',
onDone: {
target: 'checkingStatus',
actions: 'setMaxDownloadCount',
},
onError: {
actions: log((_, event) => (event.data as Error).message),
},
},
},
checkingStatus: {
invoke: {
src: 'checkStatus',
@@ -121,10 +157,10 @@ export const vcItemMachine =
},
on: {
POLL: {
cond: 'isDownloadAllowed',
actions: send('POLL_STATUS', { to: 'checkStatus' }),
},
DOWNLOAD_READY: {
actions: 'resetDownloadCounter',
target: 'downloadingCredential',
},
},
@@ -172,6 +208,9 @@ export const vcItemMachine =
REVOKE_VC: {
target: 'acceptingRevokeInput',
},
ADD_WALLET_BINDING_ID: {
target: 'showBindingWarning',
},
},
},
editingTag: {
@@ -330,7 +369,10 @@ export const vcItemMachine =
],
onError: [
{
actions: [log('OTP error'), 'setOtpError'],
actions: [
log((_, event) => (event.data as Error).message),
'setOtpError',
],
target: 'acceptingOtpInput',
},
],
@@ -352,10 +394,136 @@ export const vcItemMachine =
},
},
},
showBindingWarning: {
on: {
CONFIRM: {
target: 'requestingBindingOtp',
},
CANCEL: {
target: 'idle',
},
},
},
requestingBindingOtp: {
invoke: {
src: 'requestBindingOtp',
onDone: [
{
target: 'acceptingBindingOtp',
},
],
onError: [
{
actions: 'setWalletBindingError',
target: 'showingWalletBindingError',
},
],
},
},
showingWalletBindingError: {
on: {
CANCEL: {
target: 'idle',
actions: 'setWalletBindingErrorEmpty',
},
},
},
acceptingBindingOtp: {
entry: ['clearOtp'],
on: {
INPUT_OTP: {
target: 'addKeyPair',
actions: ['setOtp'],
},
DISMISS: {
target: 'idle',
actions: ['clearOtp', 'clearTransactionId'],
},
},
},
addKeyPair: {
invoke: {
src: 'generateKeyPair',
onDone: {
target: 'addingWalletBindingId',
actions: ['setPublicKey', 'setPrivateKey'],
},
onError: [
{
actions: 'setWalletBindingError',
target: 'showingWalletBindingError',
},
],
},
},
addingWalletBindingId: {
invoke: {
src: 'addWalletBindnigId',
onDone: [
{
target: 'updatingPrivateKey',
actions: ['setWalletBindingId'],
},
],
onError: [
{
actions: 'setWalletBindingError',
target: 'showingWalletBindingError',
},
],
},
},
updatingPrivateKey: {
invoke: {
src: 'updatePrivateKey',
onDone: {
target: 'showBindingStatus',
actions: ['updatePrivateKey', 'updateVc'],
},
onError: {
actions: 'setWalletBindingError',
target: 'showingWalletBindingError',
},
},
},
showBindingStatus: {
entry: 'storeContext',
on: {
BINDING_DONE: {
target: 'idle',
actions: 'setWalletBindingErrorEmpty',
},
},
},
},
},
{
actions: {
setWalletBindingError: assign({
walletBindingError: (context, event) => (event.data as Error).message,
}),
setWalletBindingErrorEmpty: assign({
walletBindingError: () => '',
}),
setPublicKey: assign({
publicKey: (context, event) => (event.data as KeyPair).public,
}),
setPrivateKey: assign({
privateKey: (context, event) => (event.data as KeyPair).private,
}),
updatePrivateKey: assign({
privateKey: () => '',
}),
setWalletBindingId: assign({
walletBindingResponse: (context, event) =>
event.data as WalletBindingResponse,
}),
updateVc: send(
(context) => {
const { serviceRefs, ...vc } = context;
@@ -397,14 +565,14 @@ export const vcItemMachine =
tag: (_, event) => event.tag,
}),
resetDownloadCounter: model.assign({
downloadCounter: () => 0,
}),
incrementDownloadCounter: model.assign({
downloadCounter: ({ downloadCounter }) => downloadCounter + 1,
}),
setMaxDownloadCount: model.assign({
maxDownloadCount: (_context, event) => event.data as number,
}),
storeTag: send(
(context) => {
const { serviceRefs, ...data } = context;
@@ -506,6 +674,80 @@ export const vcItemMachine =
},
services: {
checkDownloadExpiryLimit: async (context) => {
var resp = await getAllConfigurations();
const maxLimit: number = resp.vcDownloadMaxRetry;
console.log(maxLimit);
if (maxLimit <= context.downloadCounter) {
throw new Error(
'Download limit expired for request id: ' + context.requestId
);
}
return maxLimit;
},
addWalletBindnigId: async (context) => {
const response = await request('POST', '/wallet-binding', {
requestTime: String(new Date().toISOString()),
request: {
authFactorType: 'WLA',
format: 'jwt',
individualId: context.id,
transactionId: context.bindingTransactionId,
publicKey: context.publicKey,
challengeList: [
{
authFactorType: 'OTP',
challenge: context.otp,
format: 'alpha-numeric',
},
],
},
});
const certificate = response.response.certificate;
await savePrivateKey(
getBindingCertificateConstant(context.id),
certificate
);
const walletResponse: WalletBindingResponse = {
walletBindingId: response.response.encryptedWalletBindingId,
keyId: response.response.keyId,
thumbprint: response.response.thumbprint,
expireDateTime: response.response.expireDateTime,
};
return walletResponse;
},
updatePrivateKey: async (context) => {
const hasSetPrivateKey: boolean = await savePrivateKey(
context.walletBindingResponse.walletBindingId,
context.privateKey
);
if (!hasSetPrivateKey) {
throw new Error('Could not store private key in keystore.');
}
return '';
},
generateKeyPair: async (context) => {
let keyPair: KeyPair = await generateKeys();
return keyPair;
},
requestBindingOtp: async (context) => {
const response = await request('POST', '/binding-otp', {
requestTime: String(new Date().toISOString()),
request: {
individualId: context.id,
otpChannels: ['EMAIL'],
},
});
if (response.response == null) {
throw new Error('Could not process request');
}
},
checkStatus: (context) => (callback, onReceive) => {
const pollInterval = setInterval(
() => callback(model.events.POLL()),
@@ -561,6 +803,7 @@ export const vcItemMachine =
isVerified: false,
lastVerifiedOn: null,
locked: context.locked,
walletBindingResponse: null,
})
);
}
@@ -633,7 +876,7 @@ export const vcItemMachine =
},
isDownloadAllowed: (_context, event) => {
return _context.downloadCounter < 10;
return _context.downloadCounter <= _context.maxDownloadCount;
},
isVcValid: (context) => {
@@ -724,6 +967,46 @@ export function selectIsAcceptingRevokeInput(state: State) {
return state.matches('acceptingRevokeInput');
}
export function selectIsRequestingOtp(state: State) {
return state.matches('requestingOtp');
export function selectIsRequestBindingOtp(state: State) {
return state.matches('requestingBindingOtp');
}
export function selectWalletBindingId(state: State) {
return state.context.walletBindingResponse;
}
export function selectEmptyWalletBindingId(state: State) {
var val = state.context.walletBindingResponse
? state.context.walletBindingResponse.walletBindingId
: undefined;
return val === undefined || val == null || val.length <= 0 ? true : false;
}
export function selectWalletBindingError(state: State) {
return state.context.walletBindingError;
}
export function selectAcceptingBindingOtp(state: State) {
return state.matches('acceptingBindingOtp');
}
export function selectShowBindingStatus(state: State) {
return state.matches('showBindingStatus');
}
export function selectShowWalletBindingError(state: State) {
return state.matches('showingWalletBindingError');
}
export function isWalletBindingInProgress(state: State) {
return state.matches('requestingBindingOtp') ||
state.matches('addingWalletBindingId') ||
state.matches('addKeyPair') ||
state.matches('updatingPrivateKey')
? true
: false;
}
export function isShowingBindingWarning(state: State) {
return state.matches('showBindingWarning');
}

View File

@@ -14,6 +14,26 @@ export interface Typegen0 {
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.addKeyPair:invocation[0]': {
type: 'done.invoke.vc-item.addKeyPair:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.addingWalletBindingId:invocation[0]': {
type: 'done.invoke.vc-item.addingWalletBindingId:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]': {
type: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.requestingBindingOtp:invocation[0]': {
type: 'done.invoke.vc-item.requestingBindingOtp:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.requestingLock:invocation[0]': {
type: 'done.invoke.vc-item.requestingLock:invocation[0]';
data: unknown;
@@ -29,6 +49,11 @@ export interface Typegen0 {
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.updatingPrivateKey:invocation[0]': {
type: 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.verifyingCredential:invocation[0]': {
type: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
data: unknown;
@@ -42,6 +67,18 @@ export interface Typegen0 {
type: 'error.platform.downloadCredential';
data: unknown;
};
'error.platform.vc-item.addKeyPair:invocation[0]': {
type: 'error.platform.vc-item.addKeyPair:invocation[0]';
data: unknown;
};
'error.platform.vc-item.addingWalletBindingId:invocation[0]': {
type: 'error.platform.vc-item.addingWalletBindingId:invocation[0]';
data: unknown;
};
'error.platform.vc-item.requestingBindingOtp:invocation[0]': {
type: 'error.platform.vc-item.requestingBindingOtp:invocation[0]';
data: unknown;
};
'error.platform.vc-item.requestingLock:invocation[0]': {
type: 'error.platform.vc-item.requestingLock:invocation[0]';
data: unknown;
@@ -50,6 +87,10 @@ export interface Typegen0 {
type: 'error.platform.vc-item.requestingRevoke:invocation[0]';
data: unknown;
};
'error.platform.vc-item.updatingPrivateKey:invocation[0]': {
type: 'error.platform.vc-item.updatingPrivateKey:invocation[0]';
data: unknown;
};
'error.platform.vc-item.verifyingCredential:invocation[0]': {
type: 'error.platform.vc-item.verifyingCredential:invocation[0]';
data: unknown;
@@ -57,11 +98,16 @@ export interface Typegen0 {
'xstate.init': { type: 'xstate.init' };
};
'invokeSrcNameMap': {
addWalletBindnigId: 'done.invoke.vc-item.addingWalletBindingId:invocation[0]';
checkDownloadExpiryLimit: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
checkStatus: 'done.invoke.checkStatus';
downloadCredential: 'done.invoke.downloadCredential';
generateKeyPair: 'done.invoke.vc-item.addKeyPair:invocation[0]';
requestBindingOtp: 'done.invoke.vc-item.requestingBindingOtp:invocation[0]';
requestLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
requestOtp: 'done.invoke.vc-item.requestingOtp:invocation[0]';
requestRevoke: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
updatePrivateKey: 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
verifyCredential: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
};
'missingImplementations': {
@@ -73,9 +119,12 @@ export interface Typegen0 {
'eventsCausingActions': {
clearOtp:
| ''
| 'BINDING_DONE'
| 'CANCEL'
| 'DISMISS'
| 'REVOKE_VC'
| 'STORE_RESPONSE'
| 'done.invoke.vc-item.requestingBindingOtp:invocation[0]'
| 'done.invoke.vc-item.requestingOtp:invocation[0]'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]'
| 'error.platform.vc-item.requestingLock:invocation[0]'
@@ -83,6 +132,8 @@ export interface Typegen0 {
| 'error.platform.vc-item.verifyingCredential:invocation[0]';
clearTransactionId:
| ''
| 'BINDING_DONE'
| 'CANCEL'
| 'DISMISS'
| 'STORE_RESPONSE'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]'
@@ -93,17 +144,19 @@ export interface Typegen0 {
markVcValid: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
requestStoredContext: 'GET_VC_RESPONSE' | 'REFRESH';
requestVcContext: 'xstate.init';
resetDownloadCounter: 'DOWNLOAD_READY';
revokeVID: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
setCredential:
| 'CREDENTIAL_DOWNLOADED'
| 'GET_VC_RESPONSE'
| 'STORE_RESPONSE';
setLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
setMaxDownloadCount: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
setOtp: 'INPUT_OTP';
setOtpError:
| 'error.platform.vc-item.requestingLock:invocation[0]'
| 'error.platform.vc-item.requestingRevoke:invocation[0]';
setPrivateKey: 'done.invoke.vc-item.addKeyPair:invocation[0]';
setPublicKey: 'done.invoke.vc-item.addKeyPair:invocation[0]';
setRevoke: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
setTag: 'SAVE_TAG';
setTransactionId:
@@ -112,14 +165,24 @@ export interface Typegen0 {
| 'done.invoke.vc-item.requestingOtp:invocation[0]'
| 'error.platform.vc-item.requestingLock:invocation[0]'
| 'error.platform.vc-item.requestingRevoke:invocation[0]';
setWalletBindingError:
| 'error.platform.vc-item.addKeyPair:invocation[0]'
| 'error.platform.vc-item.addingWalletBindingId:invocation[0]'
| 'error.platform.vc-item.requestingBindingOtp:invocation[0]'
| 'error.platform.vc-item.updatingPrivateKey:invocation[0]';
setWalletBindingErrorEmpty: 'BINDING_DONE' | 'CANCEL';
setWalletBindingId: 'done.invoke.vc-item.addingWalletBindingId:invocation[0]';
storeContext:
| 'CREDENTIAL_DOWNLOADED'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]';
storeLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
storeTag: 'SAVE_TAG';
updatePrivateKey: 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
updateVc:
| 'CREDENTIAL_DOWNLOADED'
| 'STORE_RESPONSE'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]';
};
'eventsCausingDelays': {};
@@ -129,19 +192,28 @@ export interface Typegen0 {
isVcValid: '';
};
'eventsCausingServices': {
checkStatus: 'STORE_RESPONSE';
addWalletBindnigId: 'done.invoke.vc-item.addKeyPair:invocation[0]';
checkDownloadExpiryLimit: 'STORE_RESPONSE';
checkStatus: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
downloadCredential: 'DOWNLOAD_READY';
generateKeyPair: 'INPUT_OTP';
requestBindingOtp: 'CONFIRM';
requestLock: 'INPUT_OTP';
requestOtp: 'LOCK_VC';
requestRevoke: 'INPUT_OTP';
updatePrivateKey: 'done.invoke.vc-item.addingWalletBindingId:invocation[0]';
verifyCredential: '' | 'VERIFY';
};
'matchesStates':
| 'acceptingBindingOtp'
| 'acceptingOtpInput'
| 'acceptingRevokeInput'
| 'addKeyPair'
| 'addingWalletBindingId'
| 'checkingServerData'
| 'checkingServerData.checkingStatus'
| 'checkingServerData.downloadingCredential'
| 'checkingServerData.verifyingDownloadLimitExpiry'
| 'checkingStore'
| 'checkingVc'
| 'checkingVerificationStatus'
@@ -152,14 +224,22 @@ export interface Typegen0 {
| 'invalid.otp'
| 'lockingVc'
| 'loggingRevoke'
| 'requestingBindingOtp'
| 'requestingLock'
| 'requestingOtp'
| 'requestingRevoke'
| 'revokingVc'
| 'showBindingStatus'
| 'showBindingWarning'
| 'showingWalletBindingError'
| 'storingTag'
| 'updatingPrivateKey'
| 'verifyingCredential'
| {
checkingServerData?: 'checkingStatus' | 'downloadingCredential';
checkingServerData?:
| 'checkingStatus'
| 'downloadingCredential'
| 'verifyingDownloadLimitExpiry';
invalid?: 'backend' | 'otp';
};
'tags': never;

50
package-lock.json generated
View File

@@ -40,6 +40,7 @@
"expo-updates": "~0.11.6",
"i18next": "^21.6.16",
"mosip-inji-face-sdk": "^0.1.7",
"node-forge": "^1.3.1",
"react": "17.0.1",
"react-i18next": "^11.16.6",
"react-native": "0.64.4",
@@ -58,8 +59,10 @@
"react-native-popable": "^0.4.3",
"react-native-qrcode-svg": "^6.1.1",
"react-native-restart": "^0.0.24",
"react-native-rsa-native": "^2.0.5",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.10.1",
"react-native-secure-key-store": "^2.0.10",
"react-native-securerandom": "^1.0.0",
"react-native-simple-markdown": "^1.1.0",
"react-native-svg": "12.1.1",
@@ -1999,6 +2002,14 @@
"node": ">=8.3.0"
}
},
"node_modules/@digitalbazaar/rsa-verification-key-2018/node_modules/node-forge": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz",
"integrity": "sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw==",
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/@digitalbazaar/rsa-verification-key-2018/node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@@ -18039,11 +18050,11 @@
}
},
"node_modules/node-forge": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz",
"integrity": "sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
"engines": {
"node": ">= 4.5.0"
"node": ">= 6.13.0"
}
},
"node_modules/node-gyp-build": {
@@ -21236,6 +21247,11 @@
"react-native": "*"
}
},
"node_modules/react-native-rsa-native": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/react-native-rsa-native/-/react-native-rsa-native-2.0.5.tgz",
"integrity": "sha512-gwwvFSwGW5WKrpDyBQ/eTf1UrVABeAvMcT4YWemzPSUo6aHZs1kbBm2rXmwN5okhUzJsry5zjjz/qdx5GXRugQ=="
},
"node_modules/react-native-safe-area-context": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz",
@@ -21258,6 +21274,11 @@
"react-native": "*"
}
},
"node_modules/react-native-secure-key-store": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/react-native-secure-key-store/-/react-native-secure-key-store-2.0.10.tgz",
"integrity": "sha512-K7aVlIGxyklnjhCidVexVgZF3LsgUD9GIxMy2NB/xkQsS9E2SJWkD/fJ56e25L2I6a9Mp1zuJrKnCtfBs1CvAw=="
},
"node_modules/react-native-securerandom": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-1.0.1.tgz",
@@ -28857,6 +28878,11 @@
"resolved": "https://registry.npmjs.org/crypto-ld/-/crypto-ld-4.0.3.tgz",
"integrity": "sha512-IeNCX1wv7kLjxhKrCV6Jee0CU84e2dSyVxTaM3SCuO8/fMbYzjsx1e1js+c7w8GlOslubOHJKvhJ1EI+aZ1MwQ=="
},
"node-forge": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz",
"integrity": "sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw=="
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@@ -41592,9 +41618,9 @@
}
},
"node-forge": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz",
"integrity": "sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw=="
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
},
"node-gyp-build": {
"version": "4.4.0",
@@ -44145,6 +44171,11 @@
"integrity": "sha512-pvJNU3NwQk6bCG2gOWcQpZ4IxhtELB0K9gzmtixfsaTFbW1UXXHkJNjk1kHazcbH5hrD7QbUkR63fsAVh8X4VQ==",
"requires": {}
},
"react-native-rsa-native": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/react-native-rsa-native/-/react-native-rsa-native-2.0.5.tgz",
"integrity": "sha512-gwwvFSwGW5WKrpDyBQ/eTf1UrVABeAvMcT4YWemzPSUo6aHZs1kbBm2rXmwN5okhUzJsry5zjjz/qdx5GXRugQ=="
},
"react-native-safe-area-context": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz",
@@ -44160,6 +44191,11 @@
"warn-once": "^0.1.0"
}
},
"react-native-secure-key-store": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/react-native-secure-key-store/-/react-native-secure-key-store-2.0.10.tgz",
"integrity": "sha512-K7aVlIGxyklnjhCidVexVgZF3LsgUD9GIxMy2NB/xkQsS9E2SJWkD/fJ56e25L2I6a9Mp1zuJrKnCtfBs1CvAw=="
},
"react-native-securerandom": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-1.0.1.tgz",

View File

@@ -45,6 +45,7 @@
"expo-updates": "~0.11.6",
"i18next": "^21.6.16",
"mosip-inji-face-sdk": "^0.1.7",
"node-forge": "^1.3.1",
"react": "17.0.1",
"react-i18next": "^11.16.6",
"react-native": "0.64.4",
@@ -62,8 +63,10 @@
"react-native-popable": "^0.4.3",
"react-native-qrcode-svg": "^6.1.1",
"react-native-restart": "^0.0.24",
"react-native-rsa-native": "^2.0.5",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.10.1",
"react-native-secure-key-store": "^2.0.10",
"react-native-securerandom": "^1.0.0",
"react-native-simple-markdown": "^1.1.0",
"react-native-svg": "12.1.1",

View File

@@ -12,7 +12,7 @@ import i18n from '../i18n';
import { Platform } from 'react-native';
import { isBLEEnabled } from '../lib/smartshare';
var home: TabScreen = {
const home: TabScreen = {
name: 'Home',
component: HomeScreen,
icon: 'home',
@@ -20,7 +20,7 @@ var home: TabScreen = {
title: i18n.t('MainLayout:home'),
},
};
var scan: TabScreen = {
const scan: TabScreen = {
name: 'Scan',
component: ScanLayout,
icon: 'qr-code-scanner',
@@ -29,7 +29,7 @@ var scan: TabScreen = {
headerShown: false,
},
};
var request: TabScreen = {
const request: TabScreen = {
name: 'Request',
component: RequestLayout,
icon: 'file-download',
@@ -38,7 +38,7 @@ var request: TabScreen = {
headerShown: false,
},
};
var profile: TabScreen = {
const profile: TabScreen = {
name: 'Profile',
component: ProfileScreen,
icon: 'person',

View File

@@ -11,18 +11,18 @@ export interface Typegen0 {
'invokeSrcNameMap': {};
'missingImplementations': {
actions: never;
services: never;
guards: never;
delays: never;
guards: never;
services: never;
};
'eventsCausingActions': {
resetSelectedVc: 'DISMISS_MODAL' | 'xstate.init';
setSelectedVc: 'VIEW_VC';
spawnTabActors: 'xstate.init';
};
'eventsCausingServices': {};
'eventsCausingGuards': {};
'eventsCausingDelays': {};
'eventsCausingGuards': {};
'eventsCausingServices': {};
'matchesStates':
| 'modals'
| 'modals.none'

View File

@@ -32,9 +32,9 @@ export interface Typegen0 {
};
'missingImplementations': {
actions: never;
services: never;
guards: never;
delays: never;
guards: never;
services: never;
};
'eventsCausingActions': {
clearId: 'SELECT_ID_TYPE';
@@ -69,16 +69,16 @@ export interface Typegen0 {
| 'error.platform.AddVcModal.requestingCredential:invocation[0]'
| 'xstate.init';
};
'eventsCausingServices': {
requestCredential: 'INPUT_OTP';
requestOtp: 'VALIDATE_INPUT';
};
'eventsCausingDelays': {};
'eventsCausingGuards': {
isEmptyId: 'VALIDATE_INPUT';
isIdInvalid: 'error.platform.AddVcModal.requestingCredential:invocation[0]';
isWrongIdFormat: 'VALIDATE_INPUT';
};
'eventsCausingDelays': {};
'eventsCausingServices': {
requestCredential: 'INPUT_OTP';
requestOtp: 'VALIDATE_INPUT';
};
'matchesStates':
| 'acceptingIdInput'
| 'acceptingIdInput.focusing'

View File

@@ -0,0 +1,69 @@
import { useSelector } from '@xstate/react';
import { useContext } from 'react';
import { ActorRefFrom } from 'xstate';
import {
selectIsRefreshingMyVcs,
selectMyVcs,
VcEvents,
} from '../../../machines/vc';
import { GlobalContext } from '../../../shared/GlobalContext';
import NetInfo from '@react-native-community/netinfo';
import { selectVcLabel } from '../../../machines/settings';
import {
selectAcceptingBindingOtp,
selectIsRequestBindingOtp,
selectOtpError,
selectShowBindingStatus,
selectWalletBindingError,
selectWalletBindingId,
VcItemEvents,
vcItemMachine,
} from '../../../machines/vcItem';
import { ModalProps } from '../../../components/ui/Modal';
export function useBindVcStatus(props: BindVcProps) {
const { appService } = useContext(GlobalContext);
const settingsService = appService.children.get('settings');
const vcService = appService.children.get('vc');
const netInfoFetch = (otp: string) => {
NetInfo.fetch().then((state) => {
if (state.isConnected) {
vcService.send(VcItemEvents.INPUT_OTP(otp));
} else {
vcService.send(VcItemEvents.DISMISS());
showToast('Request network failed');
}
});
};
return {
vcKeys: useSelector(vcService, selectMyVcs),
vcLabel: useSelector(settingsService, selectVcLabel),
isRefreshingVcs: useSelector(vcService, selectIsRefreshingMyVcs),
isBindingOtp: useSelector(vcService, selectIsRequestBindingOtp),
walletAddress: useSelector(vcService, selectWalletBindingId),
isAcceptingBindingOtp: useSelector(vcService, selectAcceptingBindingOtp),
showBindingStatus: useSelector(vcService, selectShowBindingStatus),
walletBindingError: useSelector(vcService, selectWalletBindingError),
inputOtp: (otp: string) => {
netInfoFetch(otp);
},
otpError: useSelector(vcService, selectOtpError),
BINDING_DONE: () => vcService.send(VcItemEvents.BINDING_DONE()),
INPUT_OTP: (otp: string) => vcService.send(VcItemEvents.INPUT_OTP(otp)),
DISMISS: () => vcService.send(VcItemEvents.DISMISS()),
REFRESH: () => vcService.send(VcEvents.REFRESH_MY_VCS()),
};
}
function showToast(arg0: string) {
throw new Error('Function not implemented.');
}
export interface BindVcProps extends ModalProps {
bindingError: string;
onDone: () => void;
}

View File

@@ -0,0 +1,55 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Column, Text, Button, Row } from '../../../components/ui';
import { Theme } from '../../../components/ui/styleUtils';
import { useBindVcStatus, BindVcProps } from './BindVcController';
import { Image } from 'react-native';
import { Modal } from '../../../components/ui/Modal';
import { Icon } from 'react-native-elements';
export const BindStatus: React.FC<BindVcProps> = (props) => {
const controller = useBindVcStatus(props);
const { t, i18n } = useTranslation('VcDetails');
var message: string = controller.walletBindingError;
return (
<Modal
isVisible={props.isVisible}
onDismiss={props.onDismiss}
headerElevation={2}
headerTitle={t('status')}
headerRight={<Icon name={''} />}>
<Column fill align="space-around" crossAlign="center" padding={'10'}>
<Column crossAlign="center">
{!controller.walletBindingError ? (
<Image source={Theme.SuccessLogo} resizeMethod="auto" />
) : (
<Row align="center" crossAlign="center" margin={'0 80 0 0'}>
<Image source={Theme.WarningLogo} resizeMethod="auto" />
<Text
margin={'0 0 0 -80'}
color={Theme.Colors.whiteText}
weight="bold">
!
</Text>
</Row>
)}
{controller.walletBindingError ? (
<Text
align="center"
color={Theme.Colors.errorMessage}
margin="16 0 0 0">
{{ message }}
</Text>
) : (
<Text align="center" margin="16 0 0 0">
{t('verificationEnabledSuccess')}
</Text>
)}
</Column>
<Button title={t('ok')} onPress={props.onDone} type="radius" />
</Column>
</Modal>
);
};

View File

@@ -0,0 +1,62 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Dimensions, Image } from 'react-native';
import { Overlay } from 'react-native-elements';
import { Button, Column, Text, Row } from '../../../components/ui';
import { Theme } from '../../../components/ui/styleUtils';
export const BindingVcWarningOverlay: React.FC<QrLoginWarningProps> = (
props
) => {
const { t } = useTranslation('VcDetails');
return (
<Overlay
isVisible={props.isVisible}
overlayStyle={Theme.BindingVcWarningOverlay.overlay}>
<Column
align="space-between"
crossAlign="center"
padding={'10'}
width={Dimensions.get('screen').width * 0.8}>
<Row align="center" crossAlign="center" margin={'0 80 0 0'}>
<Image source={Theme.WarningLogo} resizeMethod="auto" />
<Text
margin={'0 0 0 -80'}
color={Theme.Colors.whiteText}
weight="bold">
!
</Text>
</Row>
<Text size="regular" weight="bold">
{t('Alert')}
</Text>
<Text align="center" size="smaller">
{t('BindingWarning')}
</Text>
<Button
margin={'10 0 0 0'}
type="radius"
title={t('yes_confirm')}
onPress={props.onConfirm}
/>
<Button
margin={'10 0 0 0'}
type="clear"
title={t('no')}
onPress={props.onCancel}
/>
</Column>
</Overlay>
);
};
interface QrLoginWarningProps {
isVisible: boolean;
onConfirm: () => void;
onCancel: () => void;
}

View File

@@ -1,5 +1,5 @@
{
"header": "To retrieve your UIN or VID, enter your Application {{vcLabel}} number",
"header": "To retrieve your UIN or VID, enter your application {{vcLabel}} number",
"getUIN": "Get UIN/VID",
"applicationId": "Application {{vcLabel}} number",
"requestingOTP": "Requesting OTP...",

View File

@@ -6,7 +6,12 @@ import { Modal } from '../../../components/ui/Modal';
import { Theme } from '../../../components/ui/styleUtils';
import { IdInputModalProps, useIdInputModal } from './IdInputModalController';
import { useTranslation } from 'react-i18next';
import { KeyboardAvoidingView, Platform, TextInput } from 'react-native';
import {
I18nManager,
KeyboardAvoidingView,
Platform,
TextInput,
} from 'react-native';
import { TouchableOpacity } from 'react-native';
import { individualId } from '../../../shared/constants';
import { GET_INDIVIDUAL_ID } from '../../../shared/constants';
@@ -71,6 +76,9 @@ export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
? Theme.Colors.errorMessage
: Theme.Colors.textValue,
}}
inputStyle={{
textAlign: I18nManager.isRTL ? 'right' : 'left',
}}
value={controller.id}
keyboardType="number-pad"
rightIcon={

View File

@@ -1,27 +1,29 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { View } from 'react-native';
import { Icon } from 'react-native-elements';
import { PinInput } from '../../../components/PinInput';
import { Column, Text } from '../../../components/ui';
import { ModalProps } from '../../../components/ui/Modal';
import { ModalProps, Modal } from '../../../components/ui/Modal';
import { Theme } from '../../../components/ui/styleUtils';
import { Image } from 'react-native';
import { Icon } from 'react-native-elements';
export const OtpVerification: React.FC<OtpVerificationModalProps> = (props) => {
const { t } = useTranslation('OtpVerificationModal');
return (
<View style={Theme.OtpVerificationStyles.viewContainer}>
<Modal
isVisible={props.isVisible}
onDismiss={props.onDismiss}
headerElevation={2}
headerTitle={t('header')}
headerRight={<Icon name={''} />}>
<Column
fill
padding="32"
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<View style={Theme.OtpVerificationStyles.close}>
<Icon name="close" onPress={() => props.onDismiss()} />
</View>
<Icon name="lock" color={Theme.Colors.Icon} size={60} />
<Column fill align="space-between">
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Column fill align="space-between" crossAlign="center">
<Text align="center">{t('enterOtp')}</Text>
<Image source={Theme.OtpLogo} resizeMethod="auto" />
<Text
align="center"
color={Theme.Colors.errorMessage}
@@ -32,7 +34,7 @@ export const OtpVerification: React.FC<OtpVerificationModalProps> = (props) => {
</Column>
<Column fill></Column>
</Column>
</View>
</Modal>
);
};

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { DropdownIcon } from '../../components/DropdownIcon';
import { TextEditOverlay } from '../../components/TextEditOverlay';
import { Column } from '../../components/ui';
import { Column, Text } from '../../components/ui';
import { Modal } from '../../components/ui/Modal';
import { MessageOverlay } from '../../components/MessageOverlay';
import { ToastItem } from '../../components/ui/ToastItem';
@@ -10,8 +10,9 @@ import { OIDcAuthenticationModal } from '../../components/OIDcAuth';
import { useViewVcModal, ViewVcModalProps } from './ViewVcModalController';
import { useTranslation } from 'react-i18next';
import { VcDetails } from '../../components/VcDetails';
import { OtpVerification } from './MyVcs/OtpVerification';
import { BindStatus } from './MyVcs/BindVcStatus';
import { BindingVcWarningOverlay } from './MyVcs/BindingVcWarningOverlay';
export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
const { t } = useTranslation('ViewVcModal');
@@ -35,7 +36,11 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
<Modal
isVisible={props.isVisible}
onDismiss={props.onDismiss}
headerTitle={controller.vc.id}
headerTitle={
controller.vc.verifiableCredential.credentialSubject.UIN
? controller.vc.verifiableCredential.credentialSubject.UIN
: controller.vc.verifiableCredential.credentialSubject.VID
}
headerElevation={2}
headerRight={
<DropdownIcon
@@ -46,7 +51,17 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
}>
<Column scroll>
<Column fill>
<VcDetails vc={controller.vc} />
<VcDetails
vc={controller.vc}
onBinding={controller.addtoWallet}
isBindingPending={controller.isWalletBindingPending}
/>
{controller.walletBindingError !== '' && (
<Text style={{ color: 'red', fontSize: 20 }}>
Error Occured : {controller.walletBindingError}
</Text>
)}
</Column>
</Column>
{controller.isEditingTag && (
@@ -79,9 +94,41 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
/>
)}
{controller.isAcceptingBindingOtp && (
<OtpVerification
isVisible={controller.isAcceptingBindingOtp}
onDismiss={controller.DISMISS}
onInputDone={controller.inputOtp}
error={controller.otpError}
/>
)}
{controller.showBindingStatus && (
<BindStatus
isVisible={controller.showBindingStatus}
bindingError={controller.walletBindingError}
onDismiss={controller.BINDING_DONE}
onDone={controller.BINDING_DONE}
/>
)}
<BindingVcWarningOverlay
isVisible={controller.isBindingWarning}
onConfirm={controller.CONFIRM}
onCancel={controller.CANCEL}
/>
<MessageOverlay
isVisible={controller.isRequestingOtp}
title={t('requestingOtp')}
isVisible={controller.isBindingError}
title={controller.walletBindingError}
onCancel={() => {
controller.CANCEL();
}}
/>
<MessageOverlay
isVisible={controller.isWalletBindingInProgress}
title={t('inProgress')}
progress
/>

View File

@@ -11,12 +11,20 @@ import {
selectIsAcceptingRevokeInput,
selectIsEditingTag,
selectIsLockingVc,
selectIsRequestingOtp,
selectIsRevokingVc,
selectIsLoggingRevoke,
selectVc,
VcItemEvents,
vcItemMachine,
selectWalletBindingId,
selectWalletBindingError,
selectIsRequestBindingOtp,
selectAcceptingBindingOtp,
selectShowBindingStatus,
selectEmptyWalletBindingId,
isWalletBindingInProgress,
selectShowWalletBindingError,
isShowingBindingWarning,
} from '../../machines/vcItem';
import { selectPasscode } from '../../machines/auth';
import { biometricsMachine, selectIsSuccess } from '../../machines/biometrics';
@@ -123,8 +131,21 @@ export function useViewVcModal({
vcItemActor,
selectIsAcceptingRevokeInput
),
isRequestingOtp: useSelector(vcItemActor, selectIsRequestingOtp),
storedPasscode: useSelector(authService, selectPasscode),
isBindingOtp: useSelector(vcItemActor, selectIsRequestBindingOtp),
isAcceptingBindingOtp: useSelector(vcItemActor, selectAcceptingBindingOtp),
showBindingStatus: useSelector(vcItemActor, selectShowBindingStatus),
walletBindingError: useSelector(vcItemActor, selectWalletBindingError),
isWalletBindingPending: useSelector(
vcItemActor,
selectEmptyWalletBindingId
),
isWalletBindingInProgress: useSelector(
vcItemActor,
isWalletBindingInProgress
),
isBindingError: useSelector(vcItemActor, selectShowWalletBindingError),
isBindingWarning: useSelector(vcItemActor, isShowingBindingWarning),
CONFIRM_REVOKE_VC: () => {
setRevoking(true);
@@ -136,6 +157,9 @@ export function useViewVcModal({
setReAuthenticating,
setRevoking,
onError,
addtoWallet: () => {
vcItemActor.send(VcItemEvents.ADD_WALLET_BINDING_ID());
},
lockVc: () => {
vcItemActor.send(VcItemEvents.LOCK_VC());
},
@@ -145,12 +169,16 @@ export function useViewVcModal({
revokeVc: (otp: string) => {
netInfoFetch(otp);
},
ADD_WALLET: () => vcItemActor.send(VcItemEvents.ADD_WALLET_BINDING_ID()),
onSuccess,
EDIT_TAG: () => vcItemActor.send(VcItemEvents.EDIT_TAG()),
SAVE_TAG: (tag: string) => vcItemActor.send(VcItemEvents.SAVE_TAG(tag)),
DISMISS: () => vcItemActor.send(VcItemEvents.DISMISS()),
BINDING_DONE: () => vcItemActor.send(VcItemEvents.BINDING_DONE()),
LOCK_VC: () => vcItemActor.send(VcItemEvents.LOCK_VC()),
INPUT_OTP: (otp: string) => vcItemActor.send(VcItemEvents.INPUT_OTP(otp)),
CANCEL: () => vcItemActor.send(VcItemEvents.CANCEL()),
CONFIRM: () => vcItemActor.send(VcItemEvents.CONFIRM()),
};
}

View File

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Image, SafeAreaView, View } from 'react-native';
import { Divider, Icon, ListItem, Overlay } from 'react-native-elements';
import { Button, Text, Row } from '../../components/ui';
import { Button, Text, Row, Column } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import creditsContent from '../../Credits.md';
@@ -19,6 +19,11 @@ export const Credits: React.FC<CreditsProps> = (props) => {
const markdownStyles = {
heading1: {
fontSize: 24,
textAlign: 'left',
},
heading2: {
fontSize: 24,
textAlign: 'left',
},
image: {
maxWidth: 150,
@@ -42,6 +47,13 @@ export const Credits: React.FC<CreditsProps> = (props) => {
return (
<ListItem bottomDivider onPress={() => setIsViewing(true)}>
<Icon
name="filetext1"
type="antdesign"
size={20}
style={Theme.Styles.profileIconBg}
color={Theme.Colors.Icon}
/>
<ListItem.Content>
<ListItem.Title>
<Text color={props.color}>{props.label}</Text>

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { View } from 'react-native';
import { getVersion } from 'react-native-device-info';
import { ListItem, Switch } from 'react-native-elements';
import { Icon, ListItem, Switch } from 'react-native-elements';
import { Column, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { MainRouteProps } from '../../routes/main';
@@ -14,6 +14,7 @@ import { useTranslation } from 'react-i18next';
import { LanguageSelector } from '../../components/LanguageSelector';
import i18next, { SUPPORTED_LANGUAGES } from '../../i18n';
import { isBLEEnabled } from '../../lib/smartshare';
import { ScrollView } from 'react-native-gesture-handler';
const LanguageSetting: React.FC = () => {
const { t } = useTranslation('ProfileScreen');
@@ -40,92 +41,118 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
const { t } = useTranslation('ProfileScreen');
const controller = useProfileScreen(props);
return (
<Column
fill
padding="24 0"
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<MessageOverlay
isVisible={controller.alertMsg != ''}
onBackdropPress={controller.hideAlert}
title={controller.alertMsg}
/>
<EditableListItem
label={t('name')}
value={controller.name}
onEdit={controller.UPDATE_NAME}
/>
<EditableListItem
label={t('vcLabel')}
value={controller.vcLabel.singular}
onEdit={controller.UPDATE_VC_LABEL}
/>
<LanguageSetting />
<Revoke label={t('revokeLabel')} />
<ListItem bottomDivider disabled={!controller.canUseBiometrics}>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileLabel}>{t('bioUnlock')}</Text>
</ListItem.Title>
</ListItem.Content>
<Switch
value={controller.isBiometricUnlockEnabled}
onValueChange={controller.useBiometrics}
color={Theme.Colors.profileValue}
<ScrollView>
<Column
fill
padding="24 0"
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<MessageOverlay
isVisible={controller.alertMsg != ''}
onBackdropPress={controller.hideAlert}
title={controller.alertMsg}
/>
</ListItem>
<ListItem bottomDivider disabled>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileAuthFactorUnlock}>
{t('authFactorUnlock')}
<EditableListItem
label={t('name')}
value={controller.name}
onEdit={controller.UPDATE_NAME}
Icon="user"
/>
<EditableListItem
label={t('vcLabel')}
value={controller.vcLabel.singular}
onEdit={controller.UPDATE_VC_LABEL}
Icon="star"
/>
<LanguageSetting />
<Revoke label={t('revokeLabel')} Icon="rotate-left" />
<ListItem bottomDivider disabled={!controller.canUseBiometrics}>
<Icon
name="fingerprint"
type="fontawesome"
size={25}
style={Theme.Styles.profileIconBg}
color={Theme.Colors.Icon}
/>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileLabel}>{t('bioUnlock')}</Text>
</ListItem.Title>
</ListItem.Content>
<Switch
value={controller.isBiometricUnlockEnabled}
onValueChange={controller.useBiometrics}
color={Theme.Colors.profileValue}
/>
</ListItem>
<ListItem bottomDivider disabled>
<Icon
name="unlock"
size={20}
type="antdesign"
style={Theme.Styles.profileIconBg}
color={Theme.Colors.Icon}
/>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileAuthFactorUnlock}>
{t('authFactorUnlock')}
</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileLabel}>
{isBLEEnabled ? t('useBle') : t('useGoogleNearby')}
</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<Credits label={t('credits')} color={Theme.Colors.profileLabel} />
<ListItem bottomDivider onPress={controller.LOGOUT}>
<Icon
name="logout"
type="fontawesome"
size={20}
style={Theme.Styles.profileIconBg}
color={Theme.Colors.Icon}
/>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileLabel}>{t('logout')}</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<Text
weight="semibold"
margin="32 0 0 0"
align="center"
size="smaller"
color={Theme.Colors.profileVersion}>
{t('version')}: {getVersion()}
</Text>
{controller.backendInfo.application.name !== '' ? (
<View>
<Text
weight="semibold"
align="center"
size="smaller"
color={Theme.Colors.profileValue}>
{controller.backendInfo.application.name}:{' '}
{controller.backendInfo.application.version}
</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileLabel}>
{isBLEEnabled ? t('useBle') : t('useGoogleNearby')}
<Text
weight="semibold"
align="center"
size="smaller"
color={Theme.Colors.profileValue}>
MOSIP: {controller.backendInfo.config['mosip.host']}
</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<Credits label={t('credits')} color={Theme.Colors.profileLabel} />
<ListItem bottomDivider onPress={controller.LOGOUT}>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileLabel}>{t('logout')}</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<Text
weight="semibold"
margin="32 0 0 0"
align="center"
size="smaller"
color={Theme.Colors.profileVersion}>
{t('version')}: {getVersion()}
</Text>
{controller.backendInfo.application.name !== '' ? (
<View>
<Text
weight="semibold"
align="center"
size="smaller"
color={Theme.Colors.profileValue}>
{controller.backendInfo.application.name}:{' '}
{controller.backendInfo.application.version}
</Text>
<Text
weight="semibold"
align="center"
size="smaller"
color={Theme.Colors.profileValue}>
MOSIP: {controller.backendInfo.config['mosip.host']}
</Text>
</View>
) : null}
</Column>
</View>
) : null}
</Column>
</ScrollView>
);
};

View File

@@ -15,6 +15,13 @@ export const Revoke: React.FC<RevokeScreenProps> = (props) => {
return (
<ListItem bottomDivider onPress={() => controller.setAuthenticating(true)}>
<Icon
name={props.Icon}
type="font-awesome"
size={20}
style={Theme.Styles.profileIconBg}
color={Theme.Colors.Icon}
/>
<ListItem.Content>
<ListItem.Title>
<Text>{props.label}</Text>
@@ -151,4 +158,5 @@ export const Revoke: React.FC<RevokeScreenProps> = (props) => {
interface RevokeScreenProps {
label: string;
Icon: string;
}

View File

@@ -0,0 +1,91 @@
import React from 'react';
import { Button, Column, Text, Centered } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { useTranslation } from 'react-i18next';
import { VcItem } from '../../components/VcItem';
import { useQrLogin } from './QrLoginController';
import { QrLoginRef } from '../../machines/QrLoginMachine';
import { Icon } from 'react-native-elements';
import { Modal } from '../../components/ui/Modal';
export const MyBindedVcs: React.FC<MyBindedVcsProps> = (props) => {
const controller = useQrLogin(props);
const { t } = useTranslation('QrScreen');
return (
<Modal
isVisible={controller.isShowingVcList}
arrowLeft={<Icon name={''} />}
headerTitle={t('selectId')}
headerElevation={5}
onDismiss={() => {
controller.setQrLogin(false), controller.DISMISS();
}}>
<React.Fragment>
<Column fill style={{ display: props.isVisible ? 'flex' : 'none' }}>
<Column fill>
{controller.vcKeys.length > 0 && (
<React.Fragment>
<Column
fill
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Column padding="16 0" scroll>
<Column pX={14}>
{controller.vcKeys.length > 0 &&
controller.vcKeys.map((vcKey, index) => (
<VcItem
key={vcKey}
vcKey={vcKey}
margin="0 2 8 2"
onPress={controller.SELECT_VC_ITEM(index)}
showOnlyBindedVc
selectable
selected={index === controller.selectedIndex}
/>
))}
</Column>
</Column>
<Column
style={{
borderTopRightRadius: 27,
borderTopLeftRadius: 27,
}}
padding="16 24"
margin="2 0 0 0"
elevation={2}>
<Button
title={t('verify')}
margin="0 0 12 0"
styles={Theme.ButtonStyles.radius}
disabled={controller.selectedIndex == null}
onPress={controller.VERIFY}
/>
</Column>
</Column>
</React.Fragment>
)}
{controller.vcKeys.length === 0 && (
<React.Fragment>
<Centered fill>
<Text weight="semibold" margin="0 0 8 0">
{t('noBindedVc', { vcLabel: controller.vcLabel.plural })}
</Text>
</Centered>
<Button
type="solid"
title={t('back')}
onPress={controller.DISMISS}
/>
</React.Fragment>
)}
</Column>
</Column>
</React.Fragment>
</Modal>
);
};
interface MyBindedVcsProps {
isVisible: boolean;
service: QrLoginRef;
}

View File

@@ -0,0 +1,132 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Column, Row, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { useQrLogin } from './QrLoginController';
import { Image } from 'react-native';
import { Icon, ListItem, Switch } from 'react-native-elements';
import { Modal } from '../../components/ui/Modal';
import { QrLoginRef } from '../../machines/QrLoginMachine';
import { ScrollView } from 'react-native';
export const QrConsent: React.FC<QrConsentProps> = (props) => {
const { t } = useTranslation('QrScreen');
const controller = useQrLogin(props);
return (
<Modal
isVisible={controller.isRequestConsent}
arrowLeft={<Icon name={''} />}
headerTitle={t('consent')}
headerElevation={5}
onDismiss={props.onCancel}>
<Column
fill
align="space-between"
padding="0 24 0 24"
style={{ display: props.isVisible ? 'flex' : 'none' }}
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<ScrollView>
<Column
align="space-evenly"
crossAlign="center"
margin={'15 0 0 0'}
style={Theme.Styles.consentPageTop}
elevation={3}>
{controller.linkTransactionResponse && (
<Row margin={'0 0 0 38'} crossAlign="center">
<Icon name="mobile" type="font-awesome" size={60} />
<Text
color={'grey'}
weight="semibold"
style={Theme.TextStyles.small}>
{' '}
-----------------------{' '}
</Text>
<Image
source={
controller.logoUrl ? { uri: controller.logoUrl } : null
}
style={{ width: 60, height: 60 }}
/>
</Row>
)}
<Text
style={Theme.TextStyles.small}
weight="bold"
margin={'0 0 10 6'}>
{controller.clientName} {t('access')}
</Text>
</Column>
<Column scroll padding="10 0 0 0">
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Title>
<Text
color={Theme.Colors.profileLabel}
style={Theme.TextStyles.base}>
{t('Name and Picture')}
</Text>
</ListItem.Title>
</ListItem.Content>
<Switch value={true} color={Theme.Colors.ProfileIconBg} />
</ListItem>
{controller.claims.map((claim, index) => {
if (claim == 'name' || claim == 'picture') {
return null;
} else {
return (
<ListItem key={index} bottomDivider>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileLabel}>
{t(claim[0].toUpperCase() + claim.slice(1))}
</Text>
</ListItem.Title>
</ListItem.Content>
<Switch
value={controller.isShare[claim]}
onValueChange={() =>
controller.SELECT_CONSENT(
controller.isShare[claim],
claim
)
}
color={Theme.Colors.Icon}
/>
</ListItem>
);
}
})}
</Column>
</ScrollView>
<Column
margin={'0 -20 0 -20'}
style={Theme.Styles.bottomButtonsContainer}
elevation={5}>
<Button
margin={'6 10 0 10'}
styles={Theme.ButtonStyles.radius}
title={'Confirm'}
onPress={props.onConfirm}
/>
<Button
margin={'10 10 0 10'}
type="clear"
title={'Cancel'}
onPress={props.onCancel}
/>
</Column>
</Column>
</Modal>
);
};
interface QrConsentProps {
isVisible: boolean;
onConfirm: () => void;
onCancel: () => void;
service: QrLoginRef;
}

104
screens/QrLogin/QrLogin.tsx Normal file
View File

@@ -0,0 +1,104 @@
import React from 'react';
import { Button, Column, Row } from '../../components/ui';
import { useTranslation } from 'react-i18next';
import { useQrLogin } from './QrLoginController';
import { Modal } from '../../components/ui/Modal';
import { VerifyIdentityOverlay } from '../VerifyIdentityOverlay';
import { MessageOverlay } from '../../components/MessageOverlay';
import { MyBindedVcs } from './MyBindedVcs';
import { QrLoginWarning } from './QrLoginWarning';
import { QrLoginSuccess } from './QrLoginSuccessMessage';
import { QrConsent } from './QrConsent';
import { QrLoginRef } from '../../machines/QrLoginMachine';
import { Icon } from 'react-native-elements';
export const QrLogin: React.FC<QrLoginProps> = (props) => {
const controller = useQrLogin(props);
const { t } = useTranslation('QrScreen');
return (
<Modal
isVisible={props.isVisible}
onDismiss={controller.DISMISS}
headerTitle={t('title')}
headerRight={<Icon name={''} />}>
<Column fill>
<QrLoginWarning
isVisible={controller.isShowWarning}
onConfirm={controller.CONFIRM}
onCancel={controller.DISMISS}
service={props.service}
/>
<MessageOverlay
isVisible={
controller.isWaitingForData ||
controller.isLoadingMyVcs ||
controller.isLinkTransaction ||
controller.isSendingConsent
}
title={t('loading')}
progress
/>
<MessageOverlay
isVisible={controller.isShowingError}
title={controller.error}
onCancel={controller.DISMISS}
/>
<MyBindedVcs
isVisible={controller.isShowingVcList}
service={props.service}
/>
<VerifyIdentityOverlay
isVisible={controller.isVerifyingIdentity}
vc={controller.selectedVc}
onCancel={controller.CANCEL}
onFaceValid={controller.FACE_VALID}
onFaceInvalid={controller.FACE_INVALID}
/>
<MessageOverlay
isVisible={controller.isInvalidIdentity}
title={t('VerifyIdentityOverlay:errors.invalidIdentity.title')}
message={t('VerifyIdentityOverlay:errors.invalidIdentity.message')}
onBackdropPress={controller.DISMISS}>
<Row>
<Button
fill
type="clear"
title={t('common:cancel')}
onPress={controller.DISMISS}
margin={[0, 8, 0, 0]}
/>
<Button
fill
title={t('common:tryAgain')}
onPress={controller.RETRY_VERIFICATION}
/>
</Row>
</MessageOverlay>
<QrConsent
isVisible={controller.isRequestConsent}
onConfirm={controller.CONFIRM}
onCancel={controller.CANCEL}
service={props.service}
/>
<QrLoginSuccess
isVisible={controller.isVerifyingSuccesful}
onPress={controller.CONFIRM}
service={props.service}
/>
</Column>
</Modal>
);
};
export interface QrLoginProps {
isVisible: boolean;
service: QrLoginRef;
}

View File

@@ -0,0 +1,94 @@
import { useSelector } from '@xstate/react';
import { useContext, useState } from 'react';
import { ActorRefFrom } from 'xstate';
import {
QrLoginEvents,
selectClientName,
selectErrorMessage,
selectIsInvalidIdentity,
selectIsisVerifyingIdentity,
selectIsLinkTransaction,
selectIsloadMyVcs,
selectIsRequestConsent,
selectIsSendingConsent,
selectIsSharing,
selectIsShowError,
selectIsShowingVcList,
selectIsShowWarning,
selectIsVerifyingSuccesful,
selectIsWaitingForData,
selectLinkTransactionResponse,
selectLogoUrl,
selectSelectedVc,
selectVoluntaryClaims,
} from '../../machines/QrLoginMachine';
import { selectVcLabel } from '../../machines/settings';
import { selectBindedVcs } from '../../machines/vc';
import { vcItemMachine } from '../../machines/vcItem';
import { GlobalContext } from '../../shared/GlobalContext';
import { VC } from '../../types/vc';
import { QrLoginProps } from './QrLogin';
export function useQrLogin({ service }: QrLoginProps) {
const { appService } = useContext(GlobalContext);
const settingsService = appService.children.get('settings');
const vcService = appService.children.get('vc');
const [selectedIndex, setSelectedIndex] = useState<number>(null);
const SELECT_VC = (vc: VC) => service.send(QrLoginEvents.SELECT_VC(vc));
const SELECT_CONSENT = (value: boolean, claim: string) => {
service.send(QrLoginEvents.TOGGLE_CONSENT_CLAIM(value, claim));
};
const isShare = useSelector(service, selectIsSharing);
return {
SELECT_VC_ITEM:
(index: number) => (vcRef: ActorRefFrom<typeof vcItemMachine>) => {
setSelectedIndex(index);
const vcData = vcRef.getSnapshot().context;
SELECT_VC(vcData);
},
vcKeys: useSelector(vcService, selectBindedVcs),
selectedVc: useSelector(service, selectSelectedVc),
linkTransactionResponse: useSelector(
service,
selectLinkTransactionResponse
),
logoUrl: useSelector(service, selectLogoUrl),
claims: useSelector(service, selectVoluntaryClaims),
clientName: useSelector(service, selectClientName),
error: useSelector(service, selectErrorMessage),
isShare,
selectedIndex,
SELECT_VC,
SELECT_CONSENT,
isWaitingForData: useSelector(service, selectIsWaitingForData),
isShowWarning: useSelector(service, selectIsShowWarning),
isShowingVcList: useSelector(service, selectIsShowingVcList),
isLinkTransaction: useSelector(service, selectIsLinkTransaction),
isLoadingMyVcs: useSelector(service, selectIsloadMyVcs),
isRequestConsent: useSelector(service, selectIsRequestConsent),
isShowingError: useSelector(service, selectIsShowError),
isSendingConsent: useSelector(service, selectIsSendingConsent),
isVerifyingIdentity: useSelector(service, selectIsisVerifyingIdentity),
isInvalidIdentity: useSelector(service, selectIsInvalidIdentity),
isVerifyingSuccesful: useSelector(service, selectIsVerifyingSuccesful),
vcLabel: useSelector(settingsService, selectVcLabel),
DISMISS: () => service.send(QrLoginEvents.DISMISS()),
SCANNING_DONE: (qrCode: string) =>
service.send(QrLoginEvents.SCANNING_DONE(qrCode)),
CONFIRM: () => service.send(QrLoginEvents.CONFIRM()),
VERIFY: () => service.send(QrLoginEvents.VERIFY()),
CANCEL: () => service.send(QrLoginEvents.CANCEL()),
FACE_VALID: () => service.send(QrLoginEvents.FACE_VALID()),
FACE_INVALID: () => service.send(QrLoginEvents.FACE_INVALID()),
RETRY_VERIFICATION: () => service.send(QrLoginEvents.RETRY_VERIFICATION()),
};
}

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Image } from 'react-native';
import { Icon } from 'react-native-elements';
import { Modal } from '../../components/ui/Modal';
import { Centered, Button, Text, Column } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { useQrLogin } from './QrLoginController';
import { QrLoginRef } from '../../machines/QrLoginMachine';
export const QrLoginSuccess: React.FC<QrLoginSuccessProps> = (props) => {
const { t } = useTranslation('QrScreen');
const controller = useQrLogin(props);
return (
<Modal
isVisible={controller.isVerifyingSuccesful}
arrowLeft={<Icon name={''} />}
headerTitle={t('status')}
headerElevation={5}
onDismiss={controller.DISMISS}>
<Column
fill
align="space-between"
style={{ display: props.isVisible ? 'flex' : 'none' }}>
<Centered padding={'60 25 0 25'} margin={'60 0'}>
<Image
source={controller.logoUrl ? { uri: controller.logoUrl } : null}
style={{ width: 60, height: 60 }}
/>
<Text
style={Theme.Styles.detailsText}
weight="semibold"
margin="20 0 0 0"
align="center">
{t('successMessage')}
{controller.clientName}
</Text>
</Centered>
<Column
style={{ borderTopRightRadius: 27, borderTopLeftRadius: 27 }}
padding="10 24"
margin="2 0 0 0"
elevation={2}>
<Button
title={t('okay')}
margin="0 0 12 0"
styles={Theme.ButtonStyles.radius}
onPress={props.onPress}
/>
</Column>
</Column>
</Modal>
);
};
interface QrLoginSuccessProps {
isVisible: boolean;
onPress: () => void;
service: QrLoginRef;
}

View File

@@ -0,0 +1,98 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Column, Row } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { Text } from '../../components/ui';
import { Icon } from 'react-native-elements';
import { useQrLogin } from './QrLoginController';
import { Modal } from '../../components/ui/Modal';
import { Dimensions, Image } from 'react-native';
import { QrLoginRef } from '../../machines/QrLoginMachine';
export const QrLoginWarning: React.FC<QrLoginWarningProps> = (props) => {
const { t } = useTranslation('QrScreen');
const controller = useQrLogin(props);
return (
<Modal
isVisible={controller.isShowWarning}
arrowLeft={<Icon name={''} />}
headerTitle={t('confirmation')}
headerElevation={5}
onDismiss={props.onCancel}>
<Column
fill
align="space-between"
padding={'24 0 0 0'}
style={{ display: props.isVisible ? 'flex' : 'none' }}
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Column align="space-evenly" crossAlign="center" padding={'16 16 0 16'}>
<Image source={Theme.DomainWarningLogo} resizeMethod="auto" />
<Text
align="center"
style={Theme.Styles.detailsText}
margin="21 15 15 15">
{t('domainWarning')}
</Text>
<Text
align="center"
margin={'30 15 0 10'}
weight="regular"
color={Theme.Colors.Icon}
style={Theme.Styles.urlContainer}>
{controller.logoUrl}
</Text>
</Column>
<Column padding={'0 14 14 14'}>
<Text
align="center"
weight="semibold"
style={Theme.TextStyles.smaller}
margin="0 15 15 15">
{t('checkDomain')}
</Text>
<Row
align="space-evenly"
padding={'9'}
crossAlign="center"
style={Theme.Styles.lockDomainContainer}>
<Icon name="lock" size={20} color={'grey'} type="font-awesome" />
<Text
weight="semibold"
style={Theme.TextStyles.smaller}
color={Theme.Colors.GrayIcon}>
{t('domainHead')}
</Text>
</Row>
</Column>
<Column
padding={'10'}
width={Dimensions.get('screen').width * 0.98}
style={Theme.Styles.bottomButtonsContainer}>
<Button
margin={'2 12 0 12'}
title={t('confirm')}
onPress={props.onConfirm}
styles={Theme.ButtonStyles.radius}
/>
<Button
margin={'16 12 0 12'}
type="clear"
title={t('common:cancel')}
onPress={props.onCancel}
styles={Theme.ButtonStyles.clear}
/>
</Column>
</Column>
</Modal>
);
};
interface QrLoginWarningProps {
isVisible: boolean;
onConfirm: () => void;
onCancel: () => void;
service: QrLoginRef;
}

View File

@@ -1,6 +1,6 @@
{
"header": "{{vcLabel}} details",
"acceptRequest": "Accept request and receive {{vcLabel}}",
"acceptRequestAndVerify": "Accept request and verify",
"reject": "Reject"
"save": "Save {{vcLabel}}",
"verifyAndSave": "Verify and save",
"discard": "Discard"
}

View File

@@ -24,19 +24,19 @@ export const ReceiveVcScreen: React.FC = () => {
<Text weight="semibold" margin="24 24 0 24">
{t('header', { vcLabel: controller.vcLabel.singular })}
</Text>
<VcDetails vc={controller.incomingVc} />
<VcDetails vc={controller.incomingVc} isBindingPending={false} />
</Column>
<Column padding="0 24" margin="32 0 0 0">
{controller.incomingVc.shouldVerifyPresence ? (
<Button
type="outline"
title={t('acceptRequestAndVerify')}
title={t('verifyAndSave')}
margin="12 0 12 0"
onPress={controller.ACCEPT_AND_VERIFY}
/>
) : (
<Button
title={t('acceptRequest', {
title={t('save', {
vcLabel: controller.vcLabel.singular,
})}
margin="12 0 12 0"
@@ -45,7 +45,7 @@ export const ReceiveVcScreen: React.FC = () => {
)}
<Button
type="clear"
title={t('reject')}
title={t('discard')}
margin="0 0 12 0"
onPress={controller.REJECT}
/>

View File

@@ -10,7 +10,7 @@
},
"rejected": {
"title": "Notice",
"message": "You rejected {{sender}}'s {{vcLabel}}"
"message": "You discarded {{sender}}'s {{vcLabel}}"
},
"disconnected": {
"title": "Disconnected",
@@ -22,8 +22,11 @@
"timeoutHint": "It's taking too long to exchange device info..."
},
"connected": {
"message": "Connected to device. Waiting for {{vcLabel}}...",
"message": "Connected to the device. Waiting for {{vcLabel}}...",
"timeoutHint": "No data received yet. Is sending device still connected?"
},
"offline": {
"message": "Please connect to the internet to enable Online sharing mode"
}
},
"online": "Online",

View File

@@ -15,6 +15,7 @@ import {
selectIsWaitingForVcTimeout,
selectIsCheckingBluetoothService,
selectIsCancelling,
selectIsOffline,
} from '../../machines/request';
import { selectVcLabel } from '../../machines/settings';
import { GlobalContext } from '../../shared/GlobalContext';
@@ -51,6 +52,7 @@ export function useRequestScreen() {
requestService,
selectIsWaitingForVcTimeout
);
const isOffline = useSelector(requestService, selectIsOffline);
let statusMessage = '';
let statusHint = '';
@@ -59,6 +61,8 @@ export function useRequestScreen() {
statusMessage = t('status.waitingConnection');
} else if (isExchangingDeviceInfo) {
statusMessage = t('status.exchangingDeviceInfo.message');
} else if (isOffline) {
statusMessage = t('status.offline.message');
} else if (isExchangingDeviceInfoTimeout) {
statusMessage = t('status.exchangingDeviceInfo.message');
statusHint = t('status.exchangingDeviceInfo.timeoutHint');

View File

@@ -10,6 +10,7 @@ import { useScanLayout } from './ScanLayoutController';
import { LanguageSelector } from '../../components/LanguageSelector';
import { ScanScreen } from './ScanScreen';
import { I18nManager, Platform } from 'react-native';
import { Message } from '../../components/Message';
const ScanStack = createNativeStackNavigator();
@@ -68,6 +69,14 @@ export const ScanLayout: React.FC = () => {
progress={!controller.isInvalid}
onBackdropPress={controller.DISMISS_INVALID}
/>
{controller.isDisconnected && (
<Message
title={t('RequestScreen:status.disconnected.title')}
message={t('RequestScreen:status.disconnected.message')}
onBackdropPress={controller.DISMISS}
/>
)}
</React.Fragment>
);
};

View File

@@ -15,6 +15,10 @@ import {
selectIsDone,
selectIsReviewing,
selectIsScanning,
selectIsQrLoginDone,
selectIsOffline,
selectIsSent,
selectIsDisconnected,
} from '../../machines/scan';
import { selectVcLabel } from '../../machines/settings';
import { MainBottomTabParamList } from '../../routes/main';
@@ -29,6 +33,8 @@ type ScanLayoutNavigation = NavigationProp<
ScanStackParamList & MainBottomTabParamList
>;
// TODO: refactor
// eslint-disable-next-line sonarjs/cognitive-complexity
export function useScanLayout() {
const { t } = useTranslation('ScanScreen');
const { appService } = useContext(GlobalContext);
@@ -63,6 +69,10 @@ export function useScanLayout() {
scanService,
selectIsExchangingDeviceInfoTimeout
);
const isOffline = useSelector(scanService, selectIsOffline);
const isSent = useSelector(scanService, selectIsSent);
const vcLabel = useSelector(settingsService, selectVcLabel);
const onCancel = () => scanService.send(ScanEvents.CANCEL());
let statusOverlay: Pick<
@@ -89,10 +99,19 @@ export function useScanLayout() {
hint: t('status.exchangingDeviceInfoTimeout'),
onCancel,
};
} else if (isSent) {
statusOverlay = {
message: t('status.sent', { vcLabel: vcLabel.singular }),
hint: t('status.sentHint', { vcLabel: vcLabel.singular }),
};
} else if (isInvalid) {
statusOverlay = {
message: t('status.invalid'),
};
} else if (isOffline) {
statusOverlay = {
message: t('status.offline'),
};
}
useEffect(() => {
@@ -113,6 +132,8 @@ export function useScanLayout() {
const isDone = useSelector(scanService, selectIsDone);
const isReviewing = useSelector(scanService, selectIsReviewing);
const isScanning = useSelector(scanService, selectIsScanning);
const isQrLoginDone = useSelector(scanService, selectIsQrLoginDone);
useEffect(() => {
if (isDone) {
navigation.navigate('Home', { activeTab: 0 });
@@ -120,16 +141,20 @@ export function useScanLayout() {
navigation.navigate('SendVcScreen');
} else if (isScanning) {
navigation.navigate('ScanScreen');
} else if (isQrLoginDone) {
navigation.navigate('Home', { activeTab: 2 });
}
}, [isDone, isReviewing, isScanning]);
}, [isDone, isReviewing, isScanning, isQrLoginDone]);
return {
vcLabel: useSelector(settingsService, selectVcLabel),
vcLabel,
isInvalid,
isDone,
isDisconnected: useSelector(scanService, selectIsDisconnected),
statusOverlay,
DISMISS: () => scanService.send(ScanEvents.DISMISS()),
DISMISS_INVALID: () =>
isInvalid ? scanService.send(ScanEvents.DISMISS()) : null,
};

View File

@@ -14,9 +14,12 @@
},
"status": {
"connecting": "Connecting...",
"connectingTimeout": "It's taking a while to establish connection. Is the other device open for connections?",
"connectingTimeout": "It's taking a while to establish the connection. Is the other device open for connections?",
"exchangingDeviceInfo": "Exchanging device info...",
"exchangingDeviceInfoTimeout": "It's taking a while to exchange device info. You may have to reconnect.",
"invalid": "Invalid QR Code"
"invalid": "Invalid QR Code",
"offline": "Please connect to the internet to scan QR codes using Online sharing mode",
"sent": "{{ vcLabel }} has been sent...",
"sentHint": "Waiting for receiver to save or discard your {{ vcLabel }}"
}
}

View File

@@ -1,29 +1,15 @@
import React from 'react';
import { TFunction, useTranslation } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import { MessageOverlay } from '../../components/MessageOverlay';
import { QrScanner } from '../../components/QrScanner';
import { Button, Centered, Column, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { QrLogin } from '../QrLogin/QrLogin';
import { useScanScreen } from './ScanScreenController';
export const ScanScreen: React.FC = () => {
const { t } = useTranslation('ScanScreen');
const controller = useScanScreen();
const props: ScanScreenProps = { t, controller };
const BluetoothPrompt: React.FC<ScanScreenProps> = ({ t, controller }) => {
return (
<Centered fill>
<Text color={Theme.Colors.errorMessage} align="center">
{t('bluetoothDenied', { vcLabel: controller.vcLabel.singular })}
</Text>
<Button
margin={[32, 0, 0, 0]}
title={t('gotoSettings')}
onPress={controller.GOTO_SETTINGS}
/>
</Centered>
);
};
return (
<Column
@@ -36,8 +22,6 @@ export const ScanScreen: React.FC = () => {
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Text align="center">{t('header')}</Text>
{controller.isBluetoothDenied && <BluetoothPrompt {...props} />}
{controller.isLocationDisabled || controller.isLocationDenied ? (
<Column align="space-between">
<Text
@@ -55,7 +39,7 @@ export const ScanScreen: React.FC = () => {
{!controller.isEmpty ? (
controller.isScanning && (
<Column crossAlign="center">
<Column crossAlign="center" margin="0 0 0 -6">
<QrScanner onQrFound={controller.SCAN} />
</Column>
)
@@ -64,12 +48,18 @@ export const ScanScreen: React.FC = () => {
{t('noShareableVcs', { vcLabel: controller.vcLabel.plural })}
</Text>
)}
{controller.isQrLogin && (
<QrLogin
isVisible={controller.isQrLogin}
service={controller.isQrRef}
/>
)}
<MessageOverlay
isVisible={controller.isQrLoginstoring}
title={t('loading')}
progress
/>
</Centered>
</Column>
);
};
interface ScanScreenProps {
t: TFunction;
controller: ReturnType<typeof useScanScreen>;
}

View File

@@ -1,19 +1,19 @@
import { useSelector } from '@xstate/react';
import { useContext, useEffect } from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { selectIsActive, selectIsFocused } from '../../machines/app';
import {
ScanEvents,
selectIsLocationDisabled,
selectIsLocationDenied,
selectIsScanning,
selectIsBluetoothDenied,
selectIsShowQrLogin,
selectQrLoginRef,
selectIsQrLoginStoring,
} from '../../machines/scan';
import { selectVcLabel } from '../../machines/settings';
import { selectShareableVcs } from '../../machines/vc';
import { GlobalContext } from '../../shared/GlobalContext';
import BluetoothStateManager from 'react-native-bluetooth-state-manager';
export function useScanScreen() {
const { t } = useTranslation('ScanScreen');
@@ -23,17 +23,6 @@ export function useScanScreen() {
const vcService = appService.children.get('vc');
const shareableVcs = useSelector(vcService, selectShareableVcs);
const isActive = useSelector(appService, selectIsActive);
const isFocused = useSelector(appService, selectIsFocused);
const isBluetoothDenied = useSelector(scanService, selectIsBluetoothDenied);
useEffect(() => {
BluetoothStateManager.getState().then((bluetoothState) => {
if (bluetoothState === 'PoweredOn' && isBluetoothDenied) {
scanService.send(ScanEvents.SCREEN_FOCUS());
}
});
}, [isFocused, isActive]);
const isLocationDisabled = useSelector(scanService, selectIsLocationDisabled);
const isLocationDenied = useSelector(scanService, selectIsLocationDenied);
@@ -52,14 +41,14 @@ export function useScanScreen() {
locationError,
vcLabel: useSelector(settingsService, selectVcLabel),
isBluetoothDenied,
isEmpty: !shareableVcs.length,
isLocationDisabled,
isLocationDenied,
isScanning: useSelector(scanService, selectIsScanning),
isQrLogin: useSelector(scanService, selectIsShowQrLogin),
isQrLoginstoring: useSelector(scanService, selectIsQrLoginStoring),
isQrRef: useSelector(scanService, selectQrLoginRef),
LOCATION_REQUEST: () => scanService.send(ScanEvents.LOCATION_REQUEST()),
SCAN: (qrCode: string) => scanService.send(ScanEvents.SCAN(qrCode)),
GOTO_SETTINGS: () => scanService.send(ScanEvents.GOTO_SETTINGS()),
};
}

View File

@@ -15,7 +15,7 @@
},
"rejected": {
"title": "Notice",
"message": "Your {{vcLabel}} was rejected by {{receiver}}"
"message": "Your {{vcLabel}} was discarded by {{receiver}}"
}
},
"consentToPhotoVerification": "I give consent to have my photo taken for authentication"

View File

@@ -10,6 +10,7 @@ import { useSendVcScreen } from './SendVcScreenController';
import { VerifyIdentityOverlay } from '../VerifyIdentityOverlay';
import { VcItem } from '../../components/VcItem';
import { SingleVcItem } from '../../components/SingleVcItem';
import { I18nManager } from 'react-native';
export const SendVcScreen: React.FC = () => {
const { t } = useTranslation('SendVcScreen');
@@ -32,8 +33,10 @@ export const SendVcScreen: React.FC = () => {
value={controller.reason ? controller.reason : ''}
placeholder={!controller.reason ? reasonLabel : ''}
label={controller.reason ? reasonLabel : ''}
labelStyle={{ textAlign: 'left' }}
onChangeText={controller.UPDATE_REASON}
containerStyle={{ marginBottom: 24 }}
inputStyle={{ textAlign: I18nManager.isRTL ? 'right' : 'left' }}
/>
</Column>
<Column>

View File

@@ -5,8 +5,8 @@
"errors": {
"invalidIdentity": {
"title": "Unable to verify identity",
"message": "An error occured and we couldn't scan your portrait. Try again, make sure your face is visible, devoid of any accessories.",
"messageNoRetry": "An error occured and we couldn't scan your portrait."
"message": "An error occurred and we couldn't scan your portrait. Try again, make sure your face is visible, devoid of any accessories.",
"messageNoRetry": "An error occurred and we couldn't scan your portrait."
}
}
}

View File

@@ -5,15 +5,18 @@ import { FaceScanner } from '../components/FaceScanner';
import { Column, Row } from '../components/ui';
import { Theme } from '../components/ui/styleUtils';
import { VC } from '../types/vc';
import { Modal } from '../components/ui/Modal';
import { t } from 'i18next';
export const VerifyIdentityOverlay: React.FC<VerifyIdentityOverlayProps> = (
props
) => {
return (
<Overlay isVisible={props.isVisible}>
<Row align="flex-end" padding="16">
<Icon name="close" color={Theme.Colors.Icon} onPress={props.onCancel} />
</Row>
<Modal
isVisible={props.isVisible}
arrowLeft={<Icon name={''} />}
headerTitle={t('faceAuth')}
onDismiss={props.onCancel}>
<Column
fill
style={Theme.VerifyIdentityOverlayStyles.content}
@@ -26,7 +29,7 @@ export const VerifyIdentityOverlay: React.FC<VerifyIdentityOverlayProps> = (
/>
)}
</Column>
</Overlay>
</Modal>
);
};

View File

@@ -11,6 +11,7 @@ import { settingsMachine } from '../machines/settings';
import { storeMachine } from '../machines/store';
import { vcMachine } from '../machines/vc';
import { revokeVidsMachine } from '../machines/revoke';
import { qrLoginMachine } from '../machines/QrLoginMachine';
export const GlobalContext = createContext({} as GlobalServices);

View File

@@ -8,6 +8,6 @@
"errors": {
"missingPermission": "This app uses the camera to scan the QR code of another device."
},
"allowAccess": "Allow access to camera"
"allowAccess": "Allow access to the camera"
}
}

View File

@@ -1,10 +1,23 @@
import { request } from '../request';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default async function getAllProperties() {
const COMMON_PROPS_KEY: string =
'CommonPropsKey-' + '6964d04a-9268-11ed-a1eb-0242ac120002';
export default async function getAllConfigurations() {
try {
const resp = await request('GET', '/allProperties');
return resp.response;
var response = await AsyncStorage.getItem(COMMON_PROPS_KEY);
if (response) {
return JSON.parse(response);
} else {
const resp = await request('GET', '/allProperties');
const injiProps = resp.response;
const injiPropsString = JSON.stringify(injiProps);
await AsyncStorage.setItem(COMMON_PROPS_KEY, injiPropsString);
return injiProps;
}
} catch (error) {
console.log(error);
throw error;
}
}

View File

@@ -10,6 +10,8 @@ export const MY_VCS_STORE_KEY = 'myVCs';
export const RECEIVED_VCS_STORE_KEY = 'receivedVCs';
export const MY_LOGIN_STORE_KEY = 'myLogins';
export const VC_ITEM_STORE_KEY = (vc: Partial<VC>) =>
`vc:${vc.idType}:${vc.id}:${vc.requestId}`;

View File

@@ -0,0 +1,68 @@
import { KeyPair, RSA } from 'react-native-rsa-native';
import forge from 'node-forge';
import getAllConfigurations from '../commonprops/commonProps';
export function generateKeys(): Promise<KeyPair> {
return Promise.resolve(RSA.generateKeys(4096));
}
export async function getJwt(
privateKey: string,
individualId: string,
keyId: string,
thumbprint: string
) {
var token: string = null;
try {
var iat = Math.floor(new Date().getTime() / 1000);
var exp = Math.floor(new Date().getTime() / 1000) + 18000;
var config = await getAllConfigurations();
const key = forge.pki.privateKeyFromPem(privateKey);
const md = forge.md.sha256.create();
const header = {
'alg': 'RS256',
//'kid': keyId,
'x5t#S256': thumbprint,
};
var myJSON =
'{"iss": "' +
config.issuer +
'", "aud": "' +
config.audience +
'", "sub": "' +
individualId +
'", "iat": ' +
iat +
', "exp": ' +
exp +
'}';
var payload = JSON.parse(myJSON);
const strHeader = JSON.stringify(header);
const strPayload = JSON.stringify(payload);
const header64 = encodeB64(strHeader);
const payload64 = encodeB64(strPayload);
const preHash = header64 + '.' + payload64;
md.update(preHash, 'utf8');
const signature = key.sign(md);
const signature64 = encodeB64(signature);
var token: string = header64 + '.' + payload64 + '.' + signature64;
return token;
} catch (e) {
console.log(e);
throw e;
}
}
function encodeB64(str: string) {
const encodedB64 = forge.util.encode64(str);
return encodedB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
export interface WalletBindingResponse {
walletBindingId: string;
keyId: string;
thumbprint: string;
expireDateTime: string;
}

View File

@@ -0,0 +1,19 @@
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
const bindingCertificate = '-bindingCertificate';
export async function savePrivateKey(id: string, privateKey: string) {
var result = await RNSecureKeyStore.set(id, privateKey, {
accessible: ACCESSIBLE.ALWAYS_THIS_DEVICE_ONLY,
});
return result;
}
export async function getPrivateKey(id: string) {
var result = await RNSecureKeyStore.get(id);
return result;
}
export function getBindingCertificateConstant(id: string) {
return id + bindingCertificate;
}

View File

@@ -24,6 +24,7 @@ export function onlineSubscribe<T extends SmartshareEventType>(
}
const response = SmartshareEvent.fromString<T>(foundMessage);
if (response.type === 'disconnect') {
GoogleNearbyMessages.unsubscribe();
disconectCallback(response.data);
} else if (response.type === eventType) {
!config?.keepAlive && GoogleNearbyMessages.unsubscribe();
@@ -35,7 +36,12 @@ export function onlineSubscribe<T extends SmartshareEventType>(
console.log('\n[request] MESSAGE_LOST', lostMessage.slice(0, 100));
}
}
);
).catch((error: Error) => {
if (error.message.includes('existing callback is already subscribed')) {
console.log('Existing callback found. Unsubscribing then retrying...');
return onlineSubscribe(eventType, callback, disconectCallback, config);
}
});
}
export function onlineSend(event: SmartshareEvents) {
@@ -114,7 +120,7 @@ export interface SendVcEvent {
};
}
export type SendVcStatus = 'ACCEPTED' | 'REJECTED';
export type SendVcStatus = 'ACCEPTED' | 'REJECTED' | 'RECEIVED';
export interface SendVcResponseEvent {
type: 'send-vc:response';
data: SendVcStatus | number;

View File

@@ -1,3 +1,5 @@
import { WalletBindingResponse } from '../shared/cryptoutil/cryptoUtil';
export interface VC {
id: string;
idType: VcIdType;
@@ -12,6 +14,7 @@ export interface VC {
locked: boolean;
reason?: VCSharingReason[];
shouldVerifyPresence?: boolean;
walletBindingResponse?: WalletBindingResponse;
}
export interface VCSharingReason {
@@ -98,3 +101,14 @@ export interface LocalizedField {
language: string;
value: string;
}
export interface linkTransactionResponse {
authFactors: Object[];
authorizeScopes: null;
clientName: string;
configs: {};
essentialClaims: string[];
linkTransactionId: string;
logoUrl: string;
voluntaryClaims: string[];
}