mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-06 20:23:52 -05:00
[INJIMOB-1433,528] - Add passive liveness detection with blink detection (#1474)
* [INJIMOB-528] add liveness support for face verification Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-528] add and comment blink detection Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-528] update locales and remove blink detection Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-1433] add blinking and increase threshold if blinking is detected Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-1433] sync package lock json Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-1433] update node version to 18 for android build Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-1433] refactor Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-1433] refactor components Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-1433] use the default version of package resolved file Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-1433] refactor and add new env for liveness in workflow Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-1433] remove new env and unused code Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-1433] add new env for liveness and combine build descriptiona and build name Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> * [INJIMOB-528] update package lock & pbxproj files Signed-off-by: KiruthikaJeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com> * [INJIMOB-1433] add test id for elements Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> --------- Signed-off-by: adityankannan-tw <adityan410pm@gmail.com> Signed-off-by: KiruthikaJeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com> Co-authored-by: adityankannan-tw <adityan410pm@gmail.com> Co-authored-by: KiruthikaJeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com>
This commit is contained in:
3
.env
3
.env
@@ -16,6 +16,9 @@ CREDENTIAL_REGISTRY_EDIT=true
|
||||
|
||||
DEBUG_MODE=false
|
||||
|
||||
#Toggle liveness detection
|
||||
LIVENESS_DETECTION=true
|
||||
|
||||
#supported languages( en, fil, ar, hi, kn, ta)
|
||||
APPLICATION_LANGUAGE=en
|
||||
|
||||
|
||||
24
.github/workflows/internal-build.yml
vendored
24
.github/workflows/internal-build.yml
vendored
@@ -24,7 +24,7 @@ on:
|
||||
buildName:
|
||||
description: 'Build App For'
|
||||
required: true
|
||||
default: 'Sprint-x/Collab/release-x.x.x'
|
||||
default: 'Sprint-x/QA-Inji/Release-x.x.x'
|
||||
type: string
|
||||
mimotoBackendServiceUrl:
|
||||
description: 'Mimoto backend service URL'
|
||||
@@ -66,11 +66,6 @@ on:
|
||||
options:
|
||||
- orange
|
||||
- purple
|
||||
buildDescription:
|
||||
description: 'What to test'
|
||||
required: true
|
||||
default: 'QA-Triple environment'
|
||||
type: string
|
||||
allow_env_edit:
|
||||
description: 'Edit ENV'
|
||||
required: true
|
||||
@@ -79,6 +74,14 @@ on:
|
||||
options:
|
||||
- false
|
||||
- true
|
||||
liveness_detection:
|
||||
description: 'Detect Liveness'
|
||||
required: true
|
||||
default: 'true'
|
||||
type: choice
|
||||
options:
|
||||
- false
|
||||
- true
|
||||
|
||||
jobs:
|
||||
set-client-id:
|
||||
@@ -106,8 +109,9 @@ jobs:
|
||||
MIMOTO_HOST: ${{ inputs.mimotoBackendServiceUrl }}
|
||||
ESIGNET_HOST: ${{ inputs.esignetBackendServiceUrl }}
|
||||
APPLICATION_THEME: ${{ inputs.theme }}
|
||||
BUILD_DESCRIPTION: ${{ inputs.buildDescription }}
|
||||
BUILD_DESCRIPTION: ${{ inputs.buildName }}
|
||||
ALLOW_ENV_EDIT: ${{ inputs.allow_env_edit }}
|
||||
LIVENESS_DETECTION: ${{ inputs.liveness_detection }}
|
||||
APP_FLAVOR: ${{ inputs.injiFlavor }}
|
||||
SERVICE_LOCATION: '.'
|
||||
ANDROID_SERVICE_LOCATION: 'android'
|
||||
@@ -132,8 +136,9 @@ jobs:
|
||||
MIMOTO_HOST: ${{ inputs.mimotoBackendServiceUrl }}
|
||||
ESIGNET_HOST: ${{ inputs.esignetBackendServiceUrl }}
|
||||
APPLICATION_THEME: ${{ inputs.theme }}
|
||||
BUILD_DESCRIPTION: ${{ inputs.buildDescription }}
|
||||
BUILD_DESCRIPTION: ${{ inputs.buildName }}
|
||||
ALLOW_ENV_EDIT: ${{ inputs.allow_env_edit }}
|
||||
LIVENESS_DETECTION: ${{ inputs.liveness_detection }}
|
||||
APP_FLAVOR: ${{ inputs.injiFlavor }}
|
||||
SERVICE_LOCATION: '.'
|
||||
ANDROID_SERVICE_LOCATION: 'android'
|
||||
@@ -156,8 +161,9 @@ jobs:
|
||||
MIMOTO_HOST: ${{ inputs.mimotoBackendServiceUrl }}
|
||||
ESIGNET_HOST: ${{ inputs.esignetBackendServiceUrl }}
|
||||
APPLICATION_THEME: ${{ inputs.theme }}
|
||||
TESTFLIGHT_BETA_APP_DESCRIPTION: ${{ inputs.buildDescription }}
|
||||
TESTFLIGHT_BETA_APP_DESCRIPTION: ${{ inputs.buildName }}
|
||||
ALLOW_ENV_EDIT: ${{ inputs.allow_env_edit }}
|
||||
LIVENESS_DETECTION: ${{ inputs.liveness_detection }}
|
||||
TESTFLIGHT_INTERNAL_TESTERS_GROUP: ${{ inputs.internal-testers }}
|
||||
APP_FLAVOR: ${{ inputs.injiFlavor }}
|
||||
SERVICE_LOCATION: '.'
|
||||
|
||||
2
.github/workflows/push-triggers.yml
vendored
2
.github/workflows/push-triggers.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
build-android:
|
||||
uses: mosip/kattu/.github/workflows/android-build.yml@master
|
||||
with:
|
||||
NODE_VERSION: "16.x"
|
||||
NODE_VERSION: "18.x"
|
||||
KEYSTORE_ALIAS: androidbuildkey
|
||||
KEYSTORE_PASSWORD: 'password'
|
||||
SERVICE_LOCATION: '.'
|
||||
|
||||
@@ -2,7 +2,7 @@ fileignoreconfig:
|
||||
- filename: package.json
|
||||
checksum: 5b4fcb5ddc7cc96cc2d1733b544d56ea66e88cdab995a1052fbf9ac0e9c2dc21
|
||||
- filename: package-lock.json
|
||||
checksum: a67d97b91ebdc0dd132a6a38835e9ca36449e3acff8a0fdc05d1948061951240
|
||||
checksum: 50254c7e3e84d59dceb77cde95ce71cb5d0625cb5ec84971a28b6fc4f95db7f1
|
||||
- filename: lib/jsonld-signatures/suites/ed255192018/ed25519.ts
|
||||
checksum: 493b6e31144116cb612c24d98b97d8adcad5609c0a52c865a6847ced0a0ddc3a
|
||||
- filename: components/PasscodeVerify.tsx
|
||||
@@ -346,5 +346,5 @@ fileignoreconfig:
|
||||
- filename: ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved
|
||||
checksum: b168940c6b487dc96fd22f564f2e187dae46f4fa5e4a64cf81c4d810b1c1ae78
|
||||
- filename: ios/Inji.xcodeproj/project.pbxproj
|
||||
checksum: e86064dc53172114fab4fbad7508889ea47c2ecd7d60ccc4d1f71cf32ea9169c
|
||||
checksum: 4359976ed4d1ac3206d76b87d3458d070027199c8569ba123436c4b5343aba74
|
||||
version: ""
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
import React, {useCallback, useContext, useEffect, useRef} from 'react';
|
||||
import {Camera} from 'expo-camera';
|
||||
import {TouchableOpacity, View} from 'react-native';
|
||||
import {Button, Centered, Column, Row, Text} from './ui';
|
||||
import {useInterpret, useSelector} from '@xstate/react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {
|
||||
FaceScannerEvents,
|
||||
selectIsCheckingPermission,
|
||||
selectIsValid,
|
||||
selectIsPermissionDenied,
|
||||
selectIsScanning,
|
||||
selectWhichCamera,
|
||||
createFaceScannerMachine,
|
||||
selectIsInvalid,
|
||||
selectIsCapturing,
|
||||
selectIsVerifying,
|
||||
} from '../machines/faceScanner';
|
||||
import {GlobalContext} from '../shared/GlobalContext';
|
||||
import {selectIsActive} from '../machines/app';
|
||||
import {RotatingIcon} from './RotatingIcon';
|
||||
import {Theme} from './ui/styleUtils';
|
||||
import {SvgImage} from './ui/svg';
|
||||
import testIDProps from '../shared/commonUtil';
|
||||
|
||||
export const FaceScanner: React.FC<FaceScannerProps> = props => {
|
||||
const {t} = useTranslation('FaceScanner');
|
||||
const {appService} = useContext(GlobalContext);
|
||||
const isActive = useSelector(appService, selectIsActive);
|
||||
|
||||
const machine = useRef(createFaceScannerMachine(props.vcImage));
|
||||
const service = useInterpret(machine.current);
|
||||
|
||||
const whichCamera = useSelector(service, selectWhichCamera);
|
||||
|
||||
const isPermissionDenied = useSelector(service, selectIsPermissionDenied);
|
||||
const isValid = useSelector(service, selectIsValid);
|
||||
const isInvalid = useSelector(service, selectIsInvalid);
|
||||
const isCheckingPermission = useSelector(service, selectIsCheckingPermission);
|
||||
const isScanning = useSelector(service, selectIsScanning);
|
||||
const isCapturing = useSelector(service, selectIsCapturing);
|
||||
const isVerifying = useSelector(service, selectIsVerifying);
|
||||
|
||||
const setCameraRef = useCallback(
|
||||
(node: Camera) => {
|
||||
if (node != null && !isScanning) {
|
||||
service.send(FaceScannerEvents.READY(node));
|
||||
}
|
||||
},
|
||||
[isScanning],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isValid) {
|
||||
props.onValid();
|
||||
} else if (isInvalid) {
|
||||
props.onInvalid();
|
||||
}
|
||||
}, [isValid, isInvalid]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isActive) {
|
||||
service.send(FaceScannerEvents.APP_FOCUSED());
|
||||
}
|
||||
}, [isActive]);
|
||||
|
||||
if (isCheckingPermission) {
|
||||
return <Column></Column>;
|
||||
} else if (isPermissionDenied) {
|
||||
return (
|
||||
<Column padding="24" fill align="space-between">
|
||||
<Text align="center" color={Theme.Colors.errorMessage}>
|
||||
{t('missingPermissionText')}
|
||||
</Text>
|
||||
<Button
|
||||
title={t('allowCameraButton')}
|
||||
onPress={() => service.send(FaceScannerEvents.OPEN_SETTINGS())}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column fill align="space-between">
|
||||
<View style={Theme.CameraEnabledStyles.scannerContainer}>
|
||||
<Camera
|
||||
{...testIDProps('camera')}
|
||||
style={Theme.CameraEnabledStyles.scanner}
|
||||
type={whichCamera}
|
||||
ref={setCameraRef}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
testID="imageCaptureGuide"
|
||||
align="center"
|
||||
weight="semibold"
|
||||
style={Theme.TextStyles.base}
|
||||
margin="0 57">
|
||||
{t('imageCaptureGuide')}
|
||||
</Text>
|
||||
<Centered>
|
||||
{isCapturing || isVerifying ? (
|
||||
<RotatingIcon name="sync" size={64} />
|
||||
) : (
|
||||
<Row align="center">
|
||||
<Centered style={Theme.Styles.imageCaptureButton}>
|
||||
<TouchableOpacity
|
||||
onPress={() => service.send(FaceScannerEvents.CAPTURE())}>
|
||||
{SvgImage.CameraCaptureIcon()}
|
||||
</TouchableOpacity>
|
||||
<Text
|
||||
testID="captureText"
|
||||
style={Theme.CameraEnabledStyles.iconText}>
|
||||
{t('capture')}
|
||||
</Text>
|
||||
</Centered>
|
||||
|
||||
<Centered>
|
||||
<TouchableOpacity
|
||||
onPress={() => service.send(FaceScannerEvents.FLIP_CAMERA())}>
|
||||
{SvgImage.FlipCameraIcon()}
|
||||
</TouchableOpacity>
|
||||
<Text
|
||||
testID="flipCameraText"
|
||||
style={Theme.CameraEnabledStyles.iconText}>
|
||||
{t('flipCamera')}
|
||||
</Text>
|
||||
</Centered>
|
||||
</Row>
|
||||
)}
|
||||
</Centered>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
interface FaceScannerProps {
|
||||
vcImage: string;
|
||||
onValid: () => void;
|
||||
onInvalid: () => void;
|
||||
}
|
||||
78
components/FaceScanner/FaceCompare.tsx
Normal file
78
components/FaceScanner/FaceCompare.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { Camera, CameraType } from 'expo-camera';
|
||||
import { View, TouchableOpacity } from 'react-native';
|
||||
import {SvgImage} from '../ui/svg';
|
||||
import { Text, Column, Row, Centered } from '../ui';
|
||||
import {RotatingIcon} from '../RotatingIcon';
|
||||
import { Theme } from '../ui/styleUtils';
|
||||
import testIDProps from '../../shared/commonUtil';
|
||||
|
||||
const FaceCompare: React.FC<FaceCompareProps> = ({
|
||||
whichCamera,
|
||||
setCameraRef,
|
||||
isCapturing,
|
||||
isVerifying,
|
||||
service,
|
||||
t
|
||||
}) => {
|
||||
return (
|
||||
<Column fill align="space-between" style={{ backgroundColor: '#ffffff' }}>
|
||||
<View style={{ flex: 2, marginTop: 15 }}>
|
||||
<View style={Theme.CameraEnabledStyles.scannerContainer}>
|
||||
<View>
|
||||
<Camera
|
||||
{...testIDProps('camera')}
|
||||
style={Theme.CameraEnabledStyles.scanner}
|
||||
type={whichCamera}
|
||||
ref={setCameraRef}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Text
|
||||
testID="imageCaptureGuide"
|
||||
align="center"
|
||||
weight="semibold"
|
||||
style={Theme.TextStyles.base}
|
||||
margin="80 57"
|
||||
>
|
||||
{t('imageCaptureGuide')}
|
||||
</Text>
|
||||
</View>
|
||||
<Centered>
|
||||
{isCapturing || isVerifying ? (
|
||||
<RotatingIcon name="sync" size={64} />
|
||||
) : (
|
||||
<Row align="center">
|
||||
<Centered style={Theme.Styles.imageCaptureButton}>
|
||||
<TouchableOpacity onPress={() => service.send('CAPTURE')}>
|
||||
{SvgImage.CameraCaptureIcon()}
|
||||
</TouchableOpacity>
|
||||
<Text testID="captureText" style={Theme.CameraEnabledStyles.iconText}>
|
||||
{t('capture')}
|
||||
</Text>
|
||||
</Centered>
|
||||
<Centered>
|
||||
<TouchableOpacity onPress={() => service.send('FLIP_CAMERA')}>
|
||||
{SvgImage.FlipCameraIcon()}
|
||||
</TouchableOpacity>
|
||||
<Text testID="flipCameraText" style={Theme.CameraEnabledStyles.iconText}>
|
||||
{t('flipCamera')}
|
||||
</Text>
|
||||
</Centered>
|
||||
</Row>
|
||||
)}
|
||||
</Centered>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default FaceCompare;
|
||||
|
||||
interface FaceCompareProps {
|
||||
whichCamera: CameraType;
|
||||
setCameraRef:(node: Camera) => void;
|
||||
isCapturing: boolean;
|
||||
isVerifying: boolean;
|
||||
service: any;
|
||||
t: (key: string) => string;
|
||||
}
|
||||
209
components/FaceScanner/FaceScanner.tsx
Normal file
209
components/FaceScanner/FaceScanner.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {Camera} from 'expo-camera';
|
||||
import {Column, Text, Button} from '.././ui';
|
||||
import {useInterpret, useSelector} from '@xstate/react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {
|
||||
FaceScannerEvents,
|
||||
selectIsCheckingPermission,
|
||||
selectIsValid,
|
||||
selectIsPermissionDenied,
|
||||
selectIsScanning,
|
||||
selectWhichCamera,
|
||||
createFaceScannerMachine,
|
||||
selectIsInvalid,
|
||||
selectIsCapturing,
|
||||
selectIsVerifying,
|
||||
selectCameraRef,
|
||||
} from '../../machines/faceScanner';
|
||||
import {GlobalContext} from '../../shared/GlobalContext';
|
||||
import {selectIsActive} from '../../machines/app';
|
||||
import {Theme} from '.././ui/styleUtils';
|
||||
import {getRandomInt} from '../../shared/commonUtil';
|
||||
import {
|
||||
checkBlink,
|
||||
cropEyeAreaFromFace,
|
||||
faceDetectorConfig,
|
||||
getFaceBounds,
|
||||
imageCaptureConfig,
|
||||
} from './FaceScannerHelper';
|
||||
import LivenessDetection from './LivenessDetection';
|
||||
import FaceCompare from './FaceCompare';
|
||||
import {LIVENESS_CHECK} from '../../shared/constants';
|
||||
|
||||
export const FaceScanner: React.FC<FaceScannerProps> = props => {
|
||||
const {t} = useTranslation('FaceScanner');
|
||||
const {appService} = useContext(GlobalContext);
|
||||
const isActive = useSelector(appService, selectIsActive);
|
||||
|
||||
const machine = useRef(createFaceScannerMachine(props.vcImage));
|
||||
const service = useInterpret(machine.current);
|
||||
|
||||
const whichCamera = useSelector(service, selectWhichCamera);
|
||||
const cameraRef = useSelector(service, selectCameraRef);
|
||||
|
||||
const isPermissionDenied = useSelector(service, selectIsPermissionDenied);
|
||||
const isValid = useSelector(service, selectIsValid);
|
||||
const isInvalid = useSelector(service, selectIsInvalid);
|
||||
const isCheckingPermission = useSelector(service, selectIsCheckingPermission);
|
||||
const isScanning = useSelector(service, selectIsScanning);
|
||||
const isCapturing = useSelector(service, selectIsCapturing);
|
||||
const isVerifying = useSelector(service, selectIsVerifying);
|
||||
|
||||
const [counter, setCounter] = useState(0);
|
||||
const [screenColor, setScreenColor] = useState('#0000ff');
|
||||
const [faceToCompare, setFaceToCompare] = useState(null);
|
||||
const [opacity, setOpacity] = useState(1);
|
||||
const [picArray, setPicArray] = useState([]);
|
||||
|
||||
const screenFlashColors = ['#0000FF', '#00FF00', '#FF0000'];
|
||||
const MAX_COUNTER = 15;
|
||||
|
||||
const randomNumToFaceCompare = getRandomInt(counter, MAX_COUNTER - 1);
|
||||
const [infoText, setInfoText] = useState<string>(t('livenessCaptureGuide'));
|
||||
|
||||
const setCameraRef = useCallback(
|
||||
(node: Camera) => {
|
||||
if (node != null && !isScanning) {
|
||||
service.send(FaceScannerEvents.READY(node));
|
||||
}
|
||||
},
|
||||
[isScanning],
|
||||
);
|
||||
|
||||
function handleOnCancel() {
|
||||
props.onCancel();
|
||||
}
|
||||
|
||||
async function captureImage(screenColor) {
|
||||
try {
|
||||
if (cameraRef) {
|
||||
const capturedImage = await cameraRef.takePictureAsync(
|
||||
imageCaptureConfig,
|
||||
);
|
||||
|
||||
setPicArray([...picArray, {color: screenColor, image: capturedImage}]);
|
||||
|
||||
if (counter === randomNumToFaceCompare) {
|
||||
setFaceToCompare(capturedImage);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error capturing image:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFacesDetected({faces}) {
|
||||
checkBlink(faces[0]);
|
||||
|
||||
if (counter == MAX_COUNTER) {
|
||||
setCounter(counter + 1);
|
||||
cameraRef.pausePreview();
|
||||
|
||||
setScreenColor('#ffffff');
|
||||
setInfoText(t('faceProcessingInfo'));
|
||||
|
||||
const result = await cropEyeAreaFromFace(
|
||||
picArray,
|
||||
props.vcImage,
|
||||
faceToCompare,
|
||||
);
|
||||
return result ? props.onValid() : props.onInvalid();
|
||||
} else if (faces.length > 0) {
|
||||
const [withinXBounds, withinYBounds, withinYawAngle, withinRollAngle] =
|
||||
getFaceBounds(faces[0]);
|
||||
|
||||
setInfoText(t('faceOutGuide'));
|
||||
|
||||
if (
|
||||
withinXBounds &&
|
||||
withinYBounds &&
|
||||
withinRollAngle &&
|
||||
withinYawAngle &&
|
||||
counter < MAX_COUNTER
|
||||
) {
|
||||
const randomNum = getRandomInt(0, 2);
|
||||
const randomColor = screenFlashColors[randomNum];
|
||||
setScreenColor(randomColor);
|
||||
setCounter(counter + 1);
|
||||
setInfoText(t('faceInGuide'));
|
||||
await captureImage(screenColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isValid) {
|
||||
props.onValid();
|
||||
} else if (isInvalid) {
|
||||
props.onInvalid();
|
||||
}
|
||||
}, [isValid, isInvalid]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isActive) {
|
||||
service.send(FaceScannerEvents.APP_FOCUSED());
|
||||
}
|
||||
}, [isActive]);
|
||||
|
||||
if (isCheckingPermission) {
|
||||
return <Column></Column>;
|
||||
} else if (isPermissionDenied) {
|
||||
return (
|
||||
<Column padding="24" fill align="space-between">
|
||||
<Text
|
||||
testID="missingPermissionText"
|
||||
align="center"
|
||||
color={Theme.Colors.errorMessage}>
|
||||
{t('missingPermissionText')}
|
||||
</Text>
|
||||
<Button
|
||||
testID="allowCameraButton"
|
||||
title={t('allowCameraButton')}
|
||||
onPress={() => service.send(FaceScannerEvents.OPEN_SETTINGS())}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
if (LIVENESS_CHECK) {
|
||||
return (
|
||||
<LivenessDetection
|
||||
screenColor={screenColor}
|
||||
infoText={infoText}
|
||||
whichCamera={whichCamera}
|
||||
setCameraRef={setCameraRef}
|
||||
handleFacesDetected={handleFacesDetected}
|
||||
faceDetectorConfig={faceDetectorConfig}
|
||||
handleOnCancel={handleOnCancel}
|
||||
opacity={opacity}
|
||||
setOpacity={setOpacity}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<FaceCompare
|
||||
whichCamera={whichCamera}
|
||||
setCameraRef={setCameraRef}
|
||||
isCapturing={isCapturing}
|
||||
isVerifying={isVerifying}
|
||||
service={service}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
interface FaceScannerProps {
|
||||
vcImage: string;
|
||||
onValid: () => void;
|
||||
onInvalid: () => void;
|
||||
isLiveness: boolean;
|
||||
onCancel: () => void;
|
||||
}
|
||||
264
components/FaceScanner/FaceScannerHelper.ts
Normal file
264
components/FaceScanner/FaceScannerHelper.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import hexRgb, {RgbaObject} from 'hex-rgb';
|
||||
import {LIVENESS_THRESHOLD, isAndroid} from '../../shared/constants';
|
||||
import {closest} from 'color-diff';
|
||||
import * as FaceDetector from 'expo-face-detector';
|
||||
import ImageEditor from '@react-native-community/image-editor';
|
||||
import {ImageType} from 'expo-camera';
|
||||
import {getColors} from 'react-native-image-colors';
|
||||
import {faceCompare} from '@iriscan/biometric-sdk-react-native';
|
||||
|
||||
let FaceCropPicArray: any[] = new Array();
|
||||
let EyeCropPicArray: any[] = new Array();
|
||||
let predictedColorResults: any[] = new Array();
|
||||
let facePoints;
|
||||
let calculatedThreshold;
|
||||
let faceCompareOuptut;
|
||||
let capturedFaceImage;
|
||||
let leftEyeWasClosed = false;
|
||||
let rightEyeWasClosed = false;
|
||||
let lastBlinkTimestamp = 0;
|
||||
let blinkCounter = 0;
|
||||
|
||||
const offsetX = 200;
|
||||
const offsetY = 350;
|
||||
const captureInterval = 650;
|
||||
const eyeOpenProbability = 0.85;
|
||||
const blinkConfidenceScore = 0.1;
|
||||
const blinkThreshold = 0.4;
|
||||
const blinkTimeInterval = 900;
|
||||
const eyeCropHeightConst = 50;
|
||||
const XAndYBoundsMax = 280;
|
||||
const XAndYBoundsMin = 300;
|
||||
const rollAngleThreshold = 10;
|
||||
const yawAngleThreshold = 3;
|
||||
const colorFiltered = ['background', 'dominant'];
|
||||
const rxDataURI = /data:(?<mime>[\w/\-.]+);(?<encoding>\w+),(?<data>.*)/;
|
||||
const colorComparePalette = [
|
||||
{R: 255, G: 0, B: 0},
|
||||
{R: 0, G: 255, B: 0},
|
||||
{R: 0, G: 0, B: 255},
|
||||
];
|
||||
|
||||
export const imageCaptureConfig = {
|
||||
base64: true,
|
||||
quality: 1,
|
||||
imageType: ImageType.jpg,
|
||||
};
|
||||
|
||||
export const faceDetectorConfig : FaceDetectorConfig= {
|
||||
mode: FaceDetector.FaceDetectorMode.accurate,
|
||||
detectLandmarks: FaceDetector.FaceDetectorLandmarks.all,
|
||||
runClassifications: FaceDetector.FaceDetectorClassifications.all,
|
||||
contourMode: FaceDetector.FaceDetectorClassifications.all,
|
||||
minDetectionInterval: captureInterval,
|
||||
tracking: true,
|
||||
};
|
||||
|
||||
export const checkBlink = face => {
|
||||
const leftEyeOpenProbability = face.leftEyeOpenProbability;
|
||||
const rightEyeOpenProbability = face.rightEyeOpenProbability;
|
||||
|
||||
const currentTime = new Date().getTime();
|
||||
|
||||
const leftEyeClosed = leftEyeOpenProbability < blinkThreshold;
|
||||
const rightEyeClosed = rightEyeOpenProbability < blinkThreshold;
|
||||
|
||||
if (leftEyeClosed && rightEyeClosed) {
|
||||
leftEyeWasClosed = true;
|
||||
rightEyeWasClosed = true;
|
||||
}
|
||||
|
||||
if (
|
||||
leftEyeWasClosed &&
|
||||
rightEyeWasClosed &&
|
||||
!leftEyeClosed &&
|
||||
!rightEyeClosed
|
||||
) {
|
||||
if (
|
||||
lastBlinkTimestamp === 0 ||
|
||||
currentTime - lastBlinkTimestamp > blinkTimeInterval
|
||||
) {
|
||||
blinkCounter = blinkCounter + 1;
|
||||
lastBlinkTimestamp = currentTime;
|
||||
}
|
||||
leftEyeWasClosed = false;
|
||||
rightEyeWasClosed = false;
|
||||
}
|
||||
};
|
||||
|
||||
export const getFaceBounds = face => {
|
||||
const {bounds, yawAngle, rollAngle} = face;
|
||||
|
||||
const withinXBounds =
|
||||
bounds.origin.x + bounds.size.width >= XAndYBoundsMax &&
|
||||
bounds.origin.x <= XAndYBoundsMin;
|
||||
const withinYBounds =
|
||||
bounds.origin.y + bounds.size.height >= XAndYBoundsMax &&
|
||||
bounds.origin.y <= XAndYBoundsMin;
|
||||
const withinYawAngle =
|
||||
yawAngle > -yawAngleThreshold && yawAngle < yawAngleThreshold;
|
||||
const withinRollAngle =
|
||||
rollAngle > -rollAngleThreshold && rollAngle < rollAngleThreshold;
|
||||
|
||||
return [withinXBounds, withinYBounds, withinYawAngle, withinRollAngle];
|
||||
};
|
||||
|
||||
export const getNormalizedFacePoints = (facePoints: any): number[] => {
|
||||
return isAndroid()
|
||||
? [
|
||||
facePoints.LEFT_EYE.x,
|
||||
facePoints.LEFT_EYE.y,
|
||||
facePoints.RIGHT_EYE.x,
|
||||
facePoints.RIGHT_EYE.y,
|
||||
]
|
||||
: [
|
||||
facePoints.leftEyePosition.x,
|
||||
facePoints.leftEyePosition.y,
|
||||
facePoints.rightEyePosition.x,
|
||||
facePoints.rightEyePosition.y,
|
||||
];
|
||||
};
|
||||
|
||||
export const filterColor = color => {
|
||||
return (
|
||||
typeof color === 'string' &&
|
||||
color.startsWith('#') &&
|
||||
!colorFiltered.includes(color)
|
||||
);
|
||||
};
|
||||
|
||||
export const getEyeColorPredictionResult = async (
|
||||
LeftrgbaColors: RgbaObject[],
|
||||
color: RgbaObject,
|
||||
) => {
|
||||
LeftrgbaColors.forEach(colorRGBA => {
|
||||
let colorRGB = {};
|
||||
colorRGB['R'] = colorRGBA.red;
|
||||
colorRGB['G'] = colorRGBA.green;
|
||||
colorRGB['B'] = colorRGBA.blue;
|
||||
|
||||
const closestColor = closest(colorRGB, colorComparePalette);
|
||||
|
||||
const result =
|
||||
color.red === closestColor.R &&
|
||||
color.blue === closestColor.B &&
|
||||
color.green === closestColor.G;
|
||||
|
||||
predictedColorResults.push(result);
|
||||
});
|
||||
};
|
||||
|
||||
export const cropEyeAreaFromFace = async (picArray, vcImage, capturedImage) => {
|
||||
try {
|
||||
await Promise.all(
|
||||
picArray.map(async pic => {
|
||||
facePoints = (
|
||||
await FaceDetector.detectFacesAsync(pic.image.uri, faceDetectorConfig)
|
||||
).faces[0];
|
||||
|
||||
if (
|
||||
facePoints.leftEyeOpenProbability > eyeOpenProbability &&
|
||||
facePoints.rightEyeOpenProbability > eyeOpenProbability
|
||||
) {
|
||||
capturedFaceImage = await ImageEditor.cropImage(pic.image.uri, {
|
||||
offset: {
|
||||
x: facePoints.bounds.origin.x,
|
||||
y: facePoints.bounds.origin.y,
|
||||
},
|
||||
size: {
|
||||
width: facePoints.bounds.size.width,
|
||||
height: facePoints.bounds.size.height,
|
||||
},
|
||||
});
|
||||
|
||||
FaceCropPicArray.push({color: pic.color, image: capturedFaceImage});
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
FaceCropPicArray.map(async pic => {
|
||||
let [leftEyeX, leftEyeY, rightEyeX, rightEyeY] =
|
||||
getNormalizedFacePoints(facePoints);
|
||||
|
||||
const leftCroppedImage = await ImageEditor.cropImage(pic.image.uri, {
|
||||
offset: {
|
||||
x: leftEyeX - offsetX,
|
||||
y: leftEyeY - offsetY,
|
||||
},
|
||||
size: {
|
||||
width: offsetX * 2,
|
||||
height: offsetY / 2 - eyeCropHeightConst,
|
||||
},
|
||||
});
|
||||
|
||||
const rightCroppedImage = await ImageEditor.cropImage(pic.image.uri, {
|
||||
offset: {
|
||||
x: rightEyeX - offsetX,
|
||||
y: rightEyeY - offsetY,
|
||||
},
|
||||
size: {
|
||||
width: offsetX * 2,
|
||||
height: offsetY / 2 - eyeCropHeightConst,
|
||||
},
|
||||
});
|
||||
|
||||
EyeCropPicArray.push({
|
||||
color: pic.color,
|
||||
leftEye: leftCroppedImage,
|
||||
rightEye: rightCroppedImage,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
EyeCropPicArray.map(async pic => {
|
||||
const leftEyeColors = await getColors(pic.leftEye.uri);
|
||||
const rightEyeColors = await getColors(pic.rightEye.uri);
|
||||
|
||||
const leftRGBAColors = Object.values(leftEyeColors)
|
||||
.filter(filterColor)
|
||||
.map(color => hexRgb(color));
|
||||
|
||||
const rightRGBAColors = Object.values(rightEyeColors)
|
||||
.filter(filterColor)
|
||||
.map(color => hexRgb(color));
|
||||
|
||||
const rgbColor = hexRgb(pic.color);
|
||||
await getEyeColorPredictionResult(leftRGBAColors, rgbColor);
|
||||
await getEyeColorPredictionResult(rightRGBAColors, rgbColor);
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Unable to crop the images::', err);
|
||||
return false;
|
||||
}
|
||||
|
||||
calculatedThreshold =
|
||||
predictedColorResults.filter(element => element).length /
|
||||
predictedColorResults.length;
|
||||
|
||||
const matches = rxDataURI.exec(vcImage).groups;
|
||||
const vcFace = matches.data;
|
||||
|
||||
faceCompareOuptut = await faceCompare(vcFace, capturedImage.base64);
|
||||
|
||||
if (blinkCounter > 0) {
|
||||
calculatedThreshold = calculatedThreshold + blinkConfidenceScore;
|
||||
}
|
||||
|
||||
if (calculatedThreshold > LIVENESS_THRESHOLD && faceCompareOuptut) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export interface FaceDetectorConfig {
|
||||
mode: FaceDetector.FaceDetectorMode;
|
||||
detectLandmarks: FaceDetector.FaceDetectorLandmarks;
|
||||
runClassifications: FaceDetector.FaceDetectorClassifications;
|
||||
contourMode: FaceDetector.FaceDetectorClassifications;
|
||||
minDetectionInterval: number;
|
||||
tracking: boolean;
|
||||
};
|
||||
86
components/FaceScanner/LivenessDetection.tsx
Normal file
86
components/FaceScanner/LivenessDetection.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import { Camera, CameraType } from 'expo-camera';
|
||||
import { View, TouchableOpacity } from 'react-native';
|
||||
import Spinner from 'react-native-spinkit';
|
||||
import { Column, Text } from '.././ui';
|
||||
import { Theme } from '.././ui/styleUtils';
|
||||
import Svg, { Defs, Mask, Rect, Ellipse } from 'react-native-svg';
|
||||
import testIDProps from '../../shared/commonUtil';
|
||||
import { FaceDetectorConfig } from './FaceScannerHelper';
|
||||
|
||||
const LivenessDetection: React.FC<LivenessDetectionProps> = ({
|
||||
screenColor,
|
||||
infoText,
|
||||
whichCamera,
|
||||
setCameraRef,
|
||||
handleFacesDetected,
|
||||
faceDetectorConfig,
|
||||
handleOnCancel,
|
||||
opacity,
|
||||
setOpacity,
|
||||
t
|
||||
}) => {
|
||||
return (
|
||||
<Column fill align='space-between' style={{ backgroundColor: screenColor }}>
|
||||
<View style={Theme.CameraEnabledStyles.guideContainer}>
|
||||
<View style={Theme.CameraEnabledStyles.guideContentContainer}>
|
||||
<Spinner type="ThreeBounce" color={Theme.Colors.Loading} />
|
||||
<Text testID="captureInfoText" size="small" weight="bold" color="black" align="center">
|
||||
{infoText}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ flex: 2, marginTop: 15 }}>
|
||||
<View style={Theme.CameraEnabledStyles.scannerContainer}>
|
||||
<View>
|
||||
<Camera
|
||||
{...testIDProps('camera')}
|
||||
style={Theme.CameraEnabledStyles.scanner}
|
||||
type={whichCamera}
|
||||
ref={setCameraRef}
|
||||
onFacesDetected={handleFacesDetected}
|
||||
faceDetectorSettings={faceDetectorConfig}
|
||||
/>
|
||||
<Svg height="100%" width="100%" style={{ position: 'absolute' }}>
|
||||
<Defs>
|
||||
<Mask id="mask" x="0" y="0" height="100%" width="100%">
|
||||
<Rect height="100%" width="100%" fill="#fff" opacity="0.3" />
|
||||
<Ellipse rx="38%" ry="45%" cx="50%" cy="50%" fill="black" />
|
||||
</Mask>
|
||||
</Defs>
|
||||
<Rect height="100%" width="100%" fill="rgba(0, 0, 0, 0.8)" mask="url(#mask)" />
|
||||
</Svg>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={Theme.CameraEnabledStyles.buttonContainer}>
|
||||
<TouchableOpacity
|
||||
{...testIDProps('cancel')}
|
||||
style={[Theme.CameraEnabledStyles.cancelButton, { opacity }]}
|
||||
onPressIn={() => setOpacity(0.5)}
|
||||
onPressOut={() => setOpacity(1)}
|
||||
onPress={handleOnCancel}
|
||||
>
|
||||
<Text testID="cancelText" size="small" weight="bold" margin="8" color="black">
|
||||
{t('cancel')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default LivenessDetection;
|
||||
|
||||
interface LivenessDetectionProps {
|
||||
screenColor: string;
|
||||
infoText: string;
|
||||
whichCamera: CameraType;
|
||||
setCameraRef: (node: Camera) => void;
|
||||
handleFacesDetected: (faces: any) => Promise<void>;
|
||||
faceDetectorConfig: FaceDetectorConfig;
|
||||
handleOnCancel: () => void;
|
||||
opacity: number;
|
||||
setOpacity: (opacity: number) => void;
|
||||
t: (key: string) => string;
|
||||
}
|
||||
@@ -19,7 +19,8 @@ export const Modal: React.FC<ModalProps> = props => {
|
||||
visible={props.isVisible}
|
||||
onShow={props.onShow}
|
||||
onRequestClose={props.onDismiss}>
|
||||
<Column fill safe>
|
||||
<Column {...(props.showHeader ? { fill: true, safe: true } : { fill: true })}>
|
||||
{ props.showHeader ? (
|
||||
<Row elevation={props.headerElevation}>
|
||||
<View style={props.modalStyle}>
|
||||
{props.headerRight && !props.arrowLeft ? (
|
||||
@@ -75,7 +76,7 @@ export const Modal: React.FC<ModalProps> = props => {
|
||||
))}
|
||||
{props.headerRight && props.headerRight}
|
||||
</View>
|
||||
</Row>
|
||||
</Row> ) : null}
|
||||
{props.children}
|
||||
</Column>
|
||||
</RNModal>
|
||||
@@ -85,6 +86,7 @@ export const Modal: React.FC<ModalProps> = props => {
|
||||
Modal.defaultProps = {
|
||||
modalStyle: Theme.ModalStyles.defaultModal,
|
||||
showClose: true,
|
||||
showHeader: true,
|
||||
};
|
||||
|
||||
export interface ModalProps {
|
||||
@@ -92,6 +94,7 @@ export interface ModalProps {
|
||||
isVisible: boolean;
|
||||
requester?: boolean;
|
||||
showClose?: boolean;
|
||||
showHeader?: boolean;
|
||||
modalStyle?: Object;
|
||||
onDismiss?: () => void;
|
||||
headerTitle?: string;
|
||||
|
||||
@@ -1572,6 +1572,33 @@ export const DefaultTheme = {
|
||||
width: '100%',
|
||||
margin: 'auto',
|
||||
},
|
||||
guideContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
},
|
||||
guideContentContainer: {
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 9,
|
||||
width: Dimensions.get('window').width * 0.85,
|
||||
alignItems: 'center',
|
||||
marginTop: Dimensions.get('window').height * 0.12,
|
||||
padding: 3,
|
||||
},
|
||||
buttonContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
},
|
||||
cancelButton: {
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 9,
|
||||
width: Dimensions.get('window').width * 0.3,
|
||||
alignSelf: 'center',
|
||||
alignItems: 'center',
|
||||
height: 40,
|
||||
marginBottom: Dimensions.get('window').height * 0.1,
|
||||
},
|
||||
holdPhoneSteadyText: {
|
||||
color: Colors.Black,
|
||||
fontFamily: 'Inter_600SemiBold',
|
||||
|
||||
@@ -1571,6 +1571,33 @@ export const PurpleTheme = {
|
||||
width: '100%',
|
||||
margin: 'auto',
|
||||
},
|
||||
guideContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
},
|
||||
guideContentContainer: {
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 9,
|
||||
width: Dimensions.get('window').width * 0.85,
|
||||
alignItems: 'center',
|
||||
marginTop: Dimensions.get('window').height * 0.12,
|
||||
padding: 3,
|
||||
},
|
||||
buttonContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
},
|
||||
cancelButton: {
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 9,
|
||||
width: Dimensions.get('window').width * 0.3,
|
||||
alignSelf: 'center',
|
||||
alignItems: 'center',
|
||||
height: 40,
|
||||
marginBottom: Dimensions.get('window').height * 0.1,
|
||||
},
|
||||
holdPhoneSteadyText: {
|
||||
color: Colors.Black,
|
||||
fontFamily: 'Inter_600SemiBold',
|
||||
|
||||
@@ -272,7 +272,7 @@
|
||||
mainGroup = 83CBB9F61A601CBA00E9B192;
|
||||
packageReferences = (
|
||||
9CE34B1D2BFE03E4001AF414 /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */,
|
||||
E8AF2B9B2C0D93E800E775F6 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */,
|
||||
E86208242C039895007C3E24 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */,
|
||||
);
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -380,15 +380,27 @@
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Inji/Pods-Inji-resources.sh",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn/GoogleSignIn.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/MLKitFaceDetection/GoogleMVFaceDetectorResources.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/Protobuf/Protobuf_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMVFaceDetectorResources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Protobuf_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
@@ -679,7 +691,7 @@
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
E8AF2B9B2C0D93E800E775F6 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */ = {
|
||||
E86208242C039895007C3E24 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mosip/inji-vci-client-ios-swift.git";
|
||||
requirement = {
|
||||
@@ -697,7 +709,7 @@
|
||||
};
|
||||
E8AF2B9C2C0D93E800E775F6 /* VCIClient */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E8AF2B9B2C0D93E800E775F6 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */;
|
||||
package = E86208242C039895007C3E24 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */;
|
||||
productName = VCIClient;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
@@ -29,4 +29,4 @@
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties
|
||||
ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0'
|
||||
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = '1' if podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR'] == 'true'
|
||||
|
||||
platform :ios, podfile_properties['ios.deploymentTarget'] || '13.0'
|
||||
platform :ios, podfile_properties['ios.deploymentTarget'] || '13.4'
|
||||
install! 'cocoapods',
|
||||
:deterministic_uuids => false
|
||||
|
||||
|
||||
114
ios/Podfile.lock
114
ios/Podfile.lock
@@ -30,6 +30,12 @@ PODS:
|
||||
- ExpoModulesCore
|
||||
- EXConstants (14.4.2):
|
||||
- ExpoModulesCore
|
||||
- EXFaceDetector (12.4.0):
|
||||
- ExpoModulesCore
|
||||
- GoogleMLKit/FaceDetection (= 2.6.0)
|
||||
- MLKitCommon (= 5.0.0)
|
||||
- MLKitFaceDetection (= 1.5.0)
|
||||
- MLKitVision (= 3.0.0)
|
||||
- EXFileSystem (15.4.5):
|
||||
- ExpoModulesCore
|
||||
- EXFont (11.1.1):
|
||||
@@ -83,22 +89,82 @@ PODS:
|
||||
- ReactCommon/turbomodule/core (= 0.71.8)
|
||||
- fmt (6.2.1)
|
||||
- glog (0.3.5)
|
||||
- GoogleDataTransport (9.4.1):
|
||||
- GoogleUtilities/Environment (~> 7.7)
|
||||
- nanopb (< 2.30911.0, >= 2.30908.0)
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleMLKit/FaceDetection (2.6.0):
|
||||
- GoogleMLKit/MLKitCore
|
||||
- MLKitFaceDetection (~> 1.5.0)
|
||||
- GoogleMLKit/MLKitCore (2.6.0):
|
||||
- MLKitCommon (~> 5.0.0)
|
||||
- GoogleSignIn (7.0.0):
|
||||
- AppAuth (~> 1.5)
|
||||
- GTMAppAuth (< 3.0, >= 1.3)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.1)
|
||||
- GoogleToolboxForMac/DebugUtils (2.3.2):
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- GoogleToolboxForMac/Defines (2.3.2)
|
||||
- GoogleToolboxForMac/Logger (2.3.2):
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- "GoogleToolboxForMac/NSData+zlib (2.3.2)":
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (2.3.2)":
|
||||
- GoogleToolboxForMac/DebugUtils (= 2.3.2)
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (= 2.3.2)"
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (2.3.2)"
|
||||
- GoogleUtilities/Environment (7.13.3):
|
||||
- GoogleUtilities/Privacy
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleUtilities/Logger (7.13.3):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (7.13.3)
|
||||
- GoogleUtilities/UserDefaults (7.13.3):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilitiesComponents (1.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GTMAppAuth (2.0.0):
|
||||
- AppAuth/Core (~> 1.6)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.5)
|
||||
- GTMSessionFetcher/Core (3.2.0)
|
||||
- GTMSessionFetcher/Core (1.7.2)
|
||||
- GzipSwift (5.1.1)
|
||||
- hermes-engine (0.71.8):
|
||||
- hermes-engine/Pre-built (= 0.71.8)
|
||||
- hermes-engine/Pre-built (0.71.8)
|
||||
- ImageColors (2.4.0):
|
||||
- ExpoModulesCore
|
||||
- libevent (2.1.12)
|
||||
- MLImage (1.0.0-beta2)
|
||||
- MLKitCommon (5.0.0):
|
||||
- GoogleDataTransport (~> 9.0)
|
||||
- GoogleToolboxForMac/Logger (~> 2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
|
||||
- GoogleUtilities/UserDefaults (~> 7.0)
|
||||
- GoogleUtilitiesComponents (~> 1.0)
|
||||
- GTMSessionFetcher/Core (~> 1.1)
|
||||
- Protobuf (~> 3.12)
|
||||
- MLKitFaceDetection (1.5.0):
|
||||
- MLKitCommon (~> 5.0)
|
||||
- MLKitVision (~> 3.0)
|
||||
- MLKitVision (3.0.0):
|
||||
- GoogleToolboxForMac/Logger (~> 2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
|
||||
- GTMSessionFetcher/Core (~> 1.1)
|
||||
- MLImage (= 1.0.0-beta2)
|
||||
- MLKitCommon (~> 5.0)
|
||||
- Protobuf (~> 3.12)
|
||||
- MMKV (1.2.13):
|
||||
- MMKVCore (~> 1.2.13)
|
||||
- MMKVCore (1.2.16)
|
||||
- nanopb (2.30910.0):
|
||||
- nanopb/decode (= 2.30910.0)
|
||||
- nanopb/encode (= 2.30910.0)
|
||||
- nanopb/decode (2.30910.0)
|
||||
- nanopb/encode (2.30910.0)
|
||||
- Permission-BluetoothPeripheral (3.8.4):
|
||||
- RNPermissions
|
||||
- Permission-Camera (3.8.4):
|
||||
@@ -107,6 +173,8 @@ PODS:
|
||||
- RNPermissions
|
||||
- Permission-LocationWhenInUse (3.8.4):
|
||||
- RNPermissions
|
||||
- PromisesObjC (2.4.0)
|
||||
- Protobuf (3.27.0)
|
||||
- RCT-Folly (2021.07.22.00):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@@ -365,6 +433,10 @@ PODS:
|
||||
- react-native-cloud-storage (1.4.0):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core
|
||||
- react-native-image-editor (4.2.0):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- react-native-location (2.5.0):
|
||||
- React
|
||||
- react-native-mmkv-storage (0.9.1):
|
||||
@@ -541,6 +613,7 @@ DEPENDENCIES:
|
||||
- EXBarCodeScanner (from `../node_modules/expo-barcode-scanner/ios`)
|
||||
- EXCamera (from `../node_modules/expo-camera/ios`)
|
||||
- EXConstants (from `../node_modules/expo-constants/ios`)
|
||||
- EXFaceDetector (from `../node_modules/expo-face-detector/ios`)
|
||||
- EXFileSystem (from `../node_modules/expo-file-system/ios`)
|
||||
- EXFont (from `../node_modules/expo-font/ios`)
|
||||
- EXImageLoader (from `../node_modules/expo-image-loader/ios`)
|
||||
@@ -561,6 +634,7 @@ DEPENDENCIES:
|
||||
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
|
||||
- ImageColors (from `../node_modules/react-native-image-colors/ios`)
|
||||
- libevent (~> 2.1.12)
|
||||
- Permission-BluetoothPeripheral (from `../node_modules/react-native-permissions/ios/BluetoothPeripheral`)
|
||||
- Permission-Camera (from `../node_modules/react-native-permissions/ios/Camera`)
|
||||
@@ -583,6 +657,7 @@ DEPENDENCIES:
|
||||
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
|
||||
- react-native-app-auth (from `../node_modules/react-native-app-auth`)
|
||||
- react-native-cloud-storage (from `../node_modules/react-native-cloud-storage`)
|
||||
- "react-native-image-editor (from `../node_modules/@react-native-community/image-editor`)"
|
||||
- react-native-location (from `../node_modules/react-native-location`)
|
||||
- react-native-mmkv-storage (from `../node_modules/react-native-mmkv-storage`)
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
@@ -632,13 +707,25 @@ SPEC REPOS:
|
||||
- CatCrypto
|
||||
- CrcSwift
|
||||
- fmt
|
||||
- GoogleDataTransport
|
||||
- GoogleMLKit
|
||||
- GoogleSignIn
|
||||
- GoogleToolboxForMac
|
||||
- GoogleUtilities
|
||||
- GoogleUtilitiesComponents
|
||||
- GTMAppAuth
|
||||
- GTMSessionFetcher
|
||||
- GzipSwift
|
||||
- libevent
|
||||
- MLImage
|
||||
- MLKitCommon
|
||||
- MLKitFaceDetection
|
||||
- MLKitVision
|
||||
- MMKV
|
||||
- MMKVCore
|
||||
- nanopb
|
||||
- PromisesObjC
|
||||
- Protobuf
|
||||
- ReachabilitySwift
|
||||
- SSZipArchive
|
||||
- TensorFlowLiteC
|
||||
@@ -664,6 +751,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/expo-camera/ios"
|
||||
EXConstants:
|
||||
:path: "../node_modules/expo-constants/ios"
|
||||
EXFaceDetector:
|
||||
:path: "../node_modules/expo-face-detector/ios"
|
||||
EXFileSystem:
|
||||
:path: "../node_modules/expo-file-system/ios"
|
||||
EXFont:
|
||||
@@ -704,6 +793,8 @@ EXTERNAL SOURCES:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
|
||||
hermes-engine:
|
||||
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
|
||||
ImageColors:
|
||||
:path: "../node_modules/react-native-image-colors/ios"
|
||||
Permission-BluetoothPeripheral:
|
||||
:path: "../node_modules/react-native-permissions/ios/BluetoothPeripheral"
|
||||
Permission-Camera:
|
||||
@@ -744,6 +835,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-app-auth"
|
||||
react-native-cloud-storage:
|
||||
:path: "../node_modules/react-native-cloud-storage"
|
||||
react-native-image-editor:
|
||||
:path: "../node_modules/@react-native-community/image-editor"
|
||||
react-native-location:
|
||||
:path: "../node_modules/react-native-location"
|
||||
react-native-mmkv-storage:
|
||||
@@ -840,6 +933,7 @@ SPEC CHECKSUMS:
|
||||
EXBarCodeScanner: 8e23fae8d267dbef9f04817833a494200f1fce35
|
||||
EXCamera: 2dc2bd2828bca4e283018a0b5a84aec6639ff0b4
|
||||
EXConstants: ce5bbea779da8031ac818c36bea41b10e14d04e1
|
||||
EXFaceDetector: 7ea53dbcdeac3f98f409a43fe31ca22195efc900
|
||||
EXFileSystem: f8b838a880254de42a5a7da20ed5ce12e2697c1b
|
||||
EXFont: 6ea3800df746be7233208d80fe379b8ed74f4272
|
||||
EXImageLoader: fd053169a8ee932dd83bf1fe5487a50c26d27c2b
|
||||
@@ -860,18 +954,31 @@ SPEC CHECKSUMS:
|
||||
FBReactNativeSpec: 0d9a4f4de7ab614c49e98c00aedfd3bfbda33d59
|
||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
|
||||
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
|
||||
GoogleMLKit: 755661c46990a85e42278015f26400286d98ad95
|
||||
GoogleSignIn: b232380cf495a429b8095d3178a8d5855b42e842
|
||||
GoogleToolboxForMac: 8bef7c7c5cf7291c687cf5354f39f9db6399ad34
|
||||
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
|
||||
GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe
|
||||
GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae
|
||||
GTMSessionFetcher: 41b9ef0b4c08a6db4b7eb51a21ae5183ec99a2c8
|
||||
GTMSessionFetcher: 5595ec75acf5be50814f81e9189490412bad82ba
|
||||
GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
|
||||
hermes-engine: 47986d26692ae75ee7a17ab049caee8864f855de
|
||||
ImageColors: 88be684570585c07ae2750bff34eb7b78bfc53b4
|
||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||
MLImage: a454f9f8ecfd537783a12f9488f5be1a68820829
|
||||
MLKitCommon: 3bc17c6f7d25ce3660f030350b46ae7ec9ebca6e
|
||||
MLKitFaceDetection: 617cb847441868a8bfd4b48d751c9b33c1104948
|
||||
MLKitVision: e87dc3f2e456a6ab32361ebd985e078dd2746143
|
||||
MMKV: aac95d817a100479445633f2b3ed8961b4ac5043
|
||||
MMKVCore: 9cfef4c48c6c46f66226fc2e4634d78490206a48
|
||||
nanopb: 438bc412db1928dac798aa6fd75726007be04262
|
||||
Permission-BluetoothPeripheral: 2b88a131074edafd8a46a5cda4ba610ec986d2fb
|
||||
Permission-Camera: 7ec9ee99704766ff9b90198183387a7f5d82b0c1
|
||||
Permission-LocationAccuracy: a38ddb5c5d0b8e656f3c86e4a500f9bb88bc099d
|
||||
Permission-LocationWhenInUse: 24d97eeb25d8ff9f2232e070f792eeb1360ccaf0
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
Protobuf: 2c02b2e18bb9040d3bf3fd2cecaefb2a8b55265b
|
||||
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
|
||||
RCTRequired: 8af6a32dfc2b65ec82193c2dee6e1011ff22ac2a
|
||||
RCTTypeSafety: bee9dd161c175896c680d47ef1d9eaacf2b587f4
|
||||
@@ -889,6 +996,7 @@ SPEC CHECKSUMS:
|
||||
React-logger: 342f358b8decfbf8f272367f4eacf4b6154061be
|
||||
react-native-app-auth: 1d12b6874a24152715a381d8e9149398ce7c2c95
|
||||
react-native-cloud-storage: 4a4726995158d9d45bc6c4a27fd83ab4d0673632
|
||||
react-native-image-editor: a60c74bc79f6101b7863c092aa53726e9e40a327
|
||||
react-native-location: 5a40ec1cc6abf2f6d94df979f98ec76c3a415681
|
||||
react-native-mmkv-storage: cfb6854594cfdc5f7383a9e464bb025417d1721c
|
||||
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
||||
@@ -934,6 +1042,6 @@ SPEC CHECKSUMS:
|
||||
Yoga: 065f0b74dba4832d6e328238de46eb72c5de9556
|
||||
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
|
||||
|
||||
PODFILE CHECKSUM: 90dfa5dbb6ca0103a4e49014140523aff5458f68
|
||||
PODFILE CHECKSUM: ef6fb41f40316f97c32e0b8b38aa320e6f8acf15
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
"Wallet": "محفظة"
|
||||
},
|
||||
"FaceScanner": {
|
||||
"livenessCaptureGuide": "أمسك الهاتف بثبات، وحافظ على تركيز وجهك في المنتصف.",
|
||||
"faceProcessingInfo": "يرجى الانتظار بينما نقوم بمعالجة البيانات.",
|
||||
"faceOutGuide": "أبقِ وجهك داخل الشكل البيضاوي!",
|
||||
"faceInGuide": "جاري الالتقاط!",
|
||||
"cancel": "يلغي",
|
||||
"imageCaptureGuide": "أمسك الهاتف بثبات، وحافظ على تركيز وجهك في المنتصف وانقر على ‘التقاط'",
|
||||
"capture": "يأسر",
|
||||
"flipCamera": "فليب الكاميرا"
|
||||
|
||||
@@ -25,8 +25,13 @@
|
||||
"Wallet": "Wallet"
|
||||
},
|
||||
"FaceScanner": {
|
||||
"livenessCaptureGuide": "Hold the phone steady, keep your face focused in the centre.",
|
||||
"faceProcessingInfo": "Please wait while we process the data.",
|
||||
"faceOutGuide": "Keep your face inside the oval!",
|
||||
"faceInGuide": "Capturing in progress!",
|
||||
"imageCaptureGuide": "Hold the phone steady, keep your face focused in the centre and click on Capture.",
|
||||
"capture": "Capture",
|
||||
"cancel": "Cancel",
|
||||
"flipCamera": "Flip Camera"
|
||||
},
|
||||
"OIDcAuth": {
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
"passcodeMismatchError": "Hindi tumugma ang passcode."
|
||||
},
|
||||
"FaceScanner": {
|
||||
"livenessCaptureGuide": "Hawakan nang matatag ang telepono, panatilihing nakatutok ang iyong mukha sa gitna.",
|
||||
"faceProcessingInfo": "Mangyaring maghintay habang pinoproseso namin ang data.",
|
||||
"faceOutGuide": "Panatilihin ang iyong mukha sa loob ng oval!",
|
||||
"faceInGuide": "Kasalukuyang kumukuha!",
|
||||
"cancel": "Kanselahin",
|
||||
"imageCaptureGuide": "Hawakan nang matatag ang telepono, panatilihing nakatutok ang iyong mukha sa gitna at mag-click sa Capture.",
|
||||
"capture": "Kunin",
|
||||
"flipCamera": "I-flip ang Camera"
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
"passcodeMismatchError": "पासकोड का मिलान नहीं हुआ।"
|
||||
},
|
||||
"FaceScanner": {
|
||||
"livenessCaptureGuide": "फ़ोन को स्थिर रखें, अपना चेहरा केंद्र में रखें।",
|
||||
"faceProcessingInfo": "जब तक हम डेटा संसाधित कर रहे हैं कृपया प्रतीक्षा करें।",
|
||||
"faceOutGuide": "अपना चेहरा अंडाकार के अंदर रखें!",
|
||||
"faceInGuide": "कैप्चरिंग प्रगति पर है!",
|
||||
"cancel": "रद्द करना",
|
||||
"imageCaptureGuide": "फ़ोन को स्थिर रखें, अपना चेहरा केंद्र में रखें और कैप्चर पर क्लिक करें।",
|
||||
"capture": "कब्जा",
|
||||
"flipCamera": "कैमरा पलटें"
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
"passcodeMismatchError": "ಪಾಸ್ಕೋಡ್ ಹೊಂದಿಕೆಯಾಗಲಿಲ್ಲ."
|
||||
},
|
||||
"FaceScanner": {
|
||||
"livenessCaptureGuide": "ಫೋನ್ ಅನ್ನು ಸ್ಥಿರವಾಗಿ ಹಿಡಿದುಕೊಳ್ಳಿ, ನಿಮ್ಮ ಮುಖವನ್ನು ಮಧ್ಯದಲ್ಲಿ ಕೇಂದ್ರೀಕರಿಸಿ.",
|
||||
"faceProcessingInfo": "ನಾವು ಡೇಟಾವನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುವಾಗ ದಯವಿಟ್ಟು ನಿರೀಕ್ಷಿಸಿ.",
|
||||
"faceOutGuide": "ನಿಮ್ಮ ಮುಖವನ್ನು ಅಂಡಾಕಾರದೊಳಗೆ ಇರಿಸಿ!",
|
||||
"faceInGuide": "ಸೆರೆಹಿಡಿಯುವಿಕೆ ಪ್ರಗತಿಯಲ್ಲಿದೆ!",
|
||||
"cancel": "ರದ್ದುಮಾಡು",
|
||||
"imageCaptureGuide": "ಫೋನ್ ಅನ್ನು ಸ್ಥಿರವಾಗಿ ಹಿಡಿದುಕೊಳ್ಳಿ, ನಿಮ್ಮ ಮುಖವನ್ನು ಮಧ್ಯದಲ್ಲಿ ಕೇಂದ್ರೀಕರಿಸಿ ಮತ್ತು ಕ್ಯಾಪ್ಚರ್ ಕ್ಲಿಕ್ ಮಾಡಿ.",
|
||||
"capture": "ಸೆರೆಹಿಡಿಯಿರಿ",
|
||||
"flipCamera": "ಫ್ಲಿಪ್ ಕ್ಯಾಮೆರಾ"
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
"passcodeMismatchError": "கடவுக்குறியீடு பொருந்தவில்லை."
|
||||
},
|
||||
"FaceScanner": {
|
||||
"livenessCaptureGuide": "மொபைலை நிலையாகப் பிடித்து, உங்கள் முகத்தை மையமாக வைத்துக்கொள்ளவும்.",
|
||||
"faceProcessingInfo": "நாங்கள் தரவைச் செயலாக்கும் வரை காத்திருக்கவும்.",
|
||||
"faceOutGuide": "உங்கள் முகத்தை ஓவலின் உள்ளே வைத்திருங்கள்!",
|
||||
"faceInGuide": "பிடிப்பு நடைபெறுகிறது!",
|
||||
"cancel": "ரத்து செய்",
|
||||
"imageCaptureGuide": "மொபைலை நிலையாகப் பிடித்து, உங்கள் முகத்தை மையமாக வைத்து, பிடிப்பு என்பதைக் கிளிக் செய்யவும்.",
|
||||
"capture": "பிடிப்பு",
|
||||
"flipCamera": "ஃபிளிப் கேமரா"
|
||||
|
||||
@@ -240,6 +240,10 @@ export function selectWhichCamera(state: State) {
|
||||
return state.context.whichCamera;
|
||||
}
|
||||
|
||||
export function selectCameraRef(state: State) {
|
||||
return state.context.cameraRef;
|
||||
}
|
||||
|
||||
export function selectCapturedImage(state: State) {
|
||||
return state.context.capturedImage;
|
||||
}
|
||||
|
||||
@@ -1,71 +1,47 @@
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
internalEvents: {
|
||||
'done.invoke.faceScanner.capturing:invocation[0]': {
|
||||
type: 'done.invoke.faceScanner.capturing:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.faceScanner.verifying:invocation[0]': {
|
||||
type: 'done.invoke.faceScanner.verifying:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'error.platform.faceScanner.capturing:invocation[0]': {
|
||||
type: 'error.platform.faceScanner.capturing:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'xstate.init': {type: 'xstate.init'};
|
||||
};
|
||||
invokeSrcNameMap: {
|
||||
captureImage: 'done.invoke.faceScanner.capturing:invocation[0]';
|
||||
checkPermission: 'done.invoke.faceScanner.init.checkingPermission:invocation[0]';
|
||||
requestPermission: 'done.invoke.faceScanner.init.requestingPermission:invocation[0]';
|
||||
verifyImage: 'done.invoke.faceScanner.verifying:invocation[0]';
|
||||
};
|
||||
missingImplementations: {
|
||||
actions: never;
|
||||
delays: never;
|
||||
guards: never;
|
||||
services: never;
|
||||
};
|
||||
eventsCausingActions: {
|
||||
flipWhichCamera: 'FLIP_CAMERA';
|
||||
openSettings: 'OPEN_SETTINGS';
|
||||
setCameraRef: 'READY';
|
||||
setCaptureError: 'error.platform.faceScanner.capturing:invocation[0]';
|
||||
setCapturedImage: 'done.invoke.faceScanner.capturing:invocation[0]';
|
||||
};
|
||||
eventsCausingDelays: {};
|
||||
eventsCausingGuards: {
|
||||
canRequestPermission: 'DENIED';
|
||||
doesFaceMatch: 'done.invoke.faceScanner.verifying:invocation[0]';
|
||||
};
|
||||
eventsCausingServices: {
|
||||
captureImage: 'CAPTURE';
|
||||
checkPermission: 'APP_FOCUSED' | 'xstate.init';
|
||||
requestPermission: 'DENIED';
|
||||
verifyImage: 'done.invoke.faceScanner.capturing:invocation[0]';
|
||||
};
|
||||
matchesStates:
|
||||
| 'capturing'
|
||||
| 'init'
|
||||
| 'init.checkingPermission'
|
||||
| 'init.permissionDenied'
|
||||
| 'init.permissionGranted'
|
||||
| 'init.requestingPermission'
|
||||
| 'invalid'
|
||||
| 'scanning'
|
||||
| 'valid'
|
||||
| 'verifying'
|
||||
| {
|
||||
init?:
|
||||
| 'checkingPermission'
|
||||
| 'permissionDenied'
|
||||
| 'permissionGranted'
|
||||
| 'requestingPermission';
|
||||
};
|
||||
tags: never;
|
||||
}
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
internalEvents: {
|
||||
"done.invoke.faceScanner.capturing:invocation[0]": { type: "done.invoke.faceScanner.capturing:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
||||
"done.invoke.faceScanner.verifying:invocation[0]": { type: "done.invoke.faceScanner.verifying:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
||||
"error.platform.faceScanner.capturing:invocation[0]": { type: "error.platform.faceScanner.capturing:invocation[0]"; data: unknown };
|
||||
"xstate.init": { type: "xstate.init" };
|
||||
};
|
||||
invokeSrcNameMap: {
|
||||
"captureImage": "done.invoke.faceScanner.capturing:invocation[0]";
|
||||
"checkPermission": "done.invoke.faceScanner.init.checkingPermission:invocation[0]";
|
||||
"requestPermission": "done.invoke.faceScanner.init.requestingPermission:invocation[0]";
|
||||
"verifyImage": "done.invoke.faceScanner.verifying:invocation[0]";
|
||||
};
|
||||
missingImplementations: {
|
||||
actions: never;
|
||||
delays: never;
|
||||
guards: never;
|
||||
services: never;
|
||||
};
|
||||
eventsCausingActions: {
|
||||
"flipWhichCamera": "FLIP_CAMERA";
|
||||
"openSettings": "OPEN_SETTINGS";
|
||||
"setCameraRef": "READY";
|
||||
"setCaptureError": "error.platform.faceScanner.capturing:invocation[0]";
|
||||
"setCapturedImage": "done.invoke.faceScanner.capturing:invocation[0]";
|
||||
};
|
||||
eventsCausingDelays: {
|
||||
|
||||
};
|
||||
eventsCausingGuards: {
|
||||
"canRequestPermission": "DENIED";
|
||||
"doesFaceMatch": "done.invoke.faceScanner.verifying:invocation[0]";
|
||||
};
|
||||
eventsCausingServices: {
|
||||
"captureImage": "CAPTURE";
|
||||
"checkPermission": "APP_FOCUSED" | "xstate.init";
|
||||
"requestPermission": "DENIED";
|
||||
"verifyImage": "done.invoke.faceScanner.capturing:invocation[0]";
|
||||
};
|
||||
matchesStates: "capturing" | "init" | "init.checkingPermission" | "init.permissionDenied" | "init.permissionGranted" | "init.requestingPermission" | "invalid" | "scanning" | "valid" | "verifying" | { "init"?: "checkingPermission" | "permissionDenied" | "permissionGranted" | "requestingPermission"; };
|
||||
tags: never;
|
||||
}
|
||||
|
||||
@@ -390,4 +390,4 @@ export function selectIsPasscodeUnlock(state: State) {
|
||||
return (
|
||||
state.context.isBiometricToggled && !state.context.isBiometricUnlockEnabled
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,53 @@
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
internalEvents: {
|
||||
'done.invoke.settings.resetInjiProps:invocation[0]': {
|
||||
type: 'done.invoke.settings.resetInjiProps:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'error.platform.settings.resetInjiProps:invocation[0]': {
|
||||
type: 'error.platform.settings.resetInjiProps:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'xstate.init': {type: 'xstate.init'};
|
||||
};
|
||||
invokeSrcNameMap: {
|
||||
resetInjiProps: 'done.invoke.settings.resetInjiProps:invocation[0]';
|
||||
};
|
||||
missingImplementations: {
|
||||
actions: never;
|
||||
delays: never;
|
||||
guards: never;
|
||||
services: never;
|
||||
};
|
||||
eventsCausingActions: {
|
||||
requestStoredContext: 'xstate.init';
|
||||
resetCredentialRegistryResponse: 'CANCEL' | 'UPDATE_HOST';
|
||||
resetIsBiometricToggled: 'DISMISS';
|
||||
setBackupAndRestoreOptionExplored: 'SET_IS_BACKUP_AND_RESTORE_EXPLORED';
|
||||
setContext: 'STORE_RESPONSE';
|
||||
setIsBiometricToggled: 'TOGGLE_BIOMETRIC_UNLOCK';
|
||||
storeContext:
|
||||
| 'ACCEPT_HARDWARE_SUPPORT_NOT_EXISTS'
|
||||
| 'SET_IS_BACKUP_AND_RESTORE_EXPLORED'
|
||||
| 'SHOWN_ACCOUNT_SELECTION_CONFIRMATION'
|
||||
| 'STORE_RESPONSE'
|
||||
| 'TOGGLE_BIOMETRIC_UNLOCK'
|
||||
| 'UPDATE_HOST'
|
||||
| 'UPDATE_NAME'
|
||||
| 'UPDATE_VC_LABEL'
|
||||
| 'done.invoke.settings.resetInjiProps:invocation[0]';
|
||||
toggleBiometricUnlock: 'TOGGLE_BIOMETRIC_UNLOCK';
|
||||
updateCredentialRegistry: 'done.invoke.settings.resetInjiProps:invocation[0]';
|
||||
updateCredentialRegistryResponse: 'error.platform.settings.resetInjiProps:invocation[0]';
|
||||
updateCredentialRegistrySuccess: 'done.invoke.settings.resetInjiProps:invocation[0]';
|
||||
updateDefaults: 'STORE_RESPONSE';
|
||||
updateEsignetHostUrl: 'UPDATE_HOST';
|
||||
updateIsAccountSelectionConfirmationShown: 'SHOWN_ACCOUNT_SELECTION_CONFIRMATION';
|
||||
updateName: 'UPDATE_NAME';
|
||||
updatePartialDefaults: 'STORE_RESPONSE';
|
||||
updateUserShownWithHardwareKeystoreNotExists: 'ACCEPT_HARDWARE_SUPPORT_NOT_EXISTS';
|
||||
updateVcLabel: 'UPDATE_VC_LABEL';
|
||||
};
|
||||
eventsCausingDelays: {};
|
||||
eventsCausingGuards: {
|
||||
hasData: 'STORE_RESPONSE';
|
||||
hasPartialData: 'STORE_RESPONSE';
|
||||
};
|
||||
eventsCausingServices: {
|
||||
resetInjiProps: 'UPDATE_HOST';
|
||||
};
|
||||
matchesStates:
|
||||
| 'idle'
|
||||
| 'init'
|
||||
| 'resetInjiProps'
|
||||
| 'showInjiTourGuide'
|
||||
| 'storingDefaults';
|
||||
tags: never;
|
||||
}
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
internalEvents: {
|
||||
"done.invoke.settings.resetInjiProps:invocation[0]": { type: "done.invoke.settings.resetInjiProps:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
||||
"error.platform.settings.resetInjiProps:invocation[0]": { type: "error.platform.settings.resetInjiProps:invocation[0]"; data: unknown };
|
||||
"xstate.init": { type: "xstate.init" };
|
||||
};
|
||||
invokeSrcNameMap: {
|
||||
"resetInjiProps": "done.invoke.settings.resetInjiProps:invocation[0]";
|
||||
};
|
||||
missingImplementations: {
|
||||
actions: never;
|
||||
delays: never;
|
||||
guards: never;
|
||||
services: never;
|
||||
};
|
||||
eventsCausingActions: {
|
||||
"requestStoredContext": "xstate.init";
|
||||
"resetCredentialRegistryResponse": "CANCEL" | "UPDATE_HOST";
|
||||
"resetIsBiometricToggled": "DISMISS";
|
||||
"setBackupAndRestoreOptionExplored": "SET_IS_BACKUP_AND_RESTORE_EXPLORED";
|
||||
"setContext": "STORE_RESPONSE";
|
||||
"setIsBiometricToggled": "TOGGLE_BIOMETRIC_UNLOCK";
|
||||
"storeContext": "ACCEPT_HARDWARE_SUPPORT_NOT_EXISTS" | "SET_IS_BACKUP_AND_RESTORE_EXPLORED" | "SHOWN_ACCOUNT_SELECTION_CONFIRMATION" | "STORE_RESPONSE" | "TOGGLE_BIOMETRIC_UNLOCK" | "UPDATE_HOST" | "UPDATE_NAME" | "UPDATE_VC_LABEL" | "done.invoke.settings.resetInjiProps:invocation[0]";
|
||||
"toggleBiometricUnlock": "TOGGLE_BIOMETRIC_UNLOCK";
|
||||
"updateCredentialRegistry": "done.invoke.settings.resetInjiProps:invocation[0]";
|
||||
"updateCredentialRegistryResponse": "error.platform.settings.resetInjiProps:invocation[0]";
|
||||
"updateCredentialRegistrySuccess": "done.invoke.settings.resetInjiProps:invocation[0]";
|
||||
"updateDefaults": "STORE_RESPONSE";
|
||||
"updateEsignetHostUrl": "UPDATE_HOST";
|
||||
"updateIsAccountSelectionConfirmationShown": "SHOWN_ACCOUNT_SELECTION_CONFIRMATION";
|
||||
"updateName": "UPDATE_NAME";
|
||||
"updatePartialDefaults": "STORE_RESPONSE";
|
||||
"updateUserShownWithHardwareKeystoreNotExists": "ACCEPT_HARDWARE_SUPPORT_NOT_EXISTS";
|
||||
"updateVcLabel": "UPDATE_VC_LABEL";
|
||||
};
|
||||
eventsCausingDelays: {
|
||||
|
||||
};
|
||||
eventsCausingGuards: {
|
||||
"hasData": "STORE_RESPONSE";
|
||||
"hasPartialData": "STORE_RESPONSE";
|
||||
};
|
||||
eventsCausingServices: {
|
||||
"resetInjiProps": "UPDATE_HOST";
|
||||
};
|
||||
matchesStates: "idle" | "init" | "resetInjiProps" | "showInjiTourGuide" | "storingDefaults";
|
||||
tags: never;
|
||||
}
|
||||
|
||||
1124
package-lock.json
generated
1124
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@
|
||||
"@react-native-clipboard/clipboard": "^1.10.0",
|
||||
"@react-native-community/netinfo": "9.3.7",
|
||||
"@react-native-google-signin/google-signin": "^10.1.1",
|
||||
"@react-native-community/image-editor": "^4.2.0",
|
||||
"@react-native-picker/picker": "2.4.8",
|
||||
"@react-navigation/bottom-tabs": "^6.0.7",
|
||||
"@react-navigation/native": "^6.0.8",
|
||||
@@ -36,6 +37,7 @@
|
||||
"buffer": "^6.0.3",
|
||||
"date-fns": "^2.26.0",
|
||||
"expo": "~49.0.23",
|
||||
"hex-rgb": "^5.0.0",
|
||||
"expo-auth-session": "^5.2.0",
|
||||
"expo-barcode-scanner": "~12.3.2",
|
||||
"expo-camera": "^13.6.0",
|
||||
@@ -45,8 +47,10 @@
|
||||
"expo-localization": "~14.1.1",
|
||||
"expo-modules-autolinking": "~1.5.0",
|
||||
"expo-updates": "^0.18.17",
|
||||
"expo-face-detector": "12.4.0",
|
||||
"expo-web-browser": "^12.5.0",
|
||||
"i18next": "^21.6.16",
|
||||
"color-diff": "^1.4.0",
|
||||
"iso-639-3": "^3.0.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"node-forge": "^1.3.1",
|
||||
@@ -74,6 +78,7 @@
|
||||
"react-native-linear-gradient": "^2.8.0",
|
||||
"react-native-localize": "^3.0.2",
|
||||
"react-native-location": "^2.5.0",
|
||||
"react-native-image-colors": "^2.4.0",
|
||||
"react-native-mmkv-storage": "^0.9.1",
|
||||
"react-native-permissions": "^3.8.0",
|
||||
"react-native-popable": "^0.4.3",
|
||||
|
||||
@@ -11,8 +11,8 @@ import {QrLoginRef} from '../../machines/QrLogin/QrLoginMachine';
|
||||
import {Icon} from 'react-native-elements';
|
||||
import {View} from 'react-native';
|
||||
import {FaceVerificationAlertOverlay} from '../Scan/FaceVerificationAlertOverlay';
|
||||
import {Error} from '../../components/ui/Error';
|
||||
import {SvgImage} from '../../components/ui/svg';
|
||||
import { LIVENESS_CHECK } from '../../shared/constants';
|
||||
|
||||
export const QrLogin: React.FC<QrLoginProps> = props => {
|
||||
const controller = useQrLogin(props);
|
||||
@@ -59,6 +59,7 @@ export const QrLogin: React.FC<QrLoginProps> = props => {
|
||||
isInvalidIdentity={controller.isInvalidIdentity}
|
||||
onNavigateHome={controller.GO_TO_HOME}
|
||||
onRetryVerification={controller.RETRY_VERIFICATION}
|
||||
isLivenessEnabled={LIVENESS_CHECK}
|
||||
/>
|
||||
|
||||
<FaceVerificationAlertOverlay
|
||||
|
||||
@@ -15,6 +15,7 @@ import {SvgImage} from '../../components/ui/svg';
|
||||
import {View, I18nManager} from 'react-native';
|
||||
import {Text} from './../../components/ui';
|
||||
import {BannerStatusType} from '../../components/BannerNotification';
|
||||
import { LIVENESS_CHECK } from '../../shared/constants';
|
||||
|
||||
const ScanStack = createNativeStackNavigator();
|
||||
|
||||
@@ -60,6 +61,7 @@ export const ScanLayout: React.FC = () => {
|
||||
isInvalidIdentity={controller.isInvalidIdentity}
|
||||
onNavigateHome={controller.GOTO_HOME}
|
||||
onRetryVerification={controller.RETRY_VERIFICATION}
|
||||
isLivenessEnabled={LIVENESS_CHECK}
|
||||
/>
|
||||
<ScanStack.Navigator initialRouteName="ScanScreen">
|
||||
{controller.isReviewing &&
|
||||
|
||||
@@ -23,6 +23,7 @@ import {Issuers} from '../../shared/openId4VCI/Utils';
|
||||
import {FaceVerificationAlertOverlay} from './FaceVerificationAlertOverlay';
|
||||
import {Error} from '../../components/ui/Error';
|
||||
import {SvgImage} from '../../components/ui/svg';
|
||||
import { LIVENESS_CHECK } from '../../shared/constants';
|
||||
|
||||
export const SendVcScreen: React.FC = () => {
|
||||
const {t} = useTranslation('SendVcScreen');
|
||||
@@ -140,6 +141,7 @@ export const SendVcScreen: React.FC = () => {
|
||||
isInvalidIdentity={controller.isInvalidIdentity}
|
||||
onNavigateHome={controller.GO_TO_HOME}
|
||||
onRetryVerification={controller.RETRY_VERIFICATION}
|
||||
isLivenessEnabled={LIVENESS_CHECK}
|
||||
/>
|
||||
|
||||
<FaceVerificationAlertOverlay
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import {FaceScanner} from '../components/FaceScanner';
|
||||
import {FaceScanner} from '../components/FaceScanner/FaceScanner';
|
||||
import {Column} from '../components/ui';
|
||||
import {Theme} from '../components/ui/styleUtils';
|
||||
import {VerifiableCredential} from '../machines/VerifiableCredential/VCMetaMachine/vc';
|
||||
@@ -15,13 +15,28 @@ export const VerifyIdentityOverlay: React.FC<
|
||||
const credential = props.credential;
|
||||
const vcImage = props.verifiableCredentialData.face;
|
||||
|
||||
const modalProps = {
|
||||
isVisible: props.isVerifyingIdentity,
|
||||
onDismiss: props.onCancel,
|
||||
animationType: 'slide',
|
||||
arrowLeft: true,
|
||||
headerTitle: t('faceAuth'),
|
||||
presentationStyle: "overFullScreen",
|
||||
showClose: true,
|
||||
showHeader: true,
|
||||
};
|
||||
|
||||
if (props.isLivenessEnabled) {
|
||||
modalProps.arrowLeft = false;
|
||||
modalProps.headerTitle = '';
|
||||
modalProps.showClose = false;
|
||||
modalProps.showHeader = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
isVisible={props.isVerifyingIdentity}
|
||||
arrowLeft={true}
|
||||
headerTitle={t('faceAuth')}
|
||||
onDismiss={props.onCancel}>
|
||||
{...modalProps}>
|
||||
<Column
|
||||
fill
|
||||
style={Theme.VerifyIdentityOverlayStyles.content}
|
||||
@@ -31,6 +46,8 @@ export const VerifyIdentityOverlay: React.FC<
|
||||
vcImage={vcImage}
|
||||
onValid={props.onFaceValid}
|
||||
onInvalid={props.onFaceInvalid}
|
||||
isLiveness={props.isLivenessEnabled}
|
||||
onCancel={props.onCancel}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
@@ -68,4 +85,5 @@ export interface VerifyIdentityOverlayProps {
|
||||
isInvalidIdentity: boolean;
|
||||
onNavigateHome: () => void;
|
||||
onRetryVerification: () => void;
|
||||
isLivenessEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@ export const generateRandomString = async () => {
|
||||
);
|
||||
return randomString;
|
||||
};
|
||||
export const getRandomInt = (min, max) => {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
export const generateBackupEncryptionKey = (
|
||||
password: string,
|
||||
salt: string,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Dimensions, Platform} from 'react-native';
|
||||
import {DEBUG_MODE, ESIGNET_HOST, MIMOTO_HOST} from 'react-native-dotenv';
|
||||
import {DEBUG_MODE, ESIGNET_HOST, MIMOTO_HOST, LIVENESS_DETECTION} from 'react-native-dotenv';
|
||||
import {Argon2iConfig} from './commonUtil';
|
||||
import {VcIdType} from '../machines/VerifiableCredential/VCMetaMachine/vc';
|
||||
|
||||
@@ -7,6 +7,9 @@ export let MIMOTO_BASE_URL = MIMOTO_HOST;
|
||||
export let ESIGNET_BASE_URL = ESIGNET_HOST;
|
||||
export let DEBUG_MODE_ENABLED = DEBUG_MODE === 'true';
|
||||
|
||||
export const LIVENESS_CHECK = LIVENESS_DETECTION === 'true';
|
||||
export const LIVENESS_THRESHOLD = 0.4;
|
||||
|
||||
export const changeCrendetialRegistry = (host: string) =>
|
||||
(MIMOTO_BASE_URL = host);
|
||||
export const changeEsignetUrl = (host: string) => (ESIGNET_BASE_URL = host);
|
||||
|
||||
5
types/react-native-dotenv/index.d.ts
vendored
5
types/react-native-dotenv/index.d.ts
vendored
@@ -39,4 +39,9 @@ declare module 'react-native-dotenv' {
|
||||
*/
|
||||
export const DEBUG_MODE: string;
|
||||
export const GOOGLE_ANDROID_CLIENT_ID: string;
|
||||
|
||||
/**
|
||||
* Flag for Toggling for Liveness Detection
|
||||
*/
|
||||
export const LIVENESS_DETECTION: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user