@aws-amplify/ui-react-liveness 3.0.7 → 3.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.mjs +28 -17
- package/dist/esm/components/FaceLivenessDetector/displayText.mjs +5 -0
- package/dist/esm/components/FaceLivenessDetector/service/types/liveness.mjs +1 -0
- package/dist/esm/components/FaceLivenessDetector/service/utils/liveness.mjs +16 -1
- package/dist/esm/components/FaceLivenessDetector/shared/FaceLivenessErrorModal.mjs +2 -2
- package/dist/esm/components/FaceLivenessDetector/shared/Hint.mjs +66 -49
- package/dist/esm/components/FaceLivenessDetector/shared/ToastWithLoader.mjs +2 -2
- package/dist/esm/components/FaceLivenessDetector/utils/getDisplayText.mjs +6 -1
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +124 -70
- package/dist/styles.css +16 -4
- package/dist/types/components/FaceLivenessDetector/displayText.d.ts +23 -7
- package/dist/types/components/FaceLivenessDetector/service/types/liveness.d.ts +2 -1
- package/dist/types/components/FaceLivenessDetector/service/utils/liveness.d.ts +3 -0
- package/dist/types/components/FaceLivenessDetector/shared/FaceLivenessErrorModal.d.ts +1 -0
- package/dist/types/components/FaceLivenessDetector/shared/ToastWithLoader.d.ts +0 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +4 -4
|
@@ -9,7 +9,7 @@ import '@tensorflow-models/face-detection';
|
|
|
9
9
|
import '@tensorflow/tfjs-backend-wasm';
|
|
10
10
|
import '@tensorflow/tfjs-backend-cpu';
|
|
11
11
|
import '@aws-amplify/core/internals/utils';
|
|
12
|
-
import { drawStaticOval } from '../service/utils/liveness.mjs';
|
|
12
|
+
import { drawStaticOval, clearOvalCanvas } from '../service/utils/liveness.mjs';
|
|
13
13
|
import 'aws-amplify/auth';
|
|
14
14
|
import '@aws-sdk/client-rekognitionstreaming';
|
|
15
15
|
import '@aws-sdk/util-format-url';
|
|
@@ -38,6 +38,7 @@ const showMatchIndicatorStates = [
|
|
|
38
38
|
FaceMatchState.TOO_FAR,
|
|
39
39
|
FaceMatchState.CANT_IDENTIFY,
|
|
40
40
|
FaceMatchState.FACE_IDENTIFIED,
|
|
41
|
+
FaceMatchState.OFF_CENTER,
|
|
41
42
|
];
|
|
42
43
|
/**
|
|
43
44
|
* For now we want to memoize the HOC for MatchIndicator because to optimize renders
|
|
@@ -65,6 +66,7 @@ const LivenessCameraModule = (props) => {
|
|
|
65
66
|
const isCheckingCamera = state.matches('cameraCheck');
|
|
66
67
|
const isWaitingForCamera = state.matches('waitForDOMAndCameraDetails');
|
|
67
68
|
const isStartView = state.matches('start') || state.matches('userCancel');
|
|
69
|
+
const isDetectFaceBeforeStart = state.matches('detectFaceBeforeStart');
|
|
68
70
|
const isRecording = state.matches('recording');
|
|
69
71
|
const isCheckSucceeded = state.matches('checkSucceeded');
|
|
70
72
|
const isFlashingFreshness = state.matches({
|
|
@@ -125,6 +127,11 @@ const LivenessCameraModule = (props) => {
|
|
|
125
127
|
setAspectRatio(videoRef.current.videoWidth / videoRef.current.videoHeight);
|
|
126
128
|
}
|
|
127
129
|
}, [send, videoRef, isCameraReady, isMobileScreen]);
|
|
130
|
+
React__default.useEffect(() => {
|
|
131
|
+
if (isDetectFaceBeforeStart) {
|
|
132
|
+
clearOvalCanvas({ canvas: canvasRef.current });
|
|
133
|
+
}
|
|
134
|
+
}, [isDetectFaceBeforeStart]);
|
|
128
135
|
const photoSensitivityWarning = React__default.useMemo(() => {
|
|
129
136
|
return (React__default.createElement(View, { style: { visibility: isStartView ? 'visible' : 'hidden' } },
|
|
130
137
|
React__default.createElement(PhotosensitiveWarning, { bodyText: instructionDisplayText.photosensitivityWarningBodyText, headingText: instructionDisplayText.photosensitivityWarningHeadingText, infoText: instructionDisplayText.photosensitivityWarningInfoText, labelText: instructionDisplayText.photosensitivityWarningLabelText })));
|
|
@@ -159,31 +166,35 @@ const LivenessCameraModule = (props) => {
|
|
|
159
166
|
React__default.createElement(Loader, { size: "large", className: LivenessClassNames.Loader, "data-testid": "centered-loader", position: "unset" }),
|
|
160
167
|
React__default.createElement(Text, { fontSize: "large", fontWeight: "bold", "data-testid": "waiting-camera-permission", className: `${LivenessClassNames.StartScreenCameraWaiting}__text` }, cameraDisplayText.waitingCameraPermissionText)));
|
|
161
168
|
}
|
|
162
|
-
|
|
169
|
+
// We don't show full screen camera on the pre check screen (isStartView/isWaitingForCamera)
|
|
170
|
+
const shouldShowFullScreenCamera = isMobileScreen && !isStartView && !isWaitingForCamera;
|
|
163
171
|
return (React__default.createElement(React__default.Fragment, null,
|
|
164
172
|
photoSensitivityWarning,
|
|
165
|
-
React__default.createElement(Flex, { className: classNames(LivenessClassNames.CameraModule,
|
|
173
|
+
React__default.createElement(Flex, { className: classNames(LivenessClassNames.CameraModule, shouldShowFullScreenCamera &&
|
|
174
|
+
`${LivenessClassNames.CameraModule}--mobile`), "data-testid": testId, gap: "zero" },
|
|
166
175
|
!isCameraReady && centeredLoader,
|
|
176
|
+
React__default.createElement(Overlay, { horizontal: "center", vertical: isRecording && !isFlashingFreshness ? 'start' : 'space-between', className: LivenessClassNames.InstructionOverlay },
|
|
177
|
+
isRecording && (React__default.createElement(DefaultRecordingIcon, { recordingIndicatorText: recordingIndicatorText })),
|
|
178
|
+
!isStartView && !isWaitingForCamera && !isCheckSucceeded && (React__default.createElement(DefaultCancelButton, { cancelLivenessCheckText: cancelLivenessCheckText })),
|
|
179
|
+
React__default.createElement(Flex, { className: classNames(LivenessClassNames.Hint, shouldShowFullScreenCamera && `${LivenessClassNames.Hint}--mobile`) },
|
|
180
|
+
React__default.createElement(Hint, { hintDisplayText: hintDisplayText })),
|
|
181
|
+
errorState && (React__default.createElement(ErrorView, { onRetry: () => {
|
|
182
|
+
send({ type: 'CANCEL' });
|
|
183
|
+
}, displayText: errorDisplayText }, renderErrorModal({
|
|
184
|
+
errorState,
|
|
185
|
+
overrideErrorDisplayText: errorDisplayText,
|
|
186
|
+
}))),
|
|
187
|
+
isRecording &&
|
|
188
|
+
!isFlashingFreshness &&
|
|
189
|
+
showMatchIndicatorStates.includes(faceMatchState) ? (React__default.createElement(MemoizedMatchIndicator, { percentage: Math.ceil(faceMatchPercentage) })) : null),
|
|
167
190
|
React__default.createElement(View, { as: "canvas", ref: freshnessColorRef, className: LivenessClassNames.FreshnessCanvas, hidden: true }),
|
|
168
191
|
React__default.createElement(View, { className: LivenessClassNames.VideoAnchor, style: {
|
|
169
192
|
aspectRatio: `${aspectRatio}`,
|
|
170
193
|
} },
|
|
171
194
|
React__default.createElement("video", { ref: videoRef, muted: true, autoPlay: true, playsInline: true, width: mediaWidth, height: mediaHeight, onCanPlay: handleMediaPlay, "data-testid": "video", className: LivenessClassNames.Video, "aria-label": cameraDisplayText.a11yVideoLabelText }),
|
|
172
|
-
React__default.createElement(Flex, { className: classNames(LivenessClassNames.OvalCanvas,
|
|
195
|
+
React__default.createElement(Flex, { className: classNames(LivenessClassNames.OvalCanvas, shouldShowFullScreenCamera &&
|
|
196
|
+
`${LivenessClassNames.OvalCanvas}--mobile`, isRecordingStopped && LivenessClassNames.FadeOut) },
|
|
173
197
|
React__default.createElement(View, { as: "canvas", ref: canvasRef })),
|
|
174
|
-
isRecording && (React__default.createElement(DefaultRecordingIcon, { recordingIndicatorText: recordingIndicatorText })),
|
|
175
|
-
!isStartView && !isWaitingForCamera && !isCheckSucceeded && (React__default.createElement(DefaultCancelButton, { cancelLivenessCheckText: cancelLivenessCheckText })),
|
|
176
|
-
React__default.createElement(Overlay, { horizontal: "center", vertical: isRecording && !isFlashingFreshness ? 'start' : 'space-between', className: LivenessClassNames.InstructionOverlay },
|
|
177
|
-
React__default.createElement(Hint, { hintDisplayText: hintDisplayText }),
|
|
178
|
-
errorState && (React__default.createElement(ErrorView, { onRetry: () => {
|
|
179
|
-
send({ type: 'CANCEL' });
|
|
180
|
-
}, displayText: errorDisplayText }, renderErrorModal({
|
|
181
|
-
errorState,
|
|
182
|
-
overrideErrorDisplayText: errorDisplayText,
|
|
183
|
-
}))),
|
|
184
|
-
isRecording &&
|
|
185
|
-
!isFlashingFreshness &&
|
|
186
|
-
showMatchIndicatorStates.includes(faceMatchState) ? (React__default.createElement(MemoizedMatchIndicator, { percentage: Math.ceil(faceMatchPercentage) })) : null),
|
|
187
198
|
isStartView &&
|
|
188
199
|
!isMobileScreen &&
|
|
189
200
|
selectableDevices &&
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const defaultErrorDisplayText = {
|
|
2
|
+
errorLabelText: 'Error',
|
|
2
3
|
timeoutHeaderText: 'Time out',
|
|
3
4
|
timeoutMessageText: "Face didn't fit inside oval in time limit. Try again and completely fill the oval with face in it.",
|
|
4
5
|
faceDistanceHeaderText: 'Forward movement detected',
|
|
@@ -24,6 +25,8 @@ const defaultLivenessDisplayText = {
|
|
|
24
25
|
goodFitCaptionText: 'Good fit',
|
|
25
26
|
goodFitAltText: "Ilustration of a person's face, perfectly fitting inside of an oval.",
|
|
26
27
|
hintCenterFaceText: 'Center your face',
|
|
28
|
+
hintCenterFaceInstructionText: 'Instruction: Before starting the check, make sure your camera is at the center top of your screen and center your face to the camera. When the check starts an oval will show up in the center. You will be prompted to move forward into the oval and then prompted to hold still. After holding still for a few seconds, you should hear check complete.',
|
|
29
|
+
hintFaceOffCenterText: 'Face is not in the oval, center your face to the camera.',
|
|
27
30
|
hintMoveFaceFrontOfCameraText: 'Move face in front of camera',
|
|
28
31
|
hintTooManyFacesText: 'Ensure only one face is in front of camera',
|
|
29
32
|
hintFaceDetectedText: 'Face detected',
|
|
@@ -32,10 +35,12 @@ const defaultLivenessDisplayText = {
|
|
|
32
35
|
hintTooFarText: 'Move closer',
|
|
33
36
|
hintConnectingText: 'Connecting...',
|
|
34
37
|
hintVerifyingText: 'Verifying...',
|
|
38
|
+
hintCheckCompleteText: 'Check complete',
|
|
35
39
|
hintIlluminationTooBrightText: 'Move to dimmer area',
|
|
36
40
|
hintIlluminationTooDarkText: 'Move to brighter area',
|
|
37
41
|
hintIlluminationNormalText: 'Lighting conditions normal',
|
|
38
42
|
hintHoldFaceForFreshnessText: 'Hold still',
|
|
43
|
+
hintMatchIndicatorText: '50% completed. Keep moving closer.',
|
|
39
44
|
photosensitivityWarningBodyText: 'This check flashes different colors. Use caution if you are photosensitive.',
|
|
40
45
|
photosensitivityWarningHeadingText: 'Photosensitivity warning',
|
|
41
46
|
photosensitivityWarningInfoText: 'Some people may experience epileptic seizures when exposed to colored lights. Use caution if you, or anyone in your family, have an epileptic condition.',
|
|
@@ -18,6 +18,7 @@ var FaceMatchState;
|
|
|
18
18
|
FaceMatchState["CANT_IDENTIFY"] = "CANNOT IDENTIFY";
|
|
19
19
|
FaceMatchState["FACE_IDENTIFIED"] = "ONE FACE IDENTIFIED";
|
|
20
20
|
FaceMatchState["TOO_MANY"] = "TOO MANY FACES";
|
|
21
|
+
FaceMatchState["OFF_CENTER"] = "OFF CENTER";
|
|
21
22
|
})(FaceMatchState || (FaceMatchState = {}));
|
|
22
23
|
|
|
23
24
|
export { FaceMatchState, IlluminationState };
|
|
@@ -162,6 +162,16 @@ function drawLivenessOvalInCanvas({ canvas, oval, scaleFactor, videoEl, isStartS
|
|
|
162
162
|
throw new Error('Cannot find Canvas.');
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
+
function clearOvalCanvas({ canvas, }) {
|
|
166
|
+
const ctx = canvas.getContext('2d');
|
|
167
|
+
if (ctx) {
|
|
168
|
+
ctx.restore();
|
|
169
|
+
ctx.clearRect(0, 0, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
throw new Error('Cannot find Canvas.');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
165
175
|
/**
|
|
166
176
|
* Returns the state of the provided face with respect to the provided liveness oval.
|
|
167
177
|
*/
|
|
@@ -196,12 +206,17 @@ function getFaceMatchStateInLivenessOval(face, ovalDetails, initialFaceIntersect
|
|
|
196
206
|
const faceMatchPercentage = Math.max(Math.min(1, (0.75 * (intersection - initialFaceIntersection)) /
|
|
197
207
|
(intersectionThreshold - initialFaceIntersection) +
|
|
198
208
|
0.25), 0) * 100;
|
|
209
|
+
const faceIsOutsideOvalToTheLeft = minOvalX > minFaceX && maxOvalX > maxFaceX;
|
|
210
|
+
const faceIsOutsideOvalToTheRight = minFaceX > minOvalX && maxFaceX > maxOvalX;
|
|
199
211
|
if (intersection > intersectionThreshold &&
|
|
200
212
|
Math.abs(minOvalX - minFaceX) < ovalMatchWidthThreshold &&
|
|
201
213
|
Math.abs(maxOvalX - maxFaceX) < ovalMatchWidthThreshold &&
|
|
202
214
|
Math.abs(maxOvalY - maxFaceY) < ovalMatchHeightThreshold) {
|
|
203
215
|
faceMatchState = FaceMatchState.MATCHED;
|
|
204
216
|
}
|
|
217
|
+
else if (faceIsOutsideOvalToTheLeft || faceIsOutsideOvalToTheRight) {
|
|
218
|
+
faceMatchState = FaceMatchState.OFF_CENTER;
|
|
219
|
+
}
|
|
205
220
|
else if (minOvalY - minFaceY > faceDetectionHeightThreshold ||
|
|
206
221
|
maxFaceY - maxOvalY > faceDetectionHeightThreshold ||
|
|
207
222
|
(minOvalX - minFaceX > faceDetectionWidthThreshold &&
|
|
@@ -459,4 +474,4 @@ function getBoundingBox({ deviceHeight, deviceWidth, height, width, top, left, }
|
|
|
459
474
|
};
|
|
460
475
|
}
|
|
461
476
|
|
|
462
|
-
export { drawLivenessOvalInCanvas, drawStaticOval, estimateIllumination, fillOverlayCanvasFractional, generateBboxFromLandmarks, getBoundingBox, getColorsSequencesFromSessionInformation, getFaceMatchState, getFaceMatchStateInLivenessOval, getIntersectionOverUnion, getOvalBoundingBox, getOvalDetailsFromSessionInformation, getRGBArrayFromColorString, getStaticLivenessOvalDetails, isCameraDeviceVirtual, isClientFreshnessColorSequence, isFaceDistanceBelowThreshold };
|
|
477
|
+
export { clearOvalCanvas, drawLivenessOvalInCanvas, drawStaticOval, estimateIllumination, fillOverlayCanvasFractional, generateBboxFromLandmarks, getBoundingBox, getColorsSequencesFromSessionInformation, getFaceMatchState, getFaceMatchStateInLivenessOval, getIntersectionOverUnion, getOvalBoundingBox, getOvalDetailsFromSessionInformation, getRGBArrayFromColorString, getStaticLivenessOvalDetails, isCameraDeviceVirtual, isClientFreshnessColorSequence, isFaceDistanceBelowThreshold };
|
|
@@ -23,7 +23,7 @@ import { LivenessClassNames } from '../types/classNames.mjs';
|
|
|
23
23
|
|
|
24
24
|
const renderToastErrorModal = (props) => {
|
|
25
25
|
const { error: errorState, displayText } = props;
|
|
26
|
-
const { timeoutHeaderText, timeoutMessageText, faceDistanceHeaderText, faceDistanceMessageText, multipleFacesHeaderText, multipleFacesMessageText, clientHeaderText, clientMessageText, serverHeaderText, serverMessageText, } = displayText;
|
|
26
|
+
const { errorLabelText, timeoutHeaderText, timeoutMessageText, faceDistanceHeaderText, faceDistanceMessageText, multipleFacesHeaderText, multipleFacesMessageText, clientHeaderText, clientMessageText, serverHeaderText, serverMessageText, } = displayText;
|
|
27
27
|
let heading;
|
|
28
28
|
let message;
|
|
29
29
|
switch (errorState) {
|
|
@@ -50,7 +50,7 @@ const renderToastErrorModal = (props) => {
|
|
|
50
50
|
}
|
|
51
51
|
return (React__default.createElement(React__default.Fragment, null,
|
|
52
52
|
React__default.createElement(Flex, { className: LivenessClassNames.ErrorModal },
|
|
53
|
-
React__default.createElement(AlertIcon, {
|
|
53
|
+
React__default.createElement(AlertIcon, { ariaLabel: errorLabelText, role: "img", variation: "error" }),
|
|
54
54
|
React__default.createElement(Text, { className: LivenessClassNames.ErrorModalHeading, id: "amplify-liveness-error-heading" }, heading)),
|
|
55
55
|
React__default.createElement(Text, { id: "amplify-liveness-error-message" }, message)));
|
|
56
56
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { View } from '@aws-amplify/ui-react';
|
|
2
|
+
import { VisuallyHidden, View } from '@aws-amplify/ui-react';
|
|
3
3
|
import '../service/machine/index.mjs';
|
|
4
4
|
import { FaceMatchState, IlluminationState } from '../service/types/liveness.mjs';
|
|
5
5
|
import '@tensorflow/tfjs-core';
|
|
@@ -25,9 +25,10 @@ const selectFaceMatchState = createLivenessSelector((state) => state.context.fac
|
|
|
25
25
|
const selectIlluminationState = createLivenessSelector((state) => state.context.faceMatchAssociatedParams.illuminationState);
|
|
26
26
|
const selectIsFaceFarEnoughBeforeRecording = createLivenessSelector((state) => state.context.isFaceFarEnoughBeforeRecording);
|
|
27
27
|
const selectFaceMatchStateBeforeStart = createLivenessSelector((state) => state.context.faceMatchStateBeforeStart);
|
|
28
|
-
const
|
|
28
|
+
const selectFaceMatchPercentage = createLivenessSelector((state) => state.context.faceMatchAssociatedParams?.faceMatchPercentage);
|
|
29
|
+
const DefaultToast = ({ text, isInitial = false, }) => {
|
|
29
30
|
return (React.createElement(Toast, { size: "large", variation: "primary", isInitial: isInitial },
|
|
30
|
-
React.createElement(View, { "aria-live": "
|
|
31
|
+
React.createElement(View, { "aria-live": "assertive" }, text)));
|
|
31
32
|
};
|
|
32
33
|
const Hint = ({ hintDisplayText }) => {
|
|
33
34
|
const [state] = useLivenessActor();
|
|
@@ -37,8 +38,11 @@ const Hint = ({ hintDisplayText }) => {
|
|
|
37
38
|
const illuminationState = useLivenessSelector(selectIlluminationState);
|
|
38
39
|
const faceMatchStateBeforeStart = useLivenessSelector(selectFaceMatchStateBeforeStart);
|
|
39
40
|
const isFaceFarEnoughBeforeRecordingState = useLivenessSelector(selectIsFaceFarEnoughBeforeRecording);
|
|
40
|
-
const
|
|
41
|
-
const
|
|
41
|
+
const faceMatchPercentage = useLivenessSelector(selectFaceMatchPercentage);
|
|
42
|
+
const isCheckFaceDetectedBeforeStart = state.matches('checkFaceDetectedBeforeStart') ||
|
|
43
|
+
state.matches('detectFaceBeforeStart');
|
|
44
|
+
const isCheckFaceDistanceBeforeRecording = state.matches('checkFaceDistanceBeforeRecording') ||
|
|
45
|
+
state.matches('detectFaceDistanceBeforeRecording');
|
|
42
46
|
const isStartView = state.matches('start') || state.matches('userCancel');
|
|
43
47
|
const isRecording = state.matches('recording');
|
|
44
48
|
const isNotRecording = state.matches('notRecording');
|
|
@@ -55,61 +59,74 @@ const Hint = ({ hintDisplayText }) => {
|
|
|
55
59
|
[FaceMatchState.TOO_CLOSE]: hintDisplayText.hintTooCloseText,
|
|
56
60
|
[FaceMatchState.TOO_FAR]: hintDisplayText.hintTooFarText,
|
|
57
61
|
[FaceMatchState.MATCHED]: hintDisplayText.hintHoldFaceForFreshnessText,
|
|
62
|
+
[FaceMatchState.OFF_CENTER]: hintDisplayText.hintFaceOffCenterText,
|
|
58
63
|
};
|
|
59
64
|
const IlluminationStateStringMap = {
|
|
60
65
|
[IlluminationState.BRIGHT]: hintDisplayText.hintIlluminationTooBrightText,
|
|
61
66
|
[IlluminationState.DARK]: hintDisplayText.hintIlluminationTooDarkText,
|
|
62
67
|
[IlluminationState.NORMAL]: hintDisplayText.hintIlluminationNormalText,
|
|
63
68
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
if (isStartView) {
|
|
70
|
+
return (React.createElement(React.Fragment, null,
|
|
71
|
+
React.createElement(VisuallyHidden, { role: "alert" }, hintDisplayText.hintCenterFaceInstructionText),
|
|
72
|
+
React.createElement(DefaultToast, { text: hintDisplayText.hintCenterFaceText, isInitial: true })));
|
|
73
|
+
}
|
|
74
|
+
if (errorState ?? (isCheckFailed || isCheckSuccessful)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
if (!isRecording) {
|
|
78
|
+
if (isCheckFaceDetectedBeforeStart) {
|
|
79
|
+
if (faceMatchStateBeforeStart === FaceMatchState.TOO_MANY) {
|
|
80
|
+
return React.createElement(DefaultToast, { text: hintDisplayText.hintTooManyFacesText });
|
|
81
|
+
}
|
|
82
|
+
return (React.createElement(DefaultToast, { text: hintDisplayText.hintMoveFaceFrontOfCameraText }));
|
|
67
83
|
}
|
|
68
|
-
|
|
69
|
-
|
|
84
|
+
// Specifically checking for false here because initially the value is undefined and we do not want to show the instruction
|
|
85
|
+
if (isCheckFaceDistanceBeforeRecording &&
|
|
86
|
+
isFaceFarEnoughBeforeRecordingState === false) {
|
|
87
|
+
return React.createElement(DefaultToast, { text: hintDisplayText.hintTooCloseText });
|
|
70
88
|
}
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
if (faceMatchStateBeforeStart === FaceMatchState.TOO_MANY) {
|
|
74
|
-
return defaultToast(FaceMatchStateStringMap[faceMatchStateBeforeStart]);
|
|
75
|
-
}
|
|
76
|
-
return defaultToast(hintDisplayText.hintMoveFaceFrontOfCameraText);
|
|
77
|
-
}
|
|
78
|
-
// Specifically checking for false here because initially the value is undefined and we do not want to show the instruction
|
|
79
|
-
if (isCheckFaceDistanceBeforeRecording &&
|
|
80
|
-
isFaceFarEnoughBeforeRecordingState === false) {
|
|
81
|
-
return defaultToast(hintDisplayText.hintTooCloseText);
|
|
82
|
-
}
|
|
83
|
-
if (isNotRecording) {
|
|
84
|
-
return (React.createElement(ToastWithLoader, { displayText: hintDisplayText.hintConnectingText }));
|
|
85
|
-
}
|
|
86
|
-
if (isUploading) {
|
|
87
|
-
return (React.createElement(ToastWithLoader, { displayText: hintDisplayText.hintVerifyingText }));
|
|
88
|
-
}
|
|
89
|
-
if (illuminationState && illuminationState !== IlluminationState.NORMAL) {
|
|
90
|
-
return defaultToast(IlluminationStateStringMap[illuminationState]);
|
|
91
|
-
}
|
|
89
|
+
if (isNotRecording) {
|
|
90
|
+
return (React.createElement(ToastWithLoader, { displayText: hintDisplayText.hintConnectingText }));
|
|
92
91
|
}
|
|
93
|
-
if (
|
|
94
|
-
return
|
|
92
|
+
if (isUploading) {
|
|
93
|
+
return (React.createElement(React.Fragment, null,
|
|
94
|
+
React.createElement(VisuallyHidden, { "aria-live": "assertive" }, hintDisplayText.hintCheckCompleteText),
|
|
95
|
+
React.createElement(ToastWithLoader, { displayText: hintDisplayText.hintVerifyingText })));
|
|
95
96
|
}
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
// TOO_FAR texts. If FaceMatchState matches TOO_CLOSE, we'll show
|
|
99
|
-
// the TOO_CLOSE text, but for FACE_IDENTIFED, CANT_IDENTIFY, TOO_MANY
|
|
100
|
-
// we are defaulting to the TOO_FAR text (for now).
|
|
101
|
-
let resultHintString = FaceMatchStateStringMap[FaceMatchState.TOO_FAR];
|
|
102
|
-
if (faceMatchState === FaceMatchState.TOO_CLOSE ||
|
|
103
|
-
faceMatchState === FaceMatchState.MATCHED) {
|
|
104
|
-
resultHintString = FaceMatchStateStringMap[faceMatchState];
|
|
105
|
-
}
|
|
106
|
-
return (React.createElement(Toast, { size: "large", variation: faceMatchState === FaceMatchState.TOO_CLOSE ? 'error' : 'primary' },
|
|
107
|
-
React.createElement(View, { "aria-live": "polite", "aria-label": resultHintString }, resultHintString)));
|
|
97
|
+
if (illuminationState && illuminationState !== IlluminationState.NORMAL) {
|
|
98
|
+
return (React.createElement(DefaultToast, { text: IlluminationStateStringMap[illuminationState] }));
|
|
108
99
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
100
|
+
}
|
|
101
|
+
if (isFlashingFreshness) {
|
|
102
|
+
return React.createElement(DefaultToast, { text: hintDisplayText.hintHoldFaceForFreshnessText });
|
|
103
|
+
}
|
|
104
|
+
if (isRecording && !isFlashingFreshness) {
|
|
105
|
+
// During face matching, we want to only show the TOO_CLOSE or
|
|
106
|
+
// TOO_FAR texts. If FaceMatchState matches TOO_CLOSE, we'll show
|
|
107
|
+
// the TOO_CLOSE text, but for FACE_IDENTIFED, CANT_IDENTIFY, TOO_MANY
|
|
108
|
+
// we are defaulting to the TOO_FAR text (for now).
|
|
109
|
+
let resultHintString = FaceMatchStateStringMap[FaceMatchState.TOO_FAR];
|
|
110
|
+
if (faceMatchState === FaceMatchState.TOO_CLOSE ||
|
|
111
|
+
faceMatchState === FaceMatchState.MATCHED) {
|
|
112
|
+
resultHintString = FaceMatchStateStringMap[faceMatchState];
|
|
113
|
+
}
|
|
114
|
+
// If the face is outside the oval set the aria-label to a string about centering face in oval
|
|
115
|
+
let a11yHintString = resultHintString;
|
|
116
|
+
if (faceMatchState === FaceMatchState.OFF_CENTER) {
|
|
117
|
+
a11yHintString = FaceMatchStateStringMap[faceMatchState];
|
|
118
|
+
}
|
|
119
|
+
else if (
|
|
120
|
+
// If the face match percentage reaches 50% append it to the a11y label
|
|
121
|
+
faceMatchState === FaceMatchState.TOO_FAR &&
|
|
122
|
+
faceMatchPercentage > 50) {
|
|
123
|
+
a11yHintString = hintDisplayText.hintMatchIndicatorText;
|
|
124
|
+
}
|
|
125
|
+
return (React.createElement(Toast, { size: "large", variation: faceMatchState === FaceMatchState.TOO_CLOSE ? 'error' : 'primary' },
|
|
126
|
+
React.createElement(VisuallyHidden, { "aria-live": "assertive" }, a11yHintString),
|
|
127
|
+
React.createElement(View, { "aria-label": a11yHintString }, resultHintString)));
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
113
130
|
};
|
|
114
131
|
|
|
115
132
|
export { Hint, selectErrorState, selectFaceMatchState, selectFaceMatchStateBeforeStart, selectIlluminationState, selectIsFaceFarEnoughBeforeRecording };
|
|
@@ -3,8 +3,8 @@ import { Flex, Loader, View } from '@aws-amplify/ui-react';
|
|
|
3
3
|
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
4
4
|
import { Toast } from './Toast.mjs';
|
|
5
5
|
|
|
6
|
-
const ToastWithLoader = ({ displayText,
|
|
7
|
-
return (React.createElement(Toast, { "aria-live": "polite"
|
|
6
|
+
const ToastWithLoader = ({ displayText, }) => {
|
|
7
|
+
return (React.createElement(Toast, { "aria-live": "polite" },
|
|
8
8
|
React.createElement(Flex, { className: LivenessClassNames.HintText },
|
|
9
9
|
React.createElement(Loader, null),
|
|
10
10
|
React.createElement(View, null, displayText))));
|
|
@@ -12,7 +12,7 @@ function getDisplayText(overrideDisplayText) {
|
|
|
12
12
|
...defaultLivenessDisplayText,
|
|
13
13
|
...overrideDisplayText,
|
|
14
14
|
};
|
|
15
|
-
const { a11yVideoLabelText, cameraMinSpecificationsHeadingText, cameraMinSpecificationsMessageText, cameraNotFoundHeadingText, cameraNotFoundMessageText, cancelLivenessCheckText, clientHeaderText, clientMessageText, hintCanNotIdentifyText, hintCenterFaceText, hintConnectingText, hintFaceDetectedText, hintHoldFaceForFreshnessText, hintIlluminationNormalText, hintIlluminationTooBrightText, hintIlluminationTooDarkText, hintMoveFaceFrontOfCameraText, hintTooManyFacesText, hintTooCloseText, hintTooFarText, hintVerifyingText, faceDistanceHeaderText, faceDistanceMessageText, goodFitCaptionText, goodFitAltText, landscapeHeaderText, landscapeMessageText, multipleFacesHeaderText, multipleFacesMessageText, photosensitivityWarningBodyText, photosensitivityWarningHeadingText, photosensitivityWarningInfoText, photosensitivityWarningLabelText, photosensitivyWarningBodyText, photosensitivyWarningHeadingText, photosensitivyWarningInfoText, photosensitivyWarningLabelText, portraitMessageText, retryCameraPermissionsText, recordingIndicatorText, serverHeaderText, serverMessageText, startScreenBeginCheckText, timeoutHeaderText, timeoutMessageText, tooFarCaptionText, tooFarAltText, tryAgainText, waitingCameraPermissionText, } = displayText;
|
|
15
|
+
const { a11yVideoLabelText, cameraMinSpecificationsHeadingText, cameraMinSpecificationsMessageText, cameraNotFoundHeadingText, cameraNotFoundMessageText, cancelLivenessCheckText, clientHeaderText, clientMessageText, errorLabelText, hintCanNotIdentifyText, hintCenterFaceText, hintCenterFaceInstructionText, hintFaceOffCenterText, hintConnectingText, hintFaceDetectedText, hintHoldFaceForFreshnessText, hintIlluminationNormalText, hintIlluminationTooBrightText, hintIlluminationTooDarkText, hintMoveFaceFrontOfCameraText, hintTooManyFacesText, hintTooCloseText, hintTooFarText, hintVerifyingText, hintCheckCompleteText, hintMatchIndicatorText, faceDistanceHeaderText, faceDistanceMessageText, goodFitCaptionText, goodFitAltText, landscapeHeaderText, landscapeMessageText, multipleFacesHeaderText, multipleFacesMessageText, photosensitivityWarningBodyText, photosensitivityWarningHeadingText, photosensitivityWarningInfoText, photosensitivityWarningLabelText, photosensitivyWarningBodyText, photosensitivyWarningHeadingText, photosensitivyWarningInfoText, photosensitivyWarningLabelText, portraitMessageText, retryCameraPermissionsText, recordingIndicatorText, serverHeaderText, serverMessageText, startScreenBeginCheckText, timeoutHeaderText, timeoutMessageText, tooFarCaptionText, tooFarAltText, tryAgainText, waitingCameraPermissionText, } = displayText;
|
|
16
16
|
const hintDisplayText = {
|
|
17
17
|
hintMoveFaceFrontOfCameraText,
|
|
18
18
|
hintTooManyFacesText,
|
|
@@ -22,11 +22,15 @@ function getDisplayText(overrideDisplayText) {
|
|
|
22
22
|
hintTooFarText,
|
|
23
23
|
hintConnectingText,
|
|
24
24
|
hintVerifyingText,
|
|
25
|
+
hintCheckCompleteText,
|
|
25
26
|
hintIlluminationTooBrightText,
|
|
26
27
|
hintIlluminationTooDarkText,
|
|
27
28
|
hintIlluminationNormalText,
|
|
28
29
|
hintHoldFaceForFreshnessText,
|
|
29
30
|
hintCenterFaceText,
|
|
31
|
+
hintCenterFaceInstructionText,
|
|
32
|
+
hintFaceOffCenterText,
|
|
33
|
+
hintMatchIndicatorText,
|
|
30
34
|
};
|
|
31
35
|
const cameraDisplayText = {
|
|
32
36
|
cameraMinSpecificationsHeadingText,
|
|
@@ -57,6 +61,7 @@ function getDisplayText(overrideDisplayText) {
|
|
|
57
61
|
recordingIndicatorText,
|
|
58
62
|
};
|
|
59
63
|
const errorDisplayText = {
|
|
64
|
+
errorLabelText,
|
|
60
65
|
timeoutHeaderText,
|
|
61
66
|
timeoutMessageText,
|
|
62
67
|
faceDistanceHeaderText,
|
package/dist/esm/version.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -78,6 +78,7 @@ var FaceMatchState;
|
|
|
78
78
|
FaceMatchState["CANT_IDENTIFY"] = "CANNOT IDENTIFY";
|
|
79
79
|
FaceMatchState["FACE_IDENTIFIED"] = "ONE FACE IDENTIFIED";
|
|
80
80
|
FaceMatchState["TOO_MANY"] = "TOO MANY FACES";
|
|
81
|
+
FaceMatchState["OFF_CENTER"] = "OFF CENTER";
|
|
81
82
|
})(FaceMatchState || (FaceMatchState = {}));
|
|
82
83
|
|
|
83
84
|
/**
|
|
@@ -272,6 +273,16 @@ function drawLivenessOvalInCanvas({ canvas, oval, scaleFactor, videoEl, isStartS
|
|
|
272
273
|
throw new Error('Cannot find Canvas.');
|
|
273
274
|
}
|
|
274
275
|
}
|
|
276
|
+
function clearOvalCanvas({ canvas, }) {
|
|
277
|
+
const ctx = canvas.getContext('2d');
|
|
278
|
+
if (ctx) {
|
|
279
|
+
ctx.restore();
|
|
280
|
+
ctx.clearRect(0, 0, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
throw new Error('Cannot find Canvas.');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
275
286
|
/**
|
|
276
287
|
* Returns the state of the provided face with respect to the provided liveness oval.
|
|
277
288
|
*/
|
|
@@ -306,12 +317,17 @@ function getFaceMatchStateInLivenessOval(face, ovalDetails, initialFaceIntersect
|
|
|
306
317
|
const faceMatchPercentage = Math.max(Math.min(1, (0.75 * (intersection - initialFaceIntersection)) /
|
|
307
318
|
(intersectionThreshold - initialFaceIntersection) +
|
|
308
319
|
0.25), 0) * 100;
|
|
320
|
+
const faceIsOutsideOvalToTheLeft = minOvalX > minFaceX && maxOvalX > maxFaceX;
|
|
321
|
+
const faceIsOutsideOvalToTheRight = minFaceX > minOvalX && maxFaceX > maxOvalX;
|
|
309
322
|
if (intersection > intersectionThreshold &&
|
|
310
323
|
Math.abs(minOvalX - minFaceX) < ovalMatchWidthThreshold &&
|
|
311
324
|
Math.abs(maxOvalX - maxFaceX) < ovalMatchWidthThreshold &&
|
|
312
325
|
Math.abs(maxOvalY - maxFaceY) < ovalMatchHeightThreshold) {
|
|
313
326
|
faceMatchState = FaceMatchState.MATCHED;
|
|
314
327
|
}
|
|
328
|
+
else if (faceIsOutsideOvalToTheLeft || faceIsOutsideOvalToTheRight) {
|
|
329
|
+
faceMatchState = FaceMatchState.OFF_CENTER;
|
|
330
|
+
}
|
|
315
331
|
else if (minOvalY - minFaceY > faceDetectionHeightThreshold ||
|
|
316
332
|
maxFaceY - maxOvalY > faceDetectionHeightThreshold ||
|
|
317
333
|
(minOvalX - minFaceX > faceDetectionWidthThreshold &&
|
|
@@ -771,7 +787,7 @@ class VideoRecorder {
|
|
|
771
787
|
}
|
|
772
788
|
}
|
|
773
789
|
|
|
774
|
-
const VERSION = '3.0.
|
|
790
|
+
const VERSION = '3.0.9';
|
|
775
791
|
|
|
776
792
|
const BASE_USER_AGENT = `ui-react-liveness/${VERSION}`;
|
|
777
793
|
const getLivenessUserAgent = () => {
|
|
@@ -2530,8 +2546,8 @@ const Toast = ({ variation = 'default', size = 'medium', children, isInitial = f
|
|
|
2530
2546
|
React__namespace.createElement(uiReact.Flex, { className: LivenessClassNames.ToastMessage, ...(isInitial ? { color: tokens.colors.font.primary } : {}) }, children))));
|
|
2531
2547
|
};
|
|
2532
2548
|
|
|
2533
|
-
const ToastWithLoader = ({ displayText,
|
|
2534
|
-
return (React__namespace.createElement(Toast, { "aria-live": "polite"
|
|
2549
|
+
const ToastWithLoader = ({ displayText, }) => {
|
|
2550
|
+
return (React__namespace.createElement(Toast, { "aria-live": "polite" },
|
|
2535
2551
|
React__namespace.createElement(uiReact.Flex, { className: LivenessClassNames.HintText },
|
|
2536
2552
|
React__namespace.createElement(uiReact.Loader, null),
|
|
2537
2553
|
React__namespace.createElement(uiReact.View, null, displayText))));
|
|
@@ -2542,9 +2558,10 @@ const selectFaceMatchState$1 = createLivenessSelector((state) => state.context.f
|
|
|
2542
2558
|
const selectIlluminationState = createLivenessSelector((state) => state.context.faceMatchAssociatedParams.illuminationState);
|
|
2543
2559
|
const selectIsFaceFarEnoughBeforeRecording = createLivenessSelector((state) => state.context.isFaceFarEnoughBeforeRecording);
|
|
2544
2560
|
const selectFaceMatchStateBeforeStart = createLivenessSelector((state) => state.context.faceMatchStateBeforeStart);
|
|
2545
|
-
const
|
|
2561
|
+
const selectFaceMatchPercentage$1 = createLivenessSelector((state) => state.context.faceMatchAssociatedParams?.faceMatchPercentage);
|
|
2562
|
+
const DefaultToast = ({ text, isInitial = false, }) => {
|
|
2546
2563
|
return (React__namespace.createElement(Toast, { size: "large", variation: "primary", isInitial: isInitial },
|
|
2547
|
-
React__namespace.createElement(uiReact.View, { "aria-live": "
|
|
2564
|
+
React__namespace.createElement(uiReact.View, { "aria-live": "assertive" }, text)));
|
|
2548
2565
|
};
|
|
2549
2566
|
const Hint = ({ hintDisplayText }) => {
|
|
2550
2567
|
const [state] = useLivenessActor();
|
|
@@ -2554,8 +2571,11 @@ const Hint = ({ hintDisplayText }) => {
|
|
|
2554
2571
|
const illuminationState = useLivenessSelector(selectIlluminationState);
|
|
2555
2572
|
const faceMatchStateBeforeStart = useLivenessSelector(selectFaceMatchStateBeforeStart);
|
|
2556
2573
|
const isFaceFarEnoughBeforeRecordingState = useLivenessSelector(selectIsFaceFarEnoughBeforeRecording);
|
|
2557
|
-
const
|
|
2558
|
-
const
|
|
2574
|
+
const faceMatchPercentage = useLivenessSelector(selectFaceMatchPercentage$1);
|
|
2575
|
+
const isCheckFaceDetectedBeforeStart = state.matches('checkFaceDetectedBeforeStart') ||
|
|
2576
|
+
state.matches('detectFaceBeforeStart');
|
|
2577
|
+
const isCheckFaceDistanceBeforeRecording = state.matches('checkFaceDistanceBeforeRecording') ||
|
|
2578
|
+
state.matches('detectFaceDistanceBeforeRecording');
|
|
2559
2579
|
const isStartView = state.matches('start') || state.matches('userCancel');
|
|
2560
2580
|
const isRecording = state.matches('recording');
|
|
2561
2581
|
const isNotRecording = state.matches('notRecording');
|
|
@@ -2572,61 +2592,74 @@ const Hint = ({ hintDisplayText }) => {
|
|
|
2572
2592
|
[FaceMatchState.TOO_CLOSE]: hintDisplayText.hintTooCloseText,
|
|
2573
2593
|
[FaceMatchState.TOO_FAR]: hintDisplayText.hintTooFarText,
|
|
2574
2594
|
[FaceMatchState.MATCHED]: hintDisplayText.hintHoldFaceForFreshnessText,
|
|
2595
|
+
[FaceMatchState.OFF_CENTER]: hintDisplayText.hintFaceOffCenterText,
|
|
2575
2596
|
};
|
|
2576
2597
|
const IlluminationStateStringMap = {
|
|
2577
2598
|
[IlluminationState.BRIGHT]: hintDisplayText.hintIlluminationTooBrightText,
|
|
2578
2599
|
[IlluminationState.DARK]: hintDisplayText.hintIlluminationTooDarkText,
|
|
2579
2600
|
[IlluminationState.NORMAL]: hintDisplayText.hintIlluminationNormalText,
|
|
2580
2601
|
};
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2602
|
+
if (isStartView) {
|
|
2603
|
+
return (React__namespace.createElement(React__namespace.Fragment, null,
|
|
2604
|
+
React__namespace.createElement(uiReact.VisuallyHidden, { role: "alert" }, hintDisplayText.hintCenterFaceInstructionText),
|
|
2605
|
+
React__namespace.createElement(DefaultToast, { text: hintDisplayText.hintCenterFaceText, isInitial: true })));
|
|
2606
|
+
}
|
|
2607
|
+
if (errorState ?? (isCheckFailed || isCheckSuccessful)) {
|
|
2608
|
+
return null;
|
|
2609
|
+
}
|
|
2610
|
+
if (!isRecording) {
|
|
2611
|
+
if (isCheckFaceDetectedBeforeStart) {
|
|
2612
|
+
if (faceMatchStateBeforeStart === FaceMatchState.TOO_MANY) {
|
|
2613
|
+
return React__namespace.createElement(DefaultToast, { text: hintDisplayText.hintTooManyFacesText });
|
|
2614
|
+
}
|
|
2615
|
+
return (React__namespace.createElement(DefaultToast, { text: hintDisplayText.hintMoveFaceFrontOfCameraText }));
|
|
2584
2616
|
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2617
|
+
// Specifically checking for false here because initially the value is undefined and we do not want to show the instruction
|
|
2618
|
+
if (isCheckFaceDistanceBeforeRecording &&
|
|
2619
|
+
isFaceFarEnoughBeforeRecordingState === false) {
|
|
2620
|
+
return React__namespace.createElement(DefaultToast, { text: hintDisplayText.hintTooCloseText });
|
|
2587
2621
|
}
|
|
2588
|
-
if (
|
|
2589
|
-
|
|
2590
|
-
if (faceMatchStateBeforeStart === FaceMatchState.TOO_MANY) {
|
|
2591
|
-
return defaultToast(FaceMatchStateStringMap[faceMatchStateBeforeStart]);
|
|
2592
|
-
}
|
|
2593
|
-
return defaultToast(hintDisplayText.hintMoveFaceFrontOfCameraText);
|
|
2594
|
-
}
|
|
2595
|
-
// Specifically checking for false here because initially the value is undefined and we do not want to show the instruction
|
|
2596
|
-
if (isCheckFaceDistanceBeforeRecording &&
|
|
2597
|
-
isFaceFarEnoughBeforeRecordingState === false) {
|
|
2598
|
-
return defaultToast(hintDisplayText.hintTooCloseText);
|
|
2599
|
-
}
|
|
2600
|
-
if (isNotRecording) {
|
|
2601
|
-
return (React__namespace.createElement(ToastWithLoader, { displayText: hintDisplayText.hintConnectingText }));
|
|
2602
|
-
}
|
|
2603
|
-
if (isUploading) {
|
|
2604
|
-
return (React__namespace.createElement(ToastWithLoader, { displayText: hintDisplayText.hintVerifyingText }));
|
|
2605
|
-
}
|
|
2606
|
-
if (illuminationState && illuminationState !== IlluminationState.NORMAL) {
|
|
2607
|
-
return defaultToast(IlluminationStateStringMap[illuminationState]);
|
|
2608
|
-
}
|
|
2622
|
+
if (isNotRecording) {
|
|
2623
|
+
return (React__namespace.createElement(ToastWithLoader, { displayText: hintDisplayText.hintConnectingText }));
|
|
2609
2624
|
}
|
|
2610
|
-
if (
|
|
2611
|
-
return
|
|
2625
|
+
if (isUploading) {
|
|
2626
|
+
return (React__namespace.createElement(React__namespace.Fragment, null,
|
|
2627
|
+
React__namespace.createElement(uiReact.VisuallyHidden, { "aria-live": "assertive" }, hintDisplayText.hintCheckCompleteText),
|
|
2628
|
+
React__namespace.createElement(ToastWithLoader, { displayText: hintDisplayText.hintVerifyingText })));
|
|
2612
2629
|
}
|
|
2613
|
-
if (
|
|
2614
|
-
|
|
2615
|
-
// TOO_FAR texts. If FaceMatchState matches TOO_CLOSE, we'll show
|
|
2616
|
-
// the TOO_CLOSE text, but for FACE_IDENTIFED, CANT_IDENTIFY, TOO_MANY
|
|
2617
|
-
// we are defaulting to the TOO_FAR text (for now).
|
|
2618
|
-
let resultHintString = FaceMatchStateStringMap[FaceMatchState.TOO_FAR];
|
|
2619
|
-
if (faceMatchState === FaceMatchState.TOO_CLOSE ||
|
|
2620
|
-
faceMatchState === FaceMatchState.MATCHED) {
|
|
2621
|
-
resultHintString = FaceMatchStateStringMap[faceMatchState];
|
|
2622
|
-
}
|
|
2623
|
-
return (React__namespace.createElement(Toast, { size: "large", variation: faceMatchState === FaceMatchState.TOO_CLOSE ? 'error' : 'primary' },
|
|
2624
|
-
React__namespace.createElement(uiReact.View, { "aria-live": "polite", "aria-label": resultHintString }, resultHintString)));
|
|
2630
|
+
if (illuminationState && illuminationState !== IlluminationState.NORMAL) {
|
|
2631
|
+
return (React__namespace.createElement(DefaultToast, { text: IlluminationStateStringMap[illuminationState] }));
|
|
2625
2632
|
}
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2633
|
+
}
|
|
2634
|
+
if (isFlashingFreshness) {
|
|
2635
|
+
return React__namespace.createElement(DefaultToast, { text: hintDisplayText.hintHoldFaceForFreshnessText });
|
|
2636
|
+
}
|
|
2637
|
+
if (isRecording && !isFlashingFreshness) {
|
|
2638
|
+
// During face matching, we want to only show the TOO_CLOSE or
|
|
2639
|
+
// TOO_FAR texts. If FaceMatchState matches TOO_CLOSE, we'll show
|
|
2640
|
+
// the TOO_CLOSE text, but for FACE_IDENTIFED, CANT_IDENTIFY, TOO_MANY
|
|
2641
|
+
// we are defaulting to the TOO_FAR text (for now).
|
|
2642
|
+
let resultHintString = FaceMatchStateStringMap[FaceMatchState.TOO_FAR];
|
|
2643
|
+
if (faceMatchState === FaceMatchState.TOO_CLOSE ||
|
|
2644
|
+
faceMatchState === FaceMatchState.MATCHED) {
|
|
2645
|
+
resultHintString = FaceMatchStateStringMap[faceMatchState];
|
|
2646
|
+
}
|
|
2647
|
+
// If the face is outside the oval set the aria-label to a string about centering face in oval
|
|
2648
|
+
let a11yHintString = resultHintString;
|
|
2649
|
+
if (faceMatchState === FaceMatchState.OFF_CENTER) {
|
|
2650
|
+
a11yHintString = FaceMatchStateStringMap[faceMatchState];
|
|
2651
|
+
}
|
|
2652
|
+
else if (
|
|
2653
|
+
// If the face match percentage reaches 50% append it to the a11y label
|
|
2654
|
+
faceMatchState === FaceMatchState.TOO_FAR &&
|
|
2655
|
+
faceMatchPercentage > 50) {
|
|
2656
|
+
a11yHintString = hintDisplayText.hintMatchIndicatorText;
|
|
2657
|
+
}
|
|
2658
|
+
return (React__namespace.createElement(Toast, { size: "large", variation: faceMatchState === FaceMatchState.TOO_CLOSE ? 'error' : 'primary' },
|
|
2659
|
+
React__namespace.createElement(uiReact.VisuallyHidden, { "aria-live": "assertive" }, a11yHintString),
|
|
2660
|
+
React__namespace.createElement(uiReact.View, { "aria-label": a11yHintString }, resultHintString)));
|
|
2661
|
+
}
|
|
2662
|
+
return null;
|
|
2630
2663
|
};
|
|
2631
2664
|
|
|
2632
2665
|
const MatchIndicator = ({ percentage, initialPercentage = 25, testId, }) => {
|
|
@@ -2662,6 +2695,7 @@ const RecordingIcon = ({ children }) => {
|
|
|
2662
2695
|
};
|
|
2663
2696
|
|
|
2664
2697
|
const defaultErrorDisplayText = {
|
|
2698
|
+
errorLabelText: 'Error',
|
|
2665
2699
|
timeoutHeaderText: 'Time out',
|
|
2666
2700
|
timeoutMessageText: "Face didn't fit inside oval in time limit. Try again and completely fill the oval with face in it.",
|
|
2667
2701
|
faceDistanceHeaderText: 'Forward movement detected',
|
|
@@ -2687,6 +2721,8 @@ const defaultLivenessDisplayText = {
|
|
|
2687
2721
|
goodFitCaptionText: 'Good fit',
|
|
2688
2722
|
goodFitAltText: "Ilustration of a person's face, perfectly fitting inside of an oval.",
|
|
2689
2723
|
hintCenterFaceText: 'Center your face',
|
|
2724
|
+
hintCenterFaceInstructionText: 'Instruction: Before starting the check, make sure your camera is at the center top of your screen and center your face to the camera. When the check starts an oval will show up in the center. You will be prompted to move forward into the oval and then prompted to hold still. After holding still for a few seconds, you should hear check complete.',
|
|
2725
|
+
hintFaceOffCenterText: 'Face is not in the oval, center your face to the camera.',
|
|
2690
2726
|
hintMoveFaceFrontOfCameraText: 'Move face in front of camera',
|
|
2691
2727
|
hintTooManyFacesText: 'Ensure only one face is in front of camera',
|
|
2692
2728
|
hintFaceDetectedText: 'Face detected',
|
|
@@ -2695,10 +2731,12 @@ const defaultLivenessDisplayText = {
|
|
|
2695
2731
|
hintTooFarText: 'Move closer',
|
|
2696
2732
|
hintConnectingText: 'Connecting...',
|
|
2697
2733
|
hintVerifyingText: 'Verifying...',
|
|
2734
|
+
hintCheckCompleteText: 'Check complete',
|
|
2698
2735
|
hintIlluminationTooBrightText: 'Move to dimmer area',
|
|
2699
2736
|
hintIlluminationTooDarkText: 'Move to brighter area',
|
|
2700
2737
|
hintIlluminationNormalText: 'Lighting conditions normal',
|
|
2701
2738
|
hintHoldFaceForFreshnessText: 'Hold still',
|
|
2739
|
+
hintMatchIndicatorText: '50% completed. Keep moving closer.',
|
|
2702
2740
|
photosensitivityWarningBodyText: 'This check flashes different colors. Use caution if you are photosensitive.',
|
|
2703
2741
|
photosensitivityWarningHeadingText: 'Photosensitivity warning',
|
|
2704
2742
|
photosensitivityWarningInfoText: 'Some people may experience epileptic seizures when exposed to colored lights. Use caution if you, or anyone in your family, have an epileptic condition.',
|
|
@@ -2718,7 +2756,7 @@ const defaultLivenessDisplayText = {
|
|
|
2718
2756
|
|
|
2719
2757
|
const renderToastErrorModal = (props) => {
|
|
2720
2758
|
const { error: errorState, displayText } = props;
|
|
2721
|
-
const { timeoutHeaderText, timeoutMessageText, faceDistanceHeaderText, faceDistanceMessageText, multipleFacesHeaderText, multipleFacesMessageText, clientHeaderText, clientMessageText, serverHeaderText, serverMessageText, } = displayText;
|
|
2759
|
+
const { errorLabelText, timeoutHeaderText, timeoutMessageText, faceDistanceHeaderText, faceDistanceMessageText, multipleFacesHeaderText, multipleFacesMessageText, clientHeaderText, clientMessageText, serverHeaderText, serverMessageText, } = displayText;
|
|
2722
2760
|
let heading;
|
|
2723
2761
|
let message;
|
|
2724
2762
|
switch (errorState) {
|
|
@@ -2745,7 +2783,7 @@ const renderToastErrorModal = (props) => {
|
|
|
2745
2783
|
}
|
|
2746
2784
|
return (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
2747
2785
|
React__default["default"].createElement(uiReact.Flex, { className: LivenessClassNames.ErrorModal },
|
|
2748
|
-
React__default["default"].createElement(internal.AlertIcon, {
|
|
2786
|
+
React__default["default"].createElement(internal.AlertIcon, { ariaLabel: errorLabelText, role: "img", variation: "error" }),
|
|
2749
2787
|
React__default["default"].createElement(uiReact.Text, { className: LivenessClassNames.ErrorModalHeading, id: "amplify-liveness-error-heading" }, heading)),
|
|
2750
2788
|
React__default["default"].createElement(uiReact.Text, { id: "amplify-liveness-error-message" }, message)));
|
|
2751
2789
|
};
|
|
@@ -2839,6 +2877,7 @@ const showMatchIndicatorStates = [
|
|
|
2839
2877
|
FaceMatchState.TOO_FAR,
|
|
2840
2878
|
FaceMatchState.CANT_IDENTIFY,
|
|
2841
2879
|
FaceMatchState.FACE_IDENTIFIED,
|
|
2880
|
+
FaceMatchState.OFF_CENTER,
|
|
2842
2881
|
];
|
|
2843
2882
|
/**
|
|
2844
2883
|
* For now we want to memoize the HOC for MatchIndicator because to optimize renders
|
|
@@ -2866,6 +2905,7 @@ const LivenessCameraModule = (props) => {
|
|
|
2866
2905
|
const isCheckingCamera = state.matches('cameraCheck');
|
|
2867
2906
|
const isWaitingForCamera = state.matches('waitForDOMAndCameraDetails');
|
|
2868
2907
|
const isStartView = state.matches('start') || state.matches('userCancel');
|
|
2908
|
+
const isDetectFaceBeforeStart = state.matches('detectFaceBeforeStart');
|
|
2869
2909
|
const isRecording = state.matches('recording');
|
|
2870
2910
|
const isCheckSucceeded = state.matches('checkSucceeded');
|
|
2871
2911
|
const isFlashingFreshness = state.matches({
|
|
@@ -2926,6 +2966,11 @@ const LivenessCameraModule = (props) => {
|
|
|
2926
2966
|
setAspectRatio(videoRef.current.videoWidth / videoRef.current.videoHeight);
|
|
2927
2967
|
}
|
|
2928
2968
|
}, [send, videoRef, isCameraReady, isMobileScreen]);
|
|
2969
|
+
React__default["default"].useEffect(() => {
|
|
2970
|
+
if (isDetectFaceBeforeStart) {
|
|
2971
|
+
clearOvalCanvas({ canvas: canvasRef.current });
|
|
2972
|
+
}
|
|
2973
|
+
}, [isDetectFaceBeforeStart]);
|
|
2929
2974
|
const photoSensitivityWarning = React__default["default"].useMemo(() => {
|
|
2930
2975
|
return (React__default["default"].createElement(uiReact.View, { style: { visibility: isStartView ? 'visible' : 'hidden' } },
|
|
2931
2976
|
React__default["default"].createElement(PhotosensitiveWarning, { bodyText: instructionDisplayText.photosensitivityWarningBodyText, headingText: instructionDisplayText.photosensitivityWarningHeadingText, infoText: instructionDisplayText.photosensitivityWarningInfoText, labelText: instructionDisplayText.photosensitivityWarningLabelText })));
|
|
@@ -2960,31 +3005,35 @@ const LivenessCameraModule = (props) => {
|
|
|
2960
3005
|
React__default["default"].createElement(uiReact.Loader, { size: "large", className: LivenessClassNames.Loader, "data-testid": "centered-loader", position: "unset" }),
|
|
2961
3006
|
React__default["default"].createElement(uiReact.Text, { fontSize: "large", fontWeight: "bold", "data-testid": "waiting-camera-permission", className: `${LivenessClassNames.StartScreenCameraWaiting}__text` }, cameraDisplayText.waitingCameraPermissionText)));
|
|
2962
3007
|
}
|
|
2963
|
-
|
|
3008
|
+
// We don't show full screen camera on the pre check screen (isStartView/isWaitingForCamera)
|
|
3009
|
+
const shouldShowFullScreenCamera = isMobileScreen && !isStartView && !isWaitingForCamera;
|
|
2964
3010
|
return (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
2965
3011
|
photoSensitivityWarning,
|
|
2966
|
-
React__default["default"].createElement(uiReact.Flex, { className: ui.classNames(LivenessClassNames.CameraModule,
|
|
3012
|
+
React__default["default"].createElement(uiReact.Flex, { className: ui.classNames(LivenessClassNames.CameraModule, shouldShowFullScreenCamera &&
|
|
3013
|
+
`${LivenessClassNames.CameraModule}--mobile`), "data-testid": testId, gap: "zero" },
|
|
2967
3014
|
!isCameraReady && centeredLoader,
|
|
3015
|
+
React__default["default"].createElement(Overlay, { horizontal: "center", vertical: isRecording && !isFlashingFreshness ? 'start' : 'space-between', className: LivenessClassNames.InstructionOverlay },
|
|
3016
|
+
isRecording && (React__default["default"].createElement(DefaultRecordingIcon, { recordingIndicatorText: recordingIndicatorText })),
|
|
3017
|
+
!isStartView && !isWaitingForCamera && !isCheckSucceeded && (React__default["default"].createElement(DefaultCancelButton, { cancelLivenessCheckText: cancelLivenessCheckText })),
|
|
3018
|
+
React__default["default"].createElement(uiReact.Flex, { className: ui.classNames(LivenessClassNames.Hint, shouldShowFullScreenCamera && `${LivenessClassNames.Hint}--mobile`) },
|
|
3019
|
+
React__default["default"].createElement(Hint, { hintDisplayText: hintDisplayText })),
|
|
3020
|
+
errorState && (React__default["default"].createElement(ErrorView, { onRetry: () => {
|
|
3021
|
+
send({ type: 'CANCEL' });
|
|
3022
|
+
}, displayText: errorDisplayText }, renderErrorModal({
|
|
3023
|
+
errorState,
|
|
3024
|
+
overrideErrorDisplayText: errorDisplayText,
|
|
3025
|
+
}))),
|
|
3026
|
+
isRecording &&
|
|
3027
|
+
!isFlashingFreshness &&
|
|
3028
|
+
showMatchIndicatorStates.includes(faceMatchState) ? (React__default["default"].createElement(MemoizedMatchIndicator, { percentage: Math.ceil(faceMatchPercentage) })) : null),
|
|
2968
3029
|
React__default["default"].createElement(uiReact.View, { as: "canvas", ref: freshnessColorRef, className: LivenessClassNames.FreshnessCanvas, hidden: true }),
|
|
2969
3030
|
React__default["default"].createElement(uiReact.View, { className: LivenessClassNames.VideoAnchor, style: {
|
|
2970
3031
|
aspectRatio: `${aspectRatio}`,
|
|
2971
3032
|
} },
|
|
2972
3033
|
React__default["default"].createElement("video", { ref: videoRef, muted: true, autoPlay: true, playsInline: true, width: mediaWidth, height: mediaHeight, onCanPlay: handleMediaPlay, "data-testid": "video", className: LivenessClassNames.Video, "aria-label": cameraDisplayText.a11yVideoLabelText }),
|
|
2973
|
-
React__default["default"].createElement(uiReact.Flex, { className: ui.classNames(LivenessClassNames.OvalCanvas,
|
|
3034
|
+
React__default["default"].createElement(uiReact.Flex, { className: ui.classNames(LivenessClassNames.OvalCanvas, shouldShowFullScreenCamera &&
|
|
3035
|
+
`${LivenessClassNames.OvalCanvas}--mobile`, isRecordingStopped && LivenessClassNames.FadeOut) },
|
|
2974
3036
|
React__default["default"].createElement(uiReact.View, { as: "canvas", ref: canvasRef })),
|
|
2975
|
-
isRecording && (React__default["default"].createElement(DefaultRecordingIcon, { recordingIndicatorText: recordingIndicatorText })),
|
|
2976
|
-
!isStartView && !isWaitingForCamera && !isCheckSucceeded && (React__default["default"].createElement(DefaultCancelButton, { cancelLivenessCheckText: cancelLivenessCheckText })),
|
|
2977
|
-
React__default["default"].createElement(Overlay, { horizontal: "center", vertical: isRecording && !isFlashingFreshness ? 'start' : 'space-between', className: LivenessClassNames.InstructionOverlay },
|
|
2978
|
-
React__default["default"].createElement(Hint, { hintDisplayText: hintDisplayText }),
|
|
2979
|
-
errorState && (React__default["default"].createElement(ErrorView, { onRetry: () => {
|
|
2980
|
-
send({ type: 'CANCEL' });
|
|
2981
|
-
}, displayText: errorDisplayText }, renderErrorModal({
|
|
2982
|
-
errorState,
|
|
2983
|
-
overrideErrorDisplayText: errorDisplayText,
|
|
2984
|
-
}))),
|
|
2985
|
-
isRecording &&
|
|
2986
|
-
!isFlashingFreshness &&
|
|
2987
|
-
showMatchIndicatorStates.includes(faceMatchState) ? (React__default["default"].createElement(MemoizedMatchIndicator, { percentage: Math.ceil(faceMatchPercentage) })) : null),
|
|
2988
3037
|
isStartView &&
|
|
2989
3038
|
!isMobileScreen &&
|
|
2990
3039
|
selectableDevices &&
|
|
@@ -3123,7 +3172,7 @@ function getDisplayText(overrideDisplayText) {
|
|
|
3123
3172
|
...defaultLivenessDisplayText,
|
|
3124
3173
|
...overrideDisplayText,
|
|
3125
3174
|
};
|
|
3126
|
-
const { a11yVideoLabelText, cameraMinSpecificationsHeadingText, cameraMinSpecificationsMessageText, cameraNotFoundHeadingText, cameraNotFoundMessageText, cancelLivenessCheckText, clientHeaderText, clientMessageText, hintCanNotIdentifyText, hintCenterFaceText, hintConnectingText, hintFaceDetectedText, hintHoldFaceForFreshnessText, hintIlluminationNormalText, hintIlluminationTooBrightText, hintIlluminationTooDarkText, hintMoveFaceFrontOfCameraText, hintTooManyFacesText, hintTooCloseText, hintTooFarText, hintVerifyingText, faceDistanceHeaderText, faceDistanceMessageText, goodFitCaptionText, goodFitAltText, landscapeHeaderText, landscapeMessageText, multipleFacesHeaderText, multipleFacesMessageText, photosensitivityWarningBodyText, photosensitivityWarningHeadingText, photosensitivityWarningInfoText, photosensitivityWarningLabelText, photosensitivyWarningBodyText, photosensitivyWarningHeadingText, photosensitivyWarningInfoText, photosensitivyWarningLabelText, portraitMessageText, retryCameraPermissionsText, recordingIndicatorText, serverHeaderText, serverMessageText, startScreenBeginCheckText, timeoutHeaderText, timeoutMessageText, tooFarCaptionText, tooFarAltText, tryAgainText, waitingCameraPermissionText, } = displayText;
|
|
3175
|
+
const { a11yVideoLabelText, cameraMinSpecificationsHeadingText, cameraMinSpecificationsMessageText, cameraNotFoundHeadingText, cameraNotFoundMessageText, cancelLivenessCheckText, clientHeaderText, clientMessageText, errorLabelText, hintCanNotIdentifyText, hintCenterFaceText, hintCenterFaceInstructionText, hintFaceOffCenterText, hintConnectingText, hintFaceDetectedText, hintHoldFaceForFreshnessText, hintIlluminationNormalText, hintIlluminationTooBrightText, hintIlluminationTooDarkText, hintMoveFaceFrontOfCameraText, hintTooManyFacesText, hintTooCloseText, hintTooFarText, hintVerifyingText, hintCheckCompleteText, hintMatchIndicatorText, faceDistanceHeaderText, faceDistanceMessageText, goodFitCaptionText, goodFitAltText, landscapeHeaderText, landscapeMessageText, multipleFacesHeaderText, multipleFacesMessageText, photosensitivityWarningBodyText, photosensitivityWarningHeadingText, photosensitivityWarningInfoText, photosensitivityWarningLabelText, photosensitivyWarningBodyText, photosensitivyWarningHeadingText, photosensitivyWarningInfoText, photosensitivyWarningLabelText, portraitMessageText, retryCameraPermissionsText, recordingIndicatorText, serverHeaderText, serverMessageText, startScreenBeginCheckText, timeoutHeaderText, timeoutMessageText, tooFarCaptionText, tooFarAltText, tryAgainText, waitingCameraPermissionText, } = displayText;
|
|
3127
3176
|
const hintDisplayText = {
|
|
3128
3177
|
hintMoveFaceFrontOfCameraText,
|
|
3129
3178
|
hintTooManyFacesText,
|
|
@@ -3133,11 +3182,15 @@ function getDisplayText(overrideDisplayText) {
|
|
|
3133
3182
|
hintTooFarText,
|
|
3134
3183
|
hintConnectingText,
|
|
3135
3184
|
hintVerifyingText,
|
|
3185
|
+
hintCheckCompleteText,
|
|
3136
3186
|
hintIlluminationTooBrightText,
|
|
3137
3187
|
hintIlluminationTooDarkText,
|
|
3138
3188
|
hintIlluminationNormalText,
|
|
3139
3189
|
hintHoldFaceForFreshnessText,
|
|
3140
3190
|
hintCenterFaceText,
|
|
3191
|
+
hintCenterFaceInstructionText,
|
|
3192
|
+
hintFaceOffCenterText,
|
|
3193
|
+
hintMatchIndicatorText,
|
|
3141
3194
|
};
|
|
3142
3195
|
const cameraDisplayText = {
|
|
3143
3196
|
cameraMinSpecificationsHeadingText,
|
|
@@ -3168,6 +3221,7 @@ function getDisplayText(overrideDisplayText) {
|
|
|
3168
3221
|
recordingIndicatorText,
|
|
3169
3222
|
};
|
|
3170
3223
|
const errorDisplayText = {
|
|
3224
|
+
errorLabelText,
|
|
3171
3225
|
timeoutHeaderText,
|
|
3172
3226
|
timeoutMessageText,
|
|
3173
3227
|
faceDistanceHeaderText,
|
package/dist/styles.css
CHANGED
|
@@ -3911,6 +3911,12 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
3911
3911
|
right: var(--amplify-space-medium);
|
|
3912
3912
|
}
|
|
3913
3913
|
|
|
3914
|
+
.liveness-detector .amplify-button--primary:focus {
|
|
3915
|
+
box-shadow: unset;
|
|
3916
|
+
outline: var(--amplify-components-button-focus-color) solid 2px;
|
|
3917
|
+
outline-offset: 2px;
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3914
3920
|
.amplify-liveness-cancel-button {
|
|
3915
3921
|
background-color: #fff;
|
|
3916
3922
|
color: hsl(190, 95%, 30%);
|
|
@@ -3945,6 +3951,7 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
3945
3951
|
left: 0;
|
|
3946
3952
|
height: 100%;
|
|
3947
3953
|
width: 100%;
|
|
3954
|
+
z-index: 2;
|
|
3948
3955
|
}
|
|
3949
3956
|
|
|
3950
3957
|
.amplify-liveness-video {
|
|
@@ -4014,7 +4021,7 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
4014
4021
|
}
|
|
4015
4022
|
|
|
4016
4023
|
.amplify-liveness-instruction-overlay {
|
|
4017
|
-
z-index:
|
|
4024
|
+
z-index: 2;
|
|
4018
4025
|
}
|
|
4019
4026
|
|
|
4020
4027
|
.amplify-liveness-countdown-container {
|
|
@@ -4248,6 +4255,10 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
4248
4255
|
font-weight: var(--amplify-font-weights-bold);
|
|
4249
4256
|
}
|
|
4250
4257
|
|
|
4258
|
+
.amplify-liveness-hint--mobile {
|
|
4259
|
+
margin-top: var(--amplify-space-xxxl);
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4251
4262
|
.amplify-liveness-hint__text {
|
|
4252
4263
|
align-items: center;
|
|
4253
4264
|
gap: var(--amplify-space-xs);
|
|
@@ -4325,6 +4336,7 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
4325
4336
|
flex-direction: column;
|
|
4326
4337
|
align-items: center;
|
|
4327
4338
|
justify-content: center;
|
|
4339
|
+
text-align: center;
|
|
4328
4340
|
height: 480px;
|
|
4329
4341
|
}
|
|
4330
4342
|
|
|
@@ -5327,6 +5339,9 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
5327
5339
|
var(--amplify-components-table-header-border-width)
|
|
5328
5340
|
var(--amplify-components-table-header-border-width);
|
|
5329
5341
|
}
|
|
5342
|
+
.amplify-table--striped .amplify-table__row:not(.amplify-table__head *):nth-child(odd) {
|
|
5343
|
+
background-color: var(--amplify-components-table-row-striped-background-color);
|
|
5344
|
+
}
|
|
5330
5345
|
.amplify-table__caption {
|
|
5331
5346
|
caption-side: var(--amplify-components-table-caption-caption-side);
|
|
5332
5347
|
color: var(--amplify-components-table-caption-color);
|
|
@@ -5385,9 +5400,6 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
5385
5400
|
.amplify-table__td:last-child {
|
|
5386
5401
|
border-right-width: var(--amplify-components-table-data-border-width);
|
|
5387
5402
|
}
|
|
5388
|
-
.amplify-table[data-variation=striped] .amplify-table__row:not(.amplify-table__head *):nth-child(odd) {
|
|
5389
|
-
background-color: var(--amplify-components-table-row-striped-background-color);
|
|
5390
|
-
}
|
|
5391
5403
|
.amplify-table[data-highlightonhover=true] .amplify-table__row:not(.amplify-table__head *):hover {
|
|
5392
5404
|
background-color: var(--amplify-components-table-row-hover-background-color);
|
|
5393
5405
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DisplayTextTemplate } from '@aws-amplify/ui';
|
|
1
2
|
export type HintDisplayText = {
|
|
2
3
|
hintMoveFaceFrontOfCameraText?: string;
|
|
3
4
|
hintTooManyFacesText?: string;
|
|
@@ -7,11 +8,15 @@ export type HintDisplayText = {
|
|
|
7
8
|
hintTooFarText?: string;
|
|
8
9
|
hintConnectingText?: string;
|
|
9
10
|
hintVerifyingText?: string;
|
|
11
|
+
hintCheckCompleteText?: string;
|
|
10
12
|
hintIlluminationTooBrightText?: string;
|
|
11
13
|
hintIlluminationTooDarkText?: string;
|
|
12
14
|
hintIlluminationNormalText?: string;
|
|
13
15
|
hintHoldFaceForFreshnessText?: string;
|
|
14
16
|
hintCenterFaceText?: string;
|
|
17
|
+
hintCenterFaceInstructionText?: string;
|
|
18
|
+
hintFaceOffCenterText?: string;
|
|
19
|
+
hintMatchIndicatorText?: string;
|
|
15
20
|
};
|
|
16
21
|
export type CameraDisplayText = {
|
|
17
22
|
cameraMinSpecificationsHeadingText?: string;
|
|
@@ -29,19 +34,32 @@ export type InstructionDisplayText = {
|
|
|
29
34
|
photosensitivityWarningHeadingText?: string;
|
|
30
35
|
photosensitivityWarningInfoText?: string;
|
|
31
36
|
photosensitivityWarningLabelText?: string;
|
|
37
|
+
startScreenBeginCheckText?: string;
|
|
38
|
+
tooFarCaptionText?: string;
|
|
39
|
+
tooFarAltText?: string;
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated `photosensitivyWarningBodyText` has been replaced with `photosensitivityWarningBodyText` amd will be removed in a future major version of `@aws-amplify/ui-react-liveness`
|
|
42
|
+
*/
|
|
32
43
|
photosensitivyWarningBodyText?: string;
|
|
44
|
+
/**
|
|
45
|
+
* @deprecated `photosensitivyWarningHeadingText` has been replaced with `photosensitivityWarningHeadingText` amd will be removed in a future major version of `@aws-amplify/ui-react-liveness`
|
|
46
|
+
*/
|
|
33
47
|
photosensitivyWarningHeadingText?: string;
|
|
48
|
+
/**
|
|
49
|
+
* @deprecated `photosensitivyWarningInfoText` has been replaced with `photosensitivityWarningInfoText` amd will be removed in a future major version of `@aws-amplify/ui-react-liveness`
|
|
50
|
+
*/
|
|
34
51
|
photosensitivyWarningInfoText?: string;
|
|
52
|
+
/**
|
|
53
|
+
* @deprecated `photosensitivyWarningLabelText` has been replaced with `photosensitivityWarningLabelText` amd will be removed in a future major version of `@aws-amplify/ui-react-liveness`
|
|
54
|
+
*/
|
|
35
55
|
photosensitivyWarningLabelText?: string;
|
|
36
|
-
startScreenBeginCheckText?: string;
|
|
37
|
-
tooFarCaptionText?: string;
|
|
38
|
-
tooFarAltText?: string;
|
|
39
56
|
};
|
|
40
57
|
export type StreamDisplayText = {
|
|
41
58
|
recordingIndicatorText?: string;
|
|
42
59
|
cancelLivenessCheckText?: string;
|
|
43
60
|
};
|
|
44
61
|
export declare const defaultErrorDisplayText: {
|
|
62
|
+
errorLabelText: string;
|
|
45
63
|
timeoutHeaderText: string;
|
|
46
64
|
timeoutMessageText: string;
|
|
47
65
|
faceDistanceHeaderText: string;
|
|
@@ -57,8 +75,6 @@ export declare const defaultErrorDisplayText: {
|
|
|
57
75
|
portraitMessageText: string;
|
|
58
76
|
tryAgainText: string;
|
|
59
77
|
};
|
|
60
|
-
export type
|
|
61
|
-
export type ErrorDisplayText = Partial<ErrorDisplayTextFoo>;
|
|
78
|
+
export type ErrorDisplayText = Partial<typeof defaultErrorDisplayText>;
|
|
62
79
|
export declare const defaultLivenessDisplayText: Required<LivenessDisplayText>;
|
|
63
|
-
export
|
|
64
|
-
}
|
|
80
|
+
export type LivenessDisplayText = DisplayTextTemplate<HintDisplayText & CameraDisplayText & InstructionDisplayText & ErrorDisplayText & StreamDisplayText>;
|
|
@@ -101,7 +101,8 @@ export declare enum FaceMatchState {
|
|
|
101
101
|
TOO_CLOSE = "TOO CLOSE",
|
|
102
102
|
CANT_IDENTIFY = "CANNOT IDENTIFY",
|
|
103
103
|
FACE_IDENTIFIED = "ONE FACE IDENTIFIED",
|
|
104
|
-
TOO_MANY = "TOO MANY FACES"
|
|
104
|
+
TOO_MANY = "TOO MANY FACES",
|
|
105
|
+
OFF_CENTER = "OFF CENTER"
|
|
105
106
|
}
|
|
106
107
|
export interface LivenessError {
|
|
107
108
|
state: ErrorState;
|
|
@@ -47,6 +47,9 @@ export declare function drawLivenessOvalInCanvas({ canvas, oval, scaleFactor, vi
|
|
|
47
47
|
videoEl: HTMLVideoElement;
|
|
48
48
|
isStartScreen?: boolean;
|
|
49
49
|
}): void;
|
|
50
|
+
export declare function clearOvalCanvas({ canvas, }: {
|
|
51
|
+
canvas: HTMLCanvasElement;
|
|
52
|
+
}): void;
|
|
50
53
|
interface FaceMatchStateInLivenessOval {
|
|
51
54
|
faceMatchState: FaceMatchState;
|
|
52
55
|
faceMatchPercentage: number;
|
|
@@ -12,6 +12,7 @@ export interface FaceLivenessErrorModalProps {
|
|
|
12
12
|
export declare const renderErrorModal: ({ errorState, overrideErrorDisplayText, }: {
|
|
13
13
|
errorState: ErrorState;
|
|
14
14
|
overrideErrorDisplayText?: Partial<{
|
|
15
|
+
errorLabelText: string;
|
|
15
16
|
timeoutHeaderText: string;
|
|
16
17
|
timeoutMessageText: string;
|
|
17
18
|
faceDistanceHeaderText: string;
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "3.0.
|
|
1
|
+
export declare const VERSION = "3.0.9";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-amplify/ui-react-liveness",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.9",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/esm/index.mjs",
|
|
6
6
|
"exports": {
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"react-dom": "^16.14.0 || ^17.0 || ^18.0"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@aws-amplify/ui": "6.0.
|
|
51
|
-
"@aws-amplify/ui-react": "6.
|
|
50
|
+
"@aws-amplify/ui": "6.0.7",
|
|
51
|
+
"@aws-amplify/ui-react": "6.1.1",
|
|
52
52
|
"@aws-sdk/client-rekognitionstreaming": "3.398.0",
|
|
53
53
|
"@aws-sdk/util-format-url": "^3.410.0",
|
|
54
54
|
"@smithy/eventstream-serde-browser": "^2.0.4",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"name": "FaceLivenessDetector",
|
|
81
81
|
"path": "dist/esm/index.mjs",
|
|
82
82
|
"import": "{ FaceLivenessDetector }",
|
|
83
|
-
"limit": "
|
|
83
|
+
"limit": "275 kB"
|
|
84
84
|
}
|
|
85
85
|
]
|
|
86
86
|
}
|