@aws-amplify/ui-react-liveness 3.0.8 → 3.0.10
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 +70 -11
- 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": "assertive"
|
|
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": "assertive", "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.10';
|
|
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": "assertive"
|
|
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": "assertive", "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
|
@@ -197,10 +197,10 @@
|
|
|
197
197
|
--amplify-components-button-outlined-success-active-border-color: var(--amplify-colors-green-100);
|
|
198
198
|
--amplify-components-button-outlined-success-active-background-color: var(--amplify-colors-green-20);
|
|
199
199
|
--amplify-components-button-outlined-success-active-color: var(--amplify-colors-green-100);
|
|
200
|
-
--amplify-components-button-outlined-error-border-color: var(--amplify-colors-red-
|
|
200
|
+
--amplify-components-button-outlined-error-border-color: var(--amplify-colors-red-80);
|
|
201
201
|
--amplify-components-button-outlined-error-background-color: transparent;
|
|
202
202
|
--amplify-components-button-outlined-error-color: var(--amplify-colors-red-100);
|
|
203
|
-
--amplify-components-button-outlined-error-hover-border-color: var(--amplify-colors-red-
|
|
203
|
+
--amplify-components-button-outlined-error-hover-border-color: var(--amplify-colors-red-80);
|
|
204
204
|
--amplify-components-button-outlined-error-hover-background-color: var(--amplify-colors-red-10);
|
|
205
205
|
--amplify-components-button-outlined-error-hover-color: var(--amplify-colors-red-100);
|
|
206
206
|
--amplify-components-button-outlined-error-focus-border-color: var(--amplify-colors-red-100);
|
|
@@ -851,6 +851,19 @@
|
|
|
851
851
|
--amplify-components-passwordfield-button-disabled-background-color: var(--amplify-components-button-disabled-background-color);
|
|
852
852
|
--amplify-components-passwordfield-button-disabled-border-color: var(--amplify-components-button-disabled-border-color);
|
|
853
853
|
--amplify-components-passwordfield-button-disabled-color: var(--amplify-components-button-disabled-color);
|
|
854
|
+
--amplify-components-passwordfield-button-error-color: var(--amplify-components-button-outlined-error-color);
|
|
855
|
+
--amplify-components-passwordfield-button-error-background-color: var(--amplify-components-button-outlined-error-background-color);
|
|
856
|
+
--amplify-components-passwordfield-button-error-border-color: var(--amplify-components-button-outlined-error-border-color);
|
|
857
|
+
--amplify-components-passwordfield-button-error-active-border-color: var(--amplify-components-button-outlined-error-active-border-color);
|
|
858
|
+
--amplify-components-passwordfield-button-error-active-background-color: var(--amplify-components-button-outlined-error-active-background-color);
|
|
859
|
+
--amplify-components-passwordfield-button-error-active-color: var(--amplify-components-button-outlined-error-active-color);
|
|
860
|
+
--amplify-components-passwordfield-button-error-focus-border-color: var(--amplify-components-button-outlined-error-focus-border-color);
|
|
861
|
+
--amplify-components-passwordfield-button-error-focus-background-color: var(--amplify-components-button-outlined-error-focus-background-color);
|
|
862
|
+
--amplify-components-passwordfield-button-error-focus-color: var(--amplify-components-button-outlined-error-focus-color);
|
|
863
|
+
--amplify-components-passwordfield-button-error-focus-box-shadow: var(--amplify-components-button-outlined-error-focus-box-shadow);
|
|
864
|
+
--amplify-components-passwordfield-button-error-hover-border-color: var(--amplify-components-button-outlined-error-hover-border-color);
|
|
865
|
+
--amplify-components-passwordfield-button-error-hover-background-color: var(--amplify-components-button-outlined-error-hover-background-color);
|
|
866
|
+
--amplify-components-passwordfield-button-error-hover-color: var(--amplify-components-button-outlined-error-hover-color);
|
|
854
867
|
--amplify-components-passwordfield-button-focus-background-color: var(--amplify-components-button-focus-background-color);
|
|
855
868
|
--amplify-components-passwordfield-button-focus-border-color: var(--amplify-components-button-focus-border-color);
|
|
856
869
|
--amplify-components-passwordfield-button-focus-color: var(--amplify-components-button-focus-color);
|
|
@@ -3911,6 +3924,12 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
3911
3924
|
right: var(--amplify-space-medium);
|
|
3912
3925
|
}
|
|
3913
3926
|
|
|
3927
|
+
.liveness-detector .amplify-button--primary:focus {
|
|
3928
|
+
box-shadow: unset;
|
|
3929
|
+
outline: var(--amplify-components-button-focus-color) solid 2px;
|
|
3930
|
+
outline-offset: 2px;
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3914
3933
|
.amplify-liveness-cancel-button {
|
|
3915
3934
|
background-color: #fff;
|
|
3916
3935
|
color: hsl(190, 95%, 30%);
|
|
@@ -3945,6 +3964,7 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
3945
3964
|
left: 0;
|
|
3946
3965
|
height: 100%;
|
|
3947
3966
|
width: 100%;
|
|
3967
|
+
z-index: 2;
|
|
3948
3968
|
}
|
|
3949
3969
|
|
|
3950
3970
|
.amplify-liveness-video {
|
|
@@ -4014,7 +4034,7 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
4014
4034
|
}
|
|
4015
4035
|
|
|
4016
4036
|
.amplify-liveness-instruction-overlay {
|
|
4017
|
-
z-index:
|
|
4037
|
+
z-index: 2;
|
|
4018
4038
|
}
|
|
4019
4039
|
|
|
4020
4040
|
.amplify-liveness-countdown-container {
|
|
@@ -4248,6 +4268,10 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
4248
4268
|
font-weight: var(--amplify-font-weights-bold);
|
|
4249
4269
|
}
|
|
4250
4270
|
|
|
4271
|
+
.amplify-liveness-hint--mobile {
|
|
4272
|
+
margin-top: var(--amplify-space-xxxl);
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4251
4275
|
.amplify-liveness-hint__text {
|
|
4252
4276
|
align-items: center;
|
|
4253
4277
|
gap: var(--amplify-space-xs);
|
|
@@ -4325,6 +4349,7 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
4325
4349
|
flex-direction: column;
|
|
4326
4350
|
align-items: center;
|
|
4327
4351
|
justify-content: center;
|
|
4352
|
+
text-align: center;
|
|
4328
4353
|
height: 480px;
|
|
4329
4354
|
}
|
|
4330
4355
|
|
|
@@ -4580,11 +4605,45 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
4580
4605
|
--amplify-components-button-hover-color: var(
|
|
4581
4606
|
--amplify-components-passwordfield-button-hover-color
|
|
4582
4607
|
);
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4608
|
+
--amplify-components-button-outlined-error-color: var(
|
|
4609
|
+
--amplify-components-passwordfield-button-error-color
|
|
4610
|
+
);
|
|
4611
|
+
--amplify-components-button-outlined-error-border-color: var(
|
|
4612
|
+
--amplify-components-passwordfield-button-error-border-color
|
|
4613
|
+
);
|
|
4614
|
+
--amplify-components-button-outlined-error-background-color: var(
|
|
4615
|
+
--amplify-components-passwordfield-button-error-background-color
|
|
4616
|
+
);
|
|
4617
|
+
--amplify-components-button-outlined-error-active-color: var(
|
|
4618
|
+
--amplify-components-passwordfield-button-error-active-color
|
|
4619
|
+
);
|
|
4620
|
+
--amplify-components-button-outlined-error-active-border-color: var(
|
|
4621
|
+
--amplify-components-passwordfield-button-error-active-border-color
|
|
4622
|
+
);
|
|
4623
|
+
--amplify-components-button-outlined-error-active-background-color: var(
|
|
4624
|
+
--amplify-components-passwordfield-button-error-active-background-color
|
|
4625
|
+
);
|
|
4626
|
+
--amplify-components-button-outlined-error-hover-color: var(
|
|
4627
|
+
--amplify-components-passwordfield-button-error-hover-color
|
|
4628
|
+
);
|
|
4629
|
+
--amplify-components-button-outlined-error-hover-border-color: var(
|
|
4630
|
+
--amplify-components-passwordfield-button-error-hover-border-color
|
|
4631
|
+
);
|
|
4632
|
+
--amplify-components-button-outlined-error-hover-background-color: var(
|
|
4633
|
+
--amplify-components-passwordfield-button-error-hover-background-color
|
|
4634
|
+
);
|
|
4635
|
+
--amplify-components-button-outlined-error-focus-color: var(
|
|
4636
|
+
--amplify-components-passwordfield-button-error-focus-color
|
|
4637
|
+
);
|
|
4638
|
+
--amplify-components-button-outlined-error-focus-box-shadow: var(
|
|
4639
|
+
--amplify-components-passwordfield-button-error-focus-box-shadow
|
|
4640
|
+
);
|
|
4641
|
+
--amplify-components-button-outlined-error-focus-border-color: var(
|
|
4642
|
+
--amplify-components-passwordfield-button-error-focus-border-color
|
|
4643
|
+
);
|
|
4644
|
+
--amplify-components-button-outlined-error-focus-background-color: var(
|
|
4645
|
+
--amplify-components-passwordfield-button-error-focus-background-color
|
|
4646
|
+
);
|
|
4588
4647
|
}
|
|
4589
4648
|
|
|
4590
4649
|
.amplify-phonenumberfield select:not(:focus) {
|
|
@@ -5327,6 +5386,9 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
5327
5386
|
var(--amplify-components-table-header-border-width)
|
|
5328
5387
|
var(--amplify-components-table-header-border-width);
|
|
5329
5388
|
}
|
|
5389
|
+
.amplify-table--striped .amplify-table__row:not(.amplify-table__head *):nth-child(odd) {
|
|
5390
|
+
background-color: var(--amplify-components-table-row-striped-background-color);
|
|
5391
|
+
}
|
|
5330
5392
|
.amplify-table__caption {
|
|
5331
5393
|
caption-side: var(--amplify-components-table-caption-caption-side);
|
|
5332
5394
|
color: var(--amplify-components-table-caption-color);
|
|
@@ -5385,9 +5447,6 @@ html[dir=rtl] .amplify-field-group__inner-start {
|
|
|
5385
5447
|
.amplify-table__td:last-child {
|
|
5386
5448
|
border-right-width: var(--amplify-components-table-data-border-width);
|
|
5387
5449
|
}
|
|
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
5450
|
.amplify-table[data-highlightonhover=true] .amplify-table__row:not(.amplify-table__head *):hover {
|
|
5392
5451
|
background-color: var(--amplify-components-table-row-hover-background-color);
|
|
5393
5452
|
}
|
|
@@ -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.10";
|
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.10",
|
|
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.1.
|
|
50
|
+
"@aws-amplify/ui": "6.0.8",
|
|
51
|
+
"@aws-amplify/ui-react": "6.1.2",
|
|
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
|
}
|