[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:
adityankannan-tw
2024-06-04 13:59:02 +05:30
committed by GitHub
parent d1a9a3c7f5
commit 339e08c462
35 changed files with 2095 additions and 364 deletions

3
.env
View File

@@ -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

View File

@@ -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: '.'

View File

@@ -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: '.'

View File

@@ -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: ""

View File

@@ -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;
}

View 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;
}

View 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;
}

View 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;
};

View 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;
}

View File

@@ -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;

View File

@@ -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',

View File

@@ -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',

View File

@@ -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 */

View File

@@ -29,4 +29,4 @@
}
],
"version" : 2
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -25,6 +25,11 @@
"Wallet": "محفظة"
},
"FaceScanner": {
"livenessCaptureGuide": "أمسك الهاتف بثبات، وحافظ على تركيز وجهك في المنتصف.",
"faceProcessingInfo": "يرجى الانتظار بينما نقوم بمعالجة البيانات.",
"faceOutGuide": "أبقِ وجهك داخل الشكل البيضاوي!",
"faceInGuide": "جاري الالتقاط!",
"cancel": "يلغي",
"imageCaptureGuide": "أمسك الهاتف بثبات، وحافظ على تركيز وجهك في المنتصف وانقر على ‘التقاط'",
"capture": "يأسر",
"flipCamera": "فليب الكاميرا"

View File

@@ -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": {

View File

@@ -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"

View File

@@ -28,6 +28,11 @@
"passcodeMismatchError": "पासकोड का मिलान नहीं हुआ।"
},
"FaceScanner": {
"livenessCaptureGuide": "फ़ोन को स्थिर रखें, अपना चेहरा केंद्र में रखें।",
"faceProcessingInfo": "जब तक हम डेटा संसाधित कर रहे हैं कृपया प्रतीक्षा करें।",
"faceOutGuide": "अपना चेहरा अंडाकार के अंदर रखें!",
"faceInGuide": "कैप्चरिंग प्रगति पर है!",
"cancel": "रद्द करना",
"imageCaptureGuide": "फ़ोन को स्थिर रखें, अपना चेहरा केंद्र में रखें और कैप्चर पर क्लिक करें।",
"capture": "कब्जा",
"flipCamera": "कैमरा पलटें"

View File

@@ -28,6 +28,11 @@
"passcodeMismatchError": "ಪಾಸ್ಕೋಡ್ ಹೊಂದಿಕೆಯಾಗಲಿಲ್ಲ."
},
"FaceScanner": {
"livenessCaptureGuide": "ಫೋನ್ ಅನ್ನು ಸ್ಥಿರವಾಗಿ ಹಿಡಿದುಕೊಳ್ಳಿ, ನಿಮ್ಮ ಮುಖವನ್ನು ಮಧ್ಯದಲ್ಲಿ ಕೇಂದ್ರೀಕರಿಸಿ.",
"faceProcessingInfo": "ನಾವು ಡೇಟಾವನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುವಾಗ ದಯವಿಟ್ಟು ನಿರೀಕ್ಷಿಸಿ.",
"faceOutGuide": "ನಿಮ್ಮ ಮುಖವನ್ನು ಅಂಡಾಕಾರದೊಳಗೆ ಇರಿಸಿ!",
"faceInGuide": "ಸೆರೆಹಿಡಿಯುವಿಕೆ ಪ್ರಗತಿಯಲ್ಲಿದೆ!",
"cancel": "ರದ್ದುಮಾಡು",
"imageCaptureGuide": "ಫೋನ್ ಅನ್ನು ಸ್ಥಿರವಾಗಿ ಹಿಡಿದುಕೊಳ್ಳಿ, ನಿಮ್ಮ ಮುಖವನ್ನು ಮಧ್ಯದಲ್ಲಿ ಕೇಂದ್ರೀಕರಿಸಿ ಮತ್ತು ಕ್ಯಾಪ್ಚರ್ ಕ್ಲಿಕ್ ಮಾಡಿ.",
"capture": "ಸೆರೆಹಿಡಿಯಿರಿ",
"flipCamera": "ಫ್ಲಿಪ್ ಕ್ಯಾಮೆರಾ"

View File

@@ -28,6 +28,11 @@
"passcodeMismatchError": "கடவுக்குறியீடு பொருந்தவில்லை."
},
"FaceScanner": {
"livenessCaptureGuide": "மொபைலை நிலையாகப் பிடித்து, உங்கள் முகத்தை மையமாக வைத்துக்கொள்ளவும்.",
"faceProcessingInfo": "நாங்கள் தரவைச் செயலாக்கும் வரை காத்திருக்கவும்.",
"faceOutGuide": "உங்கள் முகத்தை ஓவலின் உள்ளே வைத்திருங்கள்!",
"faceInGuide": "பிடிப்பு நடைபெறுகிறது!",
"cancel": "ரத்து செய்",
"imageCaptureGuide": "மொபைலை நிலையாகப் பிடித்து, உங்கள் முகத்தை மையமாக வைத்து, பிடிப்பு என்பதைக் கிளிக் செய்யவும்.",
"capture": "பிடிப்பு",
"flipCamera": "ஃபிளிப் கேமரா"

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -390,4 +390,4 @@ export function selectIsPasscodeUnlock(state: State) {
return (
state.context.isBiometricToggled && !state.context.isBiometricUnlockEnabled
);
}
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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

View File

@@ -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 &&

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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;
}