@aws-amplify/ui-react-liveness 2.0.11 → 3.0.0
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/FaceLivenessDetector.mjs +17 -1
- package/dist/esm/components/FaceLivenessDetector/FaceLivenessDetectorCore.mjs +42 -1
- package/dist/esm/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.mjs +199 -1
- package/dist/esm/components/FaceLivenessDetector/LivenessCheck/LivenessCheck.mjs +97 -1
- package/dist/esm/components/FaceLivenessDetector/displayText.mjs +50 -1
- package/dist/esm/components/FaceLivenessDetector/hooks/useLivenessActor.mjs +13 -1
- package/dist/esm/components/FaceLivenessDetector/hooks/useLivenessSelector.mjs +12 -1
- package/dist/esm/components/FaceLivenessDetector/hooks/useMediaStreamInVideo.mjs +38 -1
- package/dist/esm/components/FaceLivenessDetector/providers/FaceLivenessDetectorProvider.mjs +15 -1
- package/dist/esm/components/FaceLivenessDetector/service/machine/index.mjs +1130 -1
- package/dist/esm/components/FaceLivenessDetector/service/types/error.mjs +16 -1
- package/dist/esm/components/FaceLivenessDetector/service/types/faceDetection.mjs +15 -1
- package/dist/esm/components/FaceLivenessDetector/service/types/liveness.mjs +23 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/CustomWebSocketFetchHandler.mjs +200 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/blazefaceFaceDetection.mjs +102 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/constants.mjs +18 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/eventUtils.mjs +30 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/freshnessColorDisplay.mjs +131 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/liveness.mjs +462 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/streamProvider.mjs +144 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/support.mjs +14 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/videoRecorder.mjs +98 -1
- package/dist/esm/components/FaceLivenessDetector/shared/CancelButton.mjs +24 -1
- package/dist/esm/components/FaceLivenessDetector/shared/DefaultStartScreenComponents.mjs +41 -1
- package/dist/esm/components/FaceLivenessDetector/shared/FaceLivenessErrorModal.mjs +88 -1
- package/dist/esm/components/FaceLivenessDetector/shared/Hint.mjs +114 -1
- package/dist/esm/components/FaceLivenessDetector/shared/LandscapeErrorModal.mjs +30 -1
- package/dist/esm/components/FaceLivenessDetector/shared/LivenessIconWithPopover.mjs +37 -1
- package/dist/esm/components/FaceLivenessDetector/shared/MatchIndicator.mjs +24 -1
- package/dist/esm/components/FaceLivenessDetector/shared/Overlay.mjs +9 -1
- package/dist/esm/components/FaceLivenessDetector/shared/RecordingIcon.mjs +13 -1
- package/dist/esm/components/FaceLivenessDetector/shared/Toast.mjs +12 -1
- package/dist/esm/components/FaceLivenessDetector/types/classNames.mjs +54 -1
- package/dist/esm/components/FaceLivenessDetector/utils/device.mjs +24 -1
- package/dist/esm/components/FaceLivenessDetector/utils/getDisplayText.mjs +78 -1
- package/dist/esm/components/FaceLivenessDetector/utils/helpers.mjs +14 -0
- package/dist/esm/components/FaceLivenessDetector/utils/platform.mjs +8 -1
- package/dist/esm/index.mjs +2 -1
- package/dist/esm/version.mjs +3 -1
- package/dist/index.js +3208 -1
- package/dist/styles.css +343 -680
- package/dist/types/components/FaceLivenessDetector/FaceLivenessDetector.d.ts +1 -1
- package/dist/types/components/FaceLivenessDetector/FaceLivenessDetectorCore.d.ts +1 -3
- package/dist/types/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.d.ts +7 -3
- package/dist/types/components/FaceLivenessDetector/LivenessCheck/LivenessCheck.d.ts +5 -3
- package/dist/types/components/FaceLivenessDetector/displayText.d.ts +3 -10
- package/dist/types/components/FaceLivenessDetector/service/machine/index.d.ts +1 -1
- package/dist/types/components/FaceLivenessDetector/service/types/faceDetection.d.ts +2 -0
- package/dist/types/components/FaceLivenessDetector/service/types/liveness.d.ts +1 -1
- package/dist/types/components/FaceLivenessDetector/service/types/machine.d.ts +3 -1
- package/dist/types/components/FaceLivenessDetector/service/utils/blazefaceFaceDetection.d.ts +4 -3
- package/dist/types/components/FaceLivenessDetector/service/utils/liveness.d.ts +5 -2
- package/dist/types/components/FaceLivenessDetector/shared/DefaultStartScreenComponents.d.ts +9 -15
- package/dist/types/components/FaceLivenessDetector/shared/Overlay.d.ts +2 -5
- package/dist/types/components/FaceLivenessDetector/shared/Toast.d.ts +1 -0
- package/dist/types/components/FaceLivenessDetector/types/classNames.d.ts +3 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +16 -37
- package/dist/esm/components/FaceLivenessDetector/StartLiveness/StartLiveness.mjs +0 -1
- package/dist/esm/components/FaceLivenessDetector/StartLiveness/helpers.mjs +0 -1
- package/dist/esm/components/FaceLivenessDetector/shared/GoodFitIllustration.mjs +0 -1
- package/dist/esm/components/FaceLivenessDetector/shared/StartScreenFigure.mjs +0 -1
- package/dist/esm/components/FaceLivenessDetector/shared/TooFarIllustration.mjs +0 -1
- package/dist/types/components/FaceLivenessDetector/StartLiveness/StartLiveness.d.ts +0 -9
- package/dist/types/components/FaceLivenessDetector/StartLiveness/index.d.ts +0 -1
- /package/dist/types/components/FaceLivenessDetector/{StartLiveness → utils}/helpers.d.ts +0 -0
|
@@ -1 +1,98 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Helper wrapper class over the native MediaRecorder.
|
|
3
|
+
*/
|
|
4
|
+
class VideoRecorder {
|
|
5
|
+
constructor(stream, options = {}) {
|
|
6
|
+
if (typeof MediaRecorder === 'undefined') {
|
|
7
|
+
throw Error('MediaRecorder is not supported by this browser');
|
|
8
|
+
}
|
|
9
|
+
this._stream = stream;
|
|
10
|
+
this._options = options;
|
|
11
|
+
this._chunks = [];
|
|
12
|
+
this._recorder = new MediaRecorder(stream, { bitsPerSecond: 1000000 });
|
|
13
|
+
this._setupCallbacks();
|
|
14
|
+
}
|
|
15
|
+
getState() {
|
|
16
|
+
return this._recorder.state;
|
|
17
|
+
}
|
|
18
|
+
start(timeSlice) {
|
|
19
|
+
this.clearRecordedData();
|
|
20
|
+
this.recordingStartApiTimestamp = Date.now();
|
|
21
|
+
this._recorder.start(timeSlice);
|
|
22
|
+
}
|
|
23
|
+
async stop() {
|
|
24
|
+
if (this.getState() === 'recording') {
|
|
25
|
+
this._recorder.stop();
|
|
26
|
+
}
|
|
27
|
+
return this._recorderStopped;
|
|
28
|
+
}
|
|
29
|
+
pause() {
|
|
30
|
+
this._recorder.pause();
|
|
31
|
+
}
|
|
32
|
+
clearRecordedData() {
|
|
33
|
+
this._chunks = [];
|
|
34
|
+
}
|
|
35
|
+
dispatch(event) {
|
|
36
|
+
this._recorder.dispatchEvent(event);
|
|
37
|
+
}
|
|
38
|
+
getVideoChunkSize() {
|
|
39
|
+
return this._chunks.length;
|
|
40
|
+
}
|
|
41
|
+
_setupCallbacks() {
|
|
42
|
+
// Creates a Readablestream of video chunks. Waits to receive a clientSessionInfo event before pushing
|
|
43
|
+
// a livenessActionDocument to the ReadableStream and finally closing the ReadableStream
|
|
44
|
+
this.videoStream = new ReadableStream({
|
|
45
|
+
start: (controller) => {
|
|
46
|
+
if (!this._recorder) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this._recorder.ondataavailable = (e) => {
|
|
50
|
+
if (e.data && e.data.size > 0) {
|
|
51
|
+
if (this._chunks.length === 0) {
|
|
52
|
+
this.firstChunkTimestamp = Date.now();
|
|
53
|
+
}
|
|
54
|
+
this._chunks.push(e.data);
|
|
55
|
+
controller.enqueue(e.data);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
this._recorder.addEventListener('clientSesssionInfo', (e) => {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
|
|
60
|
+
controller.enqueue(e.data.clientInfo);
|
|
61
|
+
});
|
|
62
|
+
this._recorder.addEventListener('stopVideo', () => {
|
|
63
|
+
controller.enqueue('stopVideo');
|
|
64
|
+
});
|
|
65
|
+
this._recorder.addEventListener('endStream', () => {
|
|
66
|
+
controller.close();
|
|
67
|
+
});
|
|
68
|
+
this._recorder.addEventListener('endStreamWithCode', (e) => {
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
|
|
70
|
+
controller.enqueue({
|
|
71
|
+
type: 'endStreamWithCode',
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
73
|
+
code: e.data.code,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
this.recorderStarted = new Promise((resolve) => {
|
|
79
|
+
this._recorder.onstart = () => {
|
|
80
|
+
this.recorderStartTimestamp = Date.now();
|
|
81
|
+
resolve();
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
this._recorderStopped = new Promise((resolve) => {
|
|
85
|
+
this._recorder.onstop = () => {
|
|
86
|
+
this.recorderEndTimestamp = Date.now();
|
|
87
|
+
resolve();
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
this._recorder.onerror = () => {
|
|
91
|
+
if (this.getState() !== 'stopped') {
|
|
92
|
+
this.stop();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { VideoRecorder };
|
|
@@ -1 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { Button } from '@aws-amplify/ui-react';
|
|
3
|
+
import { IconClose } from '@aws-amplify/ui-react/internal';
|
|
4
|
+
import { useLivenessActor } from '../hooks/useLivenessActor.mjs';
|
|
5
|
+
import '@xstate/react';
|
|
6
|
+
import '../providers/FaceLivenessDetectorProvider.mjs';
|
|
7
|
+
import '@aws-amplify/ui';
|
|
8
|
+
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
9
|
+
|
|
10
|
+
const CancelButton = ({ ariaLabel }) => {
|
|
11
|
+
const [state, send] = useLivenessActor();
|
|
12
|
+
const isFinalState = state.done;
|
|
13
|
+
const handleClick = () => {
|
|
14
|
+
send({
|
|
15
|
+
type: 'CANCEL',
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
if (isFinalState)
|
|
19
|
+
return null;
|
|
20
|
+
return (React__default.createElement(Button, { autoFocus: true, variation: "link", onClick: handleClick, size: "large", className: LivenessClassNames.CancelButton, "aria-label": ariaLabel },
|
|
21
|
+
React__default.createElement(IconClose, { "aria-hidden": "true", "data-testid": "close-icon" })));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { CancelButton };
|
|
@@ -1 +1,41 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { ComponentClassName } from '@aws-amplify/ui';
|
|
3
|
+
import { Flex, View } from '@aws-amplify/ui-react';
|
|
4
|
+
import { CancelButton } from './CancelButton.mjs';
|
|
5
|
+
import '../service/machine/index.mjs';
|
|
6
|
+
import '../service/types/liveness.mjs';
|
|
7
|
+
import '@tensorflow/tfjs-core';
|
|
8
|
+
import '@tensorflow-models/face-detection';
|
|
9
|
+
import '@tensorflow/tfjs-backend-wasm';
|
|
10
|
+
import '@tensorflow/tfjs-backend-cpu';
|
|
11
|
+
import '@aws-amplify/core/internals/utils';
|
|
12
|
+
import 'aws-amplify/auth';
|
|
13
|
+
import '@aws-sdk/client-rekognitionstreaming';
|
|
14
|
+
import '@aws-sdk/util-format-url';
|
|
15
|
+
import '@smithy/eventstream-serde-browser';
|
|
16
|
+
import '@smithy/fetch-http-handler';
|
|
17
|
+
import '@smithy/protocol-http';
|
|
18
|
+
import '../service/utils/freshnessColorDisplay.mjs';
|
|
19
|
+
import '@xstate/react';
|
|
20
|
+
import '../providers/FaceLivenessDetectorProvider.mjs';
|
|
21
|
+
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
22
|
+
import { RecordingIcon } from './RecordingIcon.mjs';
|
|
23
|
+
import { LivenessIconWithPopover } from './LivenessIconWithPopover.mjs';
|
|
24
|
+
|
|
25
|
+
const DefaultPhotosensitiveWarning = ({ headingText, bodyText, infoText, }) => {
|
|
26
|
+
return (React__default.createElement(Flex, { className: `${ComponentClassName.Alert} ${LivenessClassNames.StartScreenWarning}`, style: { zIndex: '3' } },
|
|
27
|
+
React__default.createElement(View, { flex: "1" },
|
|
28
|
+
React__default.createElement(View, { className: ComponentClassName.AlertHeading }, headingText),
|
|
29
|
+
React__default.createElement(View, { className: ComponentClassName.AlertBody }, bodyText)),
|
|
30
|
+
React__default.createElement(LivenessIconWithPopover, null, infoText)));
|
|
31
|
+
};
|
|
32
|
+
const DefaultRecordingIcon = ({ recordingIndicatorText, }) => {
|
|
33
|
+
return (React__default.createElement(View, { className: LivenessClassNames.RecordingIconContainer },
|
|
34
|
+
React__default.createElement(RecordingIcon, null, recordingIndicatorText)));
|
|
35
|
+
};
|
|
36
|
+
const DefaultCancelButton = ({ cancelLivenessCheckText, }) => {
|
|
37
|
+
return (React__default.createElement(View, { className: LivenessClassNames.CancelContainer },
|
|
38
|
+
React__default.createElement(CancelButton, { ariaLabel: cancelLivenessCheckText })));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { DefaultCancelButton, DefaultPhotosensitiveWarning, DefaultRecordingIcon };
|
|
@@ -1 +1,88 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { Flex, Text, Button } from '@aws-amplify/ui-react';
|
|
3
|
+
import { AlertIcon } from '@aws-amplify/ui-react/internal';
|
|
4
|
+
import '../service/machine/index.mjs';
|
|
5
|
+
import '../service/types/liveness.mjs';
|
|
6
|
+
import { LivenessErrorState } from '../service/types/error.mjs';
|
|
7
|
+
import '@tensorflow/tfjs-core';
|
|
8
|
+
import '@tensorflow-models/face-detection';
|
|
9
|
+
import '@tensorflow/tfjs-backend-wasm';
|
|
10
|
+
import '@tensorflow/tfjs-backend-cpu';
|
|
11
|
+
import '@aws-amplify/core/internals/utils';
|
|
12
|
+
import 'aws-amplify/auth';
|
|
13
|
+
import '@aws-sdk/client-rekognitionstreaming';
|
|
14
|
+
import '@aws-sdk/util-format-url';
|
|
15
|
+
import '@smithy/eventstream-serde-browser';
|
|
16
|
+
import '@smithy/fetch-http-handler';
|
|
17
|
+
import '@smithy/protocol-http';
|
|
18
|
+
import '../service/utils/freshnessColorDisplay.mjs';
|
|
19
|
+
import { Toast } from './Toast.mjs';
|
|
20
|
+
import { Overlay } from './Overlay.mjs';
|
|
21
|
+
import { defaultErrorDisplayText } from '../displayText.mjs';
|
|
22
|
+
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
23
|
+
|
|
24
|
+
const renderToastErrorModal = (props) => {
|
|
25
|
+
const { error: errorState, displayText } = props;
|
|
26
|
+
const { timeoutHeaderText, timeoutMessageText, faceDistanceHeaderText, faceDistanceMessageText, multipleFacesHeaderText, multipleFacesMessageText, clientHeaderText, clientMessageText, serverHeaderText, serverMessageText, } = displayText;
|
|
27
|
+
let heading;
|
|
28
|
+
let message;
|
|
29
|
+
switch (errorState) {
|
|
30
|
+
case LivenessErrorState.TIMEOUT:
|
|
31
|
+
heading = timeoutHeaderText;
|
|
32
|
+
message = timeoutMessageText;
|
|
33
|
+
break;
|
|
34
|
+
case LivenessErrorState.FACE_DISTANCE_ERROR:
|
|
35
|
+
heading = faceDistanceHeaderText;
|
|
36
|
+
message = faceDistanceMessageText;
|
|
37
|
+
break;
|
|
38
|
+
case LivenessErrorState.MULTIPLE_FACES_ERROR:
|
|
39
|
+
heading = multipleFacesHeaderText;
|
|
40
|
+
message = multipleFacesMessageText;
|
|
41
|
+
break;
|
|
42
|
+
case LivenessErrorState.RUNTIME_ERROR:
|
|
43
|
+
heading = clientHeaderText;
|
|
44
|
+
message = clientMessageText;
|
|
45
|
+
break;
|
|
46
|
+
case LivenessErrorState.SERVER_ERROR:
|
|
47
|
+
default:
|
|
48
|
+
heading = serverHeaderText;
|
|
49
|
+
message = serverMessageText;
|
|
50
|
+
}
|
|
51
|
+
return (React__default.createElement(React__default.Fragment, null,
|
|
52
|
+
React__default.createElement(Flex, { className: LivenessClassNames.ErrorModal },
|
|
53
|
+
React__default.createElement(AlertIcon, { ariaHidden: true, variation: "error" }),
|
|
54
|
+
React__default.createElement(Text, { className: LivenessClassNames.ErrorModalHeading }, heading)),
|
|
55
|
+
message));
|
|
56
|
+
};
|
|
57
|
+
const renderErrorModal = ({ errorState, overrideErrorDisplayText, }) => {
|
|
58
|
+
const displayText = {
|
|
59
|
+
...defaultErrorDisplayText,
|
|
60
|
+
...overrideErrorDisplayText,
|
|
61
|
+
};
|
|
62
|
+
if (errorState === LivenessErrorState.CAMERA_ACCESS_ERROR ||
|
|
63
|
+
errorState === LivenessErrorState.CAMERA_FRAMERATE_ERROR ||
|
|
64
|
+
errorState === LivenessErrorState.MOBILE_LANDSCAPE_ERROR) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
return renderToastErrorModal({
|
|
69
|
+
error: errorState,
|
|
70
|
+
displayText,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const FaceLivenessErrorModal = (props) => {
|
|
75
|
+
const { children, onRetry, displayText: overrideErrorDisplayText } = props;
|
|
76
|
+
const displayText = {
|
|
77
|
+
...defaultErrorDisplayText,
|
|
78
|
+
...overrideErrorDisplayText,
|
|
79
|
+
};
|
|
80
|
+
const { tryAgainText } = displayText;
|
|
81
|
+
return (React__default.createElement(Overlay, { className: LivenessClassNames.OpaqueOverlay },
|
|
82
|
+
React__default.createElement(Toast, null,
|
|
83
|
+
children,
|
|
84
|
+
React__default.createElement(Flex, { justifyContent: "center" },
|
|
85
|
+
React__default.createElement(Button, { variation: "primary", type: "button", onClick: onRetry }, tryAgainText)))));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export { FaceLivenessErrorModal, renderErrorModal };
|
|
@@ -1 +1,114 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Flex, Loader, View } from '@aws-amplify/ui-react';
|
|
3
|
+
import '../service/machine/index.mjs';
|
|
4
|
+
import { FaceMatchState, IlluminationState } from '../service/types/liveness.mjs';
|
|
5
|
+
import '@tensorflow/tfjs-core';
|
|
6
|
+
import '@tensorflow-models/face-detection';
|
|
7
|
+
import '@tensorflow/tfjs-backend-wasm';
|
|
8
|
+
import '@tensorflow/tfjs-backend-cpu';
|
|
9
|
+
import '@aws-amplify/core/internals/utils';
|
|
10
|
+
import 'aws-amplify/auth';
|
|
11
|
+
import '@aws-sdk/client-rekognitionstreaming';
|
|
12
|
+
import '@aws-sdk/util-format-url';
|
|
13
|
+
import '@smithy/eventstream-serde-browser';
|
|
14
|
+
import '@smithy/fetch-http-handler';
|
|
15
|
+
import '@smithy/protocol-http';
|
|
16
|
+
import '../service/utils/freshnessColorDisplay.mjs';
|
|
17
|
+
import { useLivenessActor } from '../hooks/useLivenessActor.mjs';
|
|
18
|
+
import { createLivenessSelector, useLivenessSelector } from '../hooks/useLivenessSelector.mjs';
|
|
19
|
+
import '@aws-amplify/ui';
|
|
20
|
+
import { Toast } from './Toast.mjs';
|
|
21
|
+
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
22
|
+
|
|
23
|
+
const selectErrorState = createLivenessSelector((state) => state.context.errorState);
|
|
24
|
+
const selectFaceMatchState = createLivenessSelector((state) => state.context.faceMatchAssociatedParams.faceMatchState);
|
|
25
|
+
const selectIlluminationState = createLivenessSelector((state) => state.context.faceMatchAssociatedParams.illuminationState);
|
|
26
|
+
const selectIsFaceFarEnoughBeforeRecording = createLivenessSelector((state) => state.context.isFaceFarEnoughBeforeRecording);
|
|
27
|
+
const selectFaceMatchStateBeforeStart = createLivenessSelector((state) => state.context.faceMatchStateBeforeStart);
|
|
28
|
+
const Hint = ({ hintDisplayText }) => {
|
|
29
|
+
const [state] = useLivenessActor();
|
|
30
|
+
// NOTE: Do not change order of these selectors as the unit tests depend on this order
|
|
31
|
+
const errorState = useLivenessSelector(selectErrorState);
|
|
32
|
+
const faceMatchState = useLivenessSelector(selectFaceMatchState);
|
|
33
|
+
const illuminationState = useLivenessSelector(selectIlluminationState);
|
|
34
|
+
const faceMatchStateBeforeStart = useLivenessSelector(selectFaceMatchStateBeforeStart);
|
|
35
|
+
const isFaceFarEnoughBeforeRecordingState = useLivenessSelector(selectIsFaceFarEnoughBeforeRecording);
|
|
36
|
+
const isCheckFaceDetectedBeforeStart = state.matches('checkFaceDetectedBeforeStart');
|
|
37
|
+
const isCheckFaceDistanceBeforeRecording = state.matches('checkFaceDistanceBeforeRecording');
|
|
38
|
+
const isStartView = state.matches('start') || state.matches('userCancel');
|
|
39
|
+
const isRecording = state.matches('recording');
|
|
40
|
+
const isNotRecording = state.matches('notRecording');
|
|
41
|
+
const isUploading = state.matches('uploading');
|
|
42
|
+
const isCheckSuccessful = state.matches('checkSucceeded');
|
|
43
|
+
const isCheckFailed = state.matches('checkFailed');
|
|
44
|
+
const isFlashingFreshness = state.matches({
|
|
45
|
+
recording: 'flashFreshnessColors',
|
|
46
|
+
});
|
|
47
|
+
const FaceMatchStateStringMap = {
|
|
48
|
+
[FaceMatchState.CANT_IDENTIFY]: hintDisplayText.hintCanNotIdentifyText,
|
|
49
|
+
[FaceMatchState.FACE_IDENTIFIED]: hintDisplayText.hintTooFarText,
|
|
50
|
+
[FaceMatchState.TOO_MANY]: hintDisplayText.hintTooManyFacesText,
|
|
51
|
+
[FaceMatchState.TOO_CLOSE]: hintDisplayText.hintTooCloseText,
|
|
52
|
+
[FaceMatchState.TOO_FAR]: hintDisplayText.hintTooFarText,
|
|
53
|
+
[FaceMatchState.MATCHED]: hintDisplayText.hintHoldFaceForFreshnessText,
|
|
54
|
+
};
|
|
55
|
+
const IlluminationStateStringMap = {
|
|
56
|
+
[IlluminationState.BRIGHT]: hintDisplayText.hintIlluminationTooBrightText,
|
|
57
|
+
[IlluminationState.DARK]: hintDisplayText.hintIlluminationTooDarkText,
|
|
58
|
+
[IlluminationState.NORMAL]: hintDisplayText.hintIlluminationNormalText,
|
|
59
|
+
};
|
|
60
|
+
const getInstructionContent = () => {
|
|
61
|
+
if (isStartView) {
|
|
62
|
+
return (React.createElement(Toast, { size: "large", variation: "primary", isInitial: true }, hintDisplayText.hintCenterFaceText));
|
|
63
|
+
}
|
|
64
|
+
if (errorState ?? (isCheckFailed || isCheckSuccessful)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!isRecording) {
|
|
68
|
+
if (isCheckFaceDetectedBeforeStart) {
|
|
69
|
+
if (faceMatchStateBeforeStart === FaceMatchState.TOO_MANY) {
|
|
70
|
+
return (React.createElement(Toast, { size: "large", variation: "primary" }, FaceMatchStateStringMap[faceMatchStateBeforeStart]));
|
|
71
|
+
}
|
|
72
|
+
return (React.createElement(Toast, { size: "large", variation: "primary" }, hintDisplayText.hintMoveFaceFrontOfCameraText));
|
|
73
|
+
}
|
|
74
|
+
// Specifically checking for false here because initially the value is undefined and we do not want to show the instruction
|
|
75
|
+
if (isCheckFaceDistanceBeforeRecording &&
|
|
76
|
+
isFaceFarEnoughBeforeRecordingState === false) {
|
|
77
|
+
return (React.createElement(Toast, { size: "large", variation: "primary" }, hintDisplayText.hintTooCloseText));
|
|
78
|
+
}
|
|
79
|
+
if (isNotRecording) {
|
|
80
|
+
return (React.createElement(Toast, null,
|
|
81
|
+
React.createElement(Flex, { className: LivenessClassNames.HintText },
|
|
82
|
+
React.createElement(Loader, null),
|
|
83
|
+
React.createElement(View, null, hintDisplayText.hintConnectingText))));
|
|
84
|
+
}
|
|
85
|
+
if (isUploading) {
|
|
86
|
+
return (React.createElement(Toast, null,
|
|
87
|
+
React.createElement(Flex, { className: LivenessClassNames.HintText },
|
|
88
|
+
React.createElement(Loader, null),
|
|
89
|
+
React.createElement(View, null, hintDisplayText.hintVerifyingText))));
|
|
90
|
+
}
|
|
91
|
+
if (illuminationState && illuminationState !== IlluminationState.NORMAL) {
|
|
92
|
+
return (React.createElement(Toast, { size: "large", variation: "primary" }, IlluminationStateStringMap[illuminationState]));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (isFlashingFreshness) {
|
|
96
|
+
return (React.createElement(Toast, { size: "large", variation: "primary" }, hintDisplayText.hintHoldFaceForFreshnessText));
|
|
97
|
+
}
|
|
98
|
+
if (isRecording && !isFlashingFreshness) {
|
|
99
|
+
// During face matching, we want to only show the TOO_CLOSE or
|
|
100
|
+
// TOO_FAR texts. If FaceMatchState matches TOO_CLOSE, we'll show
|
|
101
|
+
// the TOO_CLOSE text, but for FACE_IDENTIFED, CANT_IDENTIFY, TOO_MANY
|
|
102
|
+
// we are defaulting to the TOO_FAR text (for now). For MATCHED state,
|
|
103
|
+
// we don't want to show any toasts.
|
|
104
|
+
return (React.createElement(Toast, { size: "large", variation: faceMatchState === FaceMatchState.TOO_CLOSE ? 'error' : 'primary' }, faceMatchState === FaceMatchState.TOO_CLOSE
|
|
105
|
+
? FaceMatchStateStringMap[FaceMatchState.TOO_CLOSE]
|
|
106
|
+
: FaceMatchStateStringMap[FaceMatchState.TOO_FAR]));
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
};
|
|
110
|
+
const instructionContent = getInstructionContent();
|
|
111
|
+
return instructionContent ? instructionContent : null;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export { Hint, selectErrorState, selectFaceMatchState, selectFaceMatchStateBeforeStart, selectIlluminationState, selectIsFaceFarEnoughBeforeRecording };
|
|
@@ -1 +1,30 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Flex, Text, Button } from '@aws-amplify/ui-react';
|
|
3
|
+
import { getLandscapeMediaQuery } from '../utils/device.mjs';
|
|
4
|
+
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
5
|
+
|
|
6
|
+
const LandscapeErrorModal = (props) => {
|
|
7
|
+
const { onRetry, header, portraitMessage, landscapeMessage, tryAgainText } = props;
|
|
8
|
+
const [isLandscape, setIsLandscape] = React.useState(true);
|
|
9
|
+
React.useLayoutEffect(() => {
|
|
10
|
+
// Get orientation: landscape media query
|
|
11
|
+
const landscapeMediaQuery = getLandscapeMediaQuery();
|
|
12
|
+
// Set ui state for initial orientation
|
|
13
|
+
setIsLandscape(landscapeMediaQuery.matches);
|
|
14
|
+
// Listen for future orientation changes
|
|
15
|
+
landscapeMediaQuery.addEventListener('change', (e) => {
|
|
16
|
+
setIsLandscape(e.matches);
|
|
17
|
+
});
|
|
18
|
+
// Remove matchMedia event listener
|
|
19
|
+
return () => {
|
|
20
|
+
landscapeMediaQuery.removeEventListener('change', (e) => setIsLandscape(e.matches));
|
|
21
|
+
};
|
|
22
|
+
}, []);
|
|
23
|
+
return (React.createElement(Flex, { className: LivenessClassNames.LandscapeErrorModal, height: isLandscape ? 'auto' : 480 },
|
|
24
|
+
React.createElement(Text, { className: LivenessClassNames.LandscapeErrorModalHeader }, header),
|
|
25
|
+
React.createElement(Text, null, isLandscape ? landscapeMessage : portraitMessage),
|
|
26
|
+
!isLandscape ? (React.createElement(Flex, { className: LivenessClassNames.LandscapeErrorModalButton },
|
|
27
|
+
React.createElement(Button, { variation: "primary", type: "button", onClick: onRetry }, tryAgainText))) : null));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { LandscapeErrorModal };
|
|
@@ -1 +1,37 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Flex } from '@aws-amplify/ui-react';
|
|
3
|
+
import { useThemeBreakpoint, AlertIcon } from '@aws-amplify/ui-react/internal';
|
|
4
|
+
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Copied from src/primitives/Alert/AlertIcon.tsx because we want to re-use the icon but it is not currently expored by AlertIcon.
|
|
8
|
+
* We currently don't want to make a change to the AlertIcon primitive itself and may expose the icon in the future but for now so as not to introduce cross component dependencies we have duplicated it.
|
|
9
|
+
*/
|
|
10
|
+
const LivenessIconWithPopover = ({ children }) => {
|
|
11
|
+
const breakpoint = useThemeBreakpoint();
|
|
12
|
+
const [shouldShowPopover, setShouldShowPopover] = React.useState(false);
|
|
13
|
+
const wrapperRef = React.useRef(null);
|
|
14
|
+
const isMobileScreen = breakpoint === 'base';
|
|
15
|
+
React.useEffect(() => {
|
|
16
|
+
function handleClickOutside(event) {
|
|
17
|
+
if (shouldShowPopover &&
|
|
18
|
+
wrapperRef.current &&
|
|
19
|
+
!wrapperRef.current.contains(event.target)) {
|
|
20
|
+
setShouldShowPopover(false);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
24
|
+
return () => {
|
|
25
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
26
|
+
};
|
|
27
|
+
}, [wrapperRef, shouldShowPopover]);
|
|
28
|
+
return (React.createElement(Flex, { className: LivenessClassNames.Popover, onClick: () => setShouldShowPopover(!shouldShowPopover), ref: wrapperRef, testId: "popover-icon" },
|
|
29
|
+
React.createElement(AlertIcon, { ariaHidden: true, variation: "info" }),
|
|
30
|
+
shouldShowPopover && (React.createElement(React.Fragment, null,
|
|
31
|
+
React.createElement(Flex, { className: LivenessClassNames.PopoverAnchor }),
|
|
32
|
+
React.createElement(Flex, { className: LivenessClassNames.PopoverAnchorSecondary }),
|
|
33
|
+
React.createElement(Flex, { className: LivenessClassNames.PopoverContainer, left: isMobileScreen ? -190 : -108, "data-testid": "popover-text" }, children)))));
|
|
34
|
+
};
|
|
35
|
+
LivenessIconWithPopover.displayName = 'LivenessIconWithPopover';
|
|
36
|
+
|
|
37
|
+
export { LivenessIconWithPopover };
|
|
@@ -1 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
3
|
+
|
|
4
|
+
const MatchIndicator = ({ percentage, initialPercentage = 25, testId, }) => {
|
|
5
|
+
const [matchPercentage, setMatchPercentage] = React__default.useState(initialPercentage);
|
|
6
|
+
React__default.useEffect(() => {
|
|
7
|
+
if (percentage < 0) {
|
|
8
|
+
setMatchPercentage(0);
|
|
9
|
+
}
|
|
10
|
+
else if (percentage > 100) {
|
|
11
|
+
setMatchPercentage(100);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
setMatchPercentage(percentage);
|
|
15
|
+
}
|
|
16
|
+
}, [percentage]);
|
|
17
|
+
const percentageStyles = {
|
|
18
|
+
'--percentage': `${matchPercentage}%`,
|
|
19
|
+
};
|
|
20
|
+
return (React__default.createElement("div", { className: LivenessClassNames.MatchIndicator, "data-testid": testId },
|
|
21
|
+
React__default.createElement("div", { className: `${LivenessClassNames.MatchIndicator}__bar`, style: percentageStyles })));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { MatchIndicator };
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Flex } from '@aws-amplify/ui-react';
|
|
3
|
+
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
4
|
+
|
|
5
|
+
const Overlay = ({ children, horizontal = 'center', vertical = 'center', className, ...rest }) => {
|
|
6
|
+
return (React.createElement(Flex, { className: `${LivenessClassNames.Overlay} ${className}`, alignItems: horizontal, justifyContent: vertical, ...rest }, children));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export { Overlay };
|
|
@@ -1 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { Flex, Icon, Text } from '@aws-amplify/ui-react';
|
|
3
|
+
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
4
|
+
|
|
5
|
+
const RecordingIcon = ({ children }) => {
|
|
6
|
+
return (React__default.createElement(Flex, { className: LivenessClassNames.RecordingIcon },
|
|
7
|
+
React__default.createElement(Flex, { "data-testid": "rec-icon", justifyContent: "center" },
|
|
8
|
+
React__default.createElement(Icon, { viewBox: { width: 20, height: 20 }, width: "20", height: "20" },
|
|
9
|
+
React__default.createElement("circle", { cx: "10", cy: "10", r: "8", fill: "red" }))),
|
|
10
|
+
React__default.createElement(Text, { as: "span", fontWeight: "bold" }, children)));
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { RecordingIcon };
|
|
@@ -1 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useTheme, View, Flex } from '@aws-amplify/ui-react';
|
|
3
|
+
import { LivenessClassNames } from '../types/classNames.mjs';
|
|
4
|
+
|
|
5
|
+
const Toast = ({ variation = 'default', size = 'medium', children, isInitial = false, ...rest }) => {
|
|
6
|
+
const { tokens } = useTheme();
|
|
7
|
+
return (React.createElement(View, { className: `${LivenessClassNames.Toast} ${LivenessClassNames.Toast}--${variation} ${LivenessClassNames.Toast}--${size}`, ...(isInitial && { backgroundColor: tokens.colors.background.primary }), ...rest },
|
|
8
|
+
React.createElement(Flex, { className: LivenessClassNames.ToastContainer },
|
|
9
|
+
React.createElement(Flex, { className: LivenessClassNames.ToastMessage, ...(isInitial ? { color: tokens.colors.font.primary } : {}) }, children))));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { Toast };
|
|
@@ -1 +1,54 @@
|
|
|
1
|
-
var
|
|
1
|
+
var LivenessClassNames;
|
|
2
|
+
(function (LivenessClassNames) {
|
|
3
|
+
LivenessClassNames["CameraModule"] = "amplify-liveness-camera-module";
|
|
4
|
+
LivenessClassNames["CancelContainer"] = "amplify-liveness-cancel-container";
|
|
5
|
+
LivenessClassNames["CancelButton"] = "amplify-liveness-cancel-button";
|
|
6
|
+
LivenessClassNames["CountdownContainer"] = "amplify-liveness-countdown-container";
|
|
7
|
+
LivenessClassNames["DescriptionBullet"] = "amplify-liveness-description-bullet";
|
|
8
|
+
LivenessClassNames["DescriptionBulletIndex"] = "amplify-liveness-description-bullet__index";
|
|
9
|
+
LivenessClassNames["DescriptionBulletIndexText"] = "amplify-liveness-description-bullet__index__text";
|
|
10
|
+
LivenessClassNames["DescriptionBulletMessage"] = "amplify-liveness-description-bullet__message";
|
|
11
|
+
LivenessClassNames["ErrorModal"] = "amplify-liveness-error-modal";
|
|
12
|
+
LivenessClassNames["ErrorModalHeading"] = "amplify-liveness-error-modal__heading";
|
|
13
|
+
LivenessClassNames["FadeOut"] = "amplify-liveness-fade-out";
|
|
14
|
+
LivenessClassNames["FreshnessCanvas"] = "amplify-liveness-freshness-canvas";
|
|
15
|
+
LivenessClassNames["InstructionList"] = "amplify-liveness-instruction-list";
|
|
16
|
+
LivenessClassNames["InstructionOverlay"] = "amplify-liveness-instruction-overlay";
|
|
17
|
+
LivenessClassNames["Figure"] = "amplify-liveness-figure";
|
|
18
|
+
LivenessClassNames["FigureCaption"] = "amplify-liveness-figure__caption";
|
|
19
|
+
LivenessClassNames["FigureIcon"] = "amplify-liveness-figure__icon";
|
|
20
|
+
LivenessClassNames["FigureImage"] = "amplify-liveness-figure__image";
|
|
21
|
+
LivenessClassNames["Figures"] = "amplify-liveness-figures";
|
|
22
|
+
LivenessClassNames["Hint"] = "amplify-liveness-hint";
|
|
23
|
+
LivenessClassNames["HintText"] = "amplify-liveness-hint__text";
|
|
24
|
+
LivenessClassNames["LandscapeErrorModal"] = "amplify-liveness-landscape-error-modal";
|
|
25
|
+
LivenessClassNames["LandscapeErrorModalButton"] = "amplify-liveness-landscape-error-modal__button";
|
|
26
|
+
LivenessClassNames["LandscapeErrorModalHeader"] = "amplify-liveness-landscape-error-modal__header";
|
|
27
|
+
LivenessClassNames["Loader"] = "amplify-liveness-loader";
|
|
28
|
+
LivenessClassNames["MatchIndicator"] = "amplify-liveness-match-indicator";
|
|
29
|
+
LivenessClassNames["OvalCanvas"] = "amplify-liveness-oval-canvas";
|
|
30
|
+
LivenessClassNames["OpaqueOverlay"] = "amplify-liveness-overlay-opaque";
|
|
31
|
+
LivenessClassNames["Overlay"] = "amplify-liveness-overlay";
|
|
32
|
+
LivenessClassNames["Popover"] = "amplify-liveness-popover";
|
|
33
|
+
LivenessClassNames["PopoverContainer"] = "amplify-liveness-popover__container";
|
|
34
|
+
LivenessClassNames["PopoverAnchor"] = "amplify-liveness-popover__anchor";
|
|
35
|
+
LivenessClassNames["PopoverAnchorSecondary"] = "amplify-liveness-popover__anchor-secondary";
|
|
36
|
+
LivenessClassNames["RecordingIconContainer"] = "amplify-liveness-recording-icon-container";
|
|
37
|
+
LivenessClassNames["RecordingIcon"] = "amplify-liveness-recording-icon";
|
|
38
|
+
LivenessClassNames["StartScreenCameraSelect"] = "amplify-liveness-start-screen-camera-select";
|
|
39
|
+
LivenessClassNames["StartScreenCameraSelectContainer"] = "amplify-liveness-start-screen-camera-select__container";
|
|
40
|
+
LivenessClassNames["StartScreenCameraWaiting"] = "amplify-liveness-start-screen-camera-waiting";
|
|
41
|
+
LivenessClassNames["StartScreenHeader"] = "amplify-liveness-start-screen-header";
|
|
42
|
+
LivenessClassNames["StartScreenHeaderBody"] = "amplify-liveness-start-screen-header__body";
|
|
43
|
+
LivenessClassNames["StartScreenHeaderHeading"] = "amplify-liveness-start-screen-header__heading";
|
|
44
|
+
LivenessClassNames["StartScreenWarning"] = "amplify-liveness-start-screen-warning";
|
|
45
|
+
LivenessClassNames["StartScreenInstructions"] = "amplify-liveness-start-screen-instructions";
|
|
46
|
+
LivenessClassNames["StartScreenInstructionsHeading"] = "amplify-liveness-start-screen-instructions__heading";
|
|
47
|
+
LivenessClassNames["Toast"] = "amplify-liveness-toast";
|
|
48
|
+
LivenessClassNames["ToastContainer"] = "amplify-liveness-toast__container";
|
|
49
|
+
LivenessClassNames["ToastMessage"] = "amplify-liveness-toast__message";
|
|
50
|
+
LivenessClassNames["Video"] = "amplify-liveness-video";
|
|
51
|
+
LivenessClassNames["VideoAnchor"] = "amplify-liveness-video-anchor";
|
|
52
|
+
})(LivenessClassNames || (LivenessClassNames = {}));
|
|
53
|
+
|
|
54
|
+
export { LivenessClassNames };
|
|
@@ -1 +1,24 @@
|
|
|
1
|
-
function
|
|
1
|
+
function isNewerIpad() {
|
|
2
|
+
// iPads on iOS13+ return as if a desktop Mac
|
|
3
|
+
// so check for maxTouchPoints also.
|
|
4
|
+
return (/Macintosh/i.test(navigator.userAgent) &&
|
|
5
|
+
!!navigator.maxTouchPoints &&
|
|
6
|
+
navigator.maxTouchPoints > 1);
|
|
7
|
+
}
|
|
8
|
+
function isMobileScreen() {
|
|
9
|
+
const isMobileDevice =
|
|
10
|
+
// Test Android/iPhone/iPad
|
|
11
|
+
/Android|iPhone|iPad/i.test(navigator.userAgent) || isNewerIpad();
|
|
12
|
+
return isMobileDevice;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Use window.matchMedia to direct landscape orientation
|
|
16
|
+
* screen.orientation is not supported in Safari so we will use
|
|
17
|
+
* media query detection to listen for changes instead.
|
|
18
|
+
* @returns MediaQueryList object
|
|
19
|
+
*/
|
|
20
|
+
function getLandscapeMediaQuery() {
|
|
21
|
+
return window.matchMedia('(orientation: landscape)');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { getLandscapeMediaQuery, isMobileScreen };
|