@aws-amplify/ui-react-liveness 3.0.22 → 3.0.24
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/LivenessCheck.mjs +1 -1
- package/dist/esm/components/FaceLivenessDetector/service/machine/machine.mjs +46 -26
- package/dist/esm/components/FaceLivenessDetector/service/utils/videoRecorder.mjs +10 -1
- package/dist/esm/components/FaceLivenessDetector/utils/device.mjs +12 -1
- package/dist/esm/components/FaceLivenessDetector/utils/getDisplayText.mjs +24 -0
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +113 -51
- package/dist/types/components/FaceLivenessDetector/service/types/machine.d.ts +1 -0
- package/dist/types/components/FaceLivenessDetector/utils/device.d.ts +1 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import '@tensorflow-models/face-detection';
|
|
|
8
8
|
import '@tensorflow/tfjs-backend-wasm';
|
|
9
9
|
import '@tensorflow/tfjs-backend-cpu';
|
|
10
10
|
import '@aws-amplify/core/internals/utils';
|
|
11
|
+
import { isMobileScreen, getLandscapeMediaQuery } from '../utils/device.mjs';
|
|
11
12
|
import '@aws-sdk/client-rekognitionstreaming';
|
|
12
13
|
import '../service/utils/createStreamingClient/createStreamingClient.mjs';
|
|
13
14
|
import '../service/utils/freshnessColorDisplay.mjs';
|
|
@@ -15,7 +16,6 @@ import { LivenessCameraModule } from './LivenessCameraModule.mjs';
|
|
|
15
16
|
import { useLivenessActor } from '../hooks/useLivenessActor.mjs';
|
|
16
17
|
import { useLivenessSelector, createLivenessSelector } from '../hooks/useLivenessSelector.mjs';
|
|
17
18
|
import '@aws-amplify/ui';
|
|
18
|
-
import { isMobileScreen, getLandscapeMediaQuery } from '../utils/device.mjs';
|
|
19
19
|
import { CancelButton } from '../shared/CancelButton.mjs';
|
|
20
20
|
import { defaultErrorDisplayText } from '../displayText.mjs';
|
|
21
21
|
import { LandscapeErrorModal } from '../shared/LandscapeErrorModal.mjs';
|
|
@@ -153,6 +153,7 @@ const livenessMachine = createMachine({
|
|
|
153
153
|
},
|
|
154
154
|
RUNTIME_ERROR: {
|
|
155
155
|
target: 'error',
|
|
156
|
+
actions: 'updateErrorStateForRuntime',
|
|
156
157
|
},
|
|
157
158
|
MOBILE_LANDSCAPE_WARNING: {
|
|
158
159
|
target: 'mobileLandscapeWarning',
|
|
@@ -257,11 +258,14 @@ const livenessMachine = createMachine({
|
|
|
257
258
|
},
|
|
258
259
|
},
|
|
259
260
|
recording: {
|
|
260
|
-
entry: [
|
|
261
|
+
entry: [
|
|
262
|
+
'clearErrorState',
|
|
263
|
+
'startRecording',
|
|
264
|
+
'sendTimeoutAfterOvalDrawingDelay',
|
|
265
|
+
],
|
|
261
266
|
initial: 'ovalDrawing',
|
|
262
267
|
states: {
|
|
263
268
|
ovalDrawing: {
|
|
264
|
-
entry: 'sendTimeoutAfterOvalDrawingDelay',
|
|
265
269
|
invoke: {
|
|
266
270
|
src: 'detectInitialFaceAndDrawOval',
|
|
267
271
|
onDone: {
|
|
@@ -280,12 +284,23 @@ const livenessMachine = createMachine({
|
|
|
280
284
|
checkFaceDetected: {
|
|
281
285
|
after: {
|
|
282
286
|
0: {
|
|
283
|
-
target: '
|
|
287
|
+
target: 'cancelOvalDrawingTimeout',
|
|
284
288
|
cond: 'hasSingleFace',
|
|
285
289
|
},
|
|
286
290
|
100: { target: 'ovalDrawing' },
|
|
287
291
|
},
|
|
288
292
|
},
|
|
293
|
+
cancelOvalDrawingTimeout: {
|
|
294
|
+
entry: [
|
|
295
|
+
'cancelOvalDrawingTimeout',
|
|
296
|
+
'sendTimeoutAfterRecordingDelay',
|
|
297
|
+
],
|
|
298
|
+
after: {
|
|
299
|
+
0: {
|
|
300
|
+
target: 'checkRecordingStarted',
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
289
304
|
checkRecordingStarted: {
|
|
290
305
|
after: {
|
|
291
306
|
0: {
|
|
@@ -299,7 +314,7 @@ const livenessMachine = createMachine({
|
|
|
299
314
|
// Evaluates face match and moves to checkMatch
|
|
300
315
|
// which continually checks for match until either timeout or face match
|
|
301
316
|
ovalMatching: {
|
|
302
|
-
entry: '
|
|
317
|
+
entry: 'cancelRecordingTimeout',
|
|
303
318
|
invoke: {
|
|
304
319
|
src: 'detectFaceAndMatchOval',
|
|
305
320
|
onDone: {
|
|
@@ -360,7 +375,7 @@ const livenessMachine = createMachine({
|
|
|
360
375
|
initial: 'pending',
|
|
361
376
|
states: {
|
|
362
377
|
pending: {
|
|
363
|
-
entry: ['
|
|
378
|
+
entry: ['pauseVideoStream'],
|
|
364
379
|
invoke: {
|
|
365
380
|
src: 'stopVideo',
|
|
366
381
|
onDone: 'waitForDisconnectEvent',
|
|
@@ -380,7 +395,7 @@ const livenessMachine = createMachine({
|
|
|
380
395
|
},
|
|
381
396
|
},
|
|
382
397
|
getLivenessResult: {
|
|
383
|
-
entry: ['
|
|
398
|
+
entry: ['freezeStream'],
|
|
384
399
|
invoke: {
|
|
385
400
|
src: 'getLiveness',
|
|
386
401
|
onError: {
|
|
@@ -417,8 +432,8 @@ const livenessMachine = createMachine({
|
|
|
417
432
|
'cleanUpResources',
|
|
418
433
|
'callErrorCallback',
|
|
419
434
|
'cancelOvalDrawingTimeout',
|
|
420
|
-
'cancelWaitForDisconnectTimeout',
|
|
421
435
|
'cancelOvalMatchTimeout',
|
|
436
|
+
'cancelRecordingTimeout',
|
|
422
437
|
'freezeStream',
|
|
423
438
|
],
|
|
424
439
|
},
|
|
@@ -607,6 +622,7 @@ const livenessMachine = createMachine({
|
|
|
607
622
|
}),
|
|
608
623
|
updateErrorStateForTimeout: assign({
|
|
609
624
|
errorState: (_, event) => event.data?.errorState || LivenessErrorState.TIMEOUT,
|
|
625
|
+
errorMessage: (_, event) => event.data?.message,
|
|
610
626
|
}),
|
|
611
627
|
updateErrorStateForRuntime: assign({
|
|
612
628
|
errorState: (_, event) => event.data?.errorState ||
|
|
@@ -643,12 +659,32 @@ const livenessMachine = createMachine({
|
|
|
643
659
|
},
|
|
644
660
|
}),
|
|
645
661
|
// timeouts
|
|
646
|
-
sendTimeoutAfterOvalDrawingDelay: actions.send({
|
|
662
|
+
sendTimeoutAfterOvalDrawingDelay: actions.send({
|
|
663
|
+
type: 'RUNTIME_ERROR',
|
|
664
|
+
data: {
|
|
665
|
+
message: 'Client failed to draw oval.',
|
|
666
|
+
},
|
|
667
|
+
}, {
|
|
647
668
|
delay: 5000,
|
|
648
669
|
id: 'ovalDrawingTimeout',
|
|
649
670
|
}),
|
|
650
671
|
cancelOvalDrawingTimeout: actions.cancel('ovalDrawingTimeout'),
|
|
651
|
-
|
|
672
|
+
sendTimeoutAfterRecordingDelay: actions.send({
|
|
673
|
+
type: 'RUNTIME_ERROR',
|
|
674
|
+
data: {
|
|
675
|
+
message: 'Client failed to start recording.',
|
|
676
|
+
},
|
|
677
|
+
}, {
|
|
678
|
+
delay: 5000,
|
|
679
|
+
id: 'recordingTimeout',
|
|
680
|
+
}),
|
|
681
|
+
cancelRecordingTimeout: actions.cancel('recordingTimeout'),
|
|
682
|
+
sendTimeoutAfterOvalMatchDelay: actions.send({
|
|
683
|
+
type: 'TIMEOUT',
|
|
684
|
+
data: {
|
|
685
|
+
message: 'Client timed out waiting for face to match oval.',
|
|
686
|
+
},
|
|
687
|
+
}, {
|
|
652
688
|
delay: (context) => {
|
|
653
689
|
return (context.serverSessionInformation?.Challenge
|
|
654
690
|
?.FaceMovementAndLightChallenge?.ChallengeConfig
|
|
@@ -657,22 +693,6 @@ const livenessMachine = createMachine({
|
|
|
657
693
|
id: 'ovalMatchTimeout',
|
|
658
694
|
}),
|
|
659
695
|
cancelOvalMatchTimeout: actions.cancel('ovalMatchTimeout'),
|
|
660
|
-
sendTimeoutAfterWaitingForDisconnect: actions.send({
|
|
661
|
-
type: 'TIMEOUT',
|
|
662
|
-
data: { errorState: LivenessErrorState.SERVER_ERROR },
|
|
663
|
-
}, {
|
|
664
|
-
delay: 20000,
|
|
665
|
-
id: 'waitForDisconnectTimeout',
|
|
666
|
-
}),
|
|
667
|
-
cancelWaitForDisconnectTimeout: actions.cancel('waitForDisconnectTimeout'),
|
|
668
|
-
sendTimeoutAfterFaceDistanceDelay: actions.send({
|
|
669
|
-
type: 'RUNTIME_ERROR',
|
|
670
|
-
data: new Error('Avoid moving closer during countdown and ensure only one face is in front of camera.'),
|
|
671
|
-
}, {
|
|
672
|
-
delay: 0,
|
|
673
|
-
id: 'faceDistanceTimeout',
|
|
674
|
-
}),
|
|
675
|
-
cancelFaceDistanceTimeout: actions.cancel('faceDistanceTimeout'),
|
|
676
696
|
// callbacks
|
|
677
697
|
callUserPermissionDeniedCallback: assign({
|
|
678
698
|
errorState: (context, event) => {
|
|
@@ -700,7 +720,7 @@ const livenessMachine = createMachine({
|
|
|
700
720
|
context.componentProps.onUserCancel?.();
|
|
701
721
|
},
|
|
702
722
|
callUserTimeoutCallback: (context) => {
|
|
703
|
-
const error = new Error('Client Timeout');
|
|
723
|
+
const error = new Error(context.errorMessage ?? 'Client Timeout');
|
|
704
724
|
error.name = context.errorState;
|
|
705
725
|
const livenessError = {
|
|
706
726
|
state: context.errorState,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { isAndroidChromeWithBrokenH264 } from '../../utils/device.mjs';
|
|
2
|
+
|
|
3
|
+
// Only to be used with Chrome for the Android Chrome H264 Bug - https://issues.chromium.org/issues/343199623
|
|
4
|
+
const ALTERNATE_CHROME_MIME_TYPE = 'video/x-matroska;codecs=vp8';
|
|
1
5
|
/**
|
|
2
6
|
* Helper wrapper class over the native MediaRecorder.
|
|
3
7
|
*/
|
|
@@ -8,7 +12,12 @@ class VideoRecorder {
|
|
|
8
12
|
}
|
|
9
13
|
this._stream = stream;
|
|
10
14
|
this._chunks = [];
|
|
11
|
-
this._recorder = new MediaRecorder(stream, {
|
|
15
|
+
this._recorder = new MediaRecorder(stream, {
|
|
16
|
+
bitsPerSecond: 1000000,
|
|
17
|
+
mimeType: isAndroidChromeWithBrokenH264()
|
|
18
|
+
? ALTERNATE_CHROME_MIME_TYPE
|
|
19
|
+
: undefined,
|
|
20
|
+
});
|
|
12
21
|
this._setupCallbacks();
|
|
13
22
|
}
|
|
14
23
|
getState() {
|
|
@@ -20,5 +20,16 @@ function isMobileScreen() {
|
|
|
20
20
|
function getLandscapeMediaQuery() {
|
|
21
21
|
return window.matchMedia('(orientation: landscape)');
|
|
22
22
|
}
|
|
23
|
+
// minor version 146+ is confirmed to have the fix https://issues.chromium.org/issues/343199623#comment34
|
|
24
|
+
function isAndroidChromeWithBrokenH264() {
|
|
25
|
+
const groups = /Chrome\/125\.[0-9]+\.[0-9]+\.([0-9]+)/i.exec(navigator.userAgent);
|
|
26
|
+
if (!groups) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const minorVersion = groups[1];
|
|
30
|
+
return (/Android/i.test(navigator.userAgent) &&
|
|
31
|
+
/Chrome\/125/i.test(navigator.userAgent) &&
|
|
32
|
+
parseInt(minorVersion) < 146);
|
|
33
|
+
}
|
|
23
34
|
|
|
24
|
-
export { getLandscapeMediaQuery, isMobileScreen };
|
|
35
|
+
export { getLandscapeMediaQuery, isAndroidChromeWithBrokenH264, isMobileScreen };
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { defaultLivenessDisplayText } from '../displayText.mjs';
|
|
2
2
|
|
|
3
|
+
function getMergedDisplayText(overrideDisplayText) {
|
|
4
|
+
const mergeField = (correctKey, deprecatedKey) => {
|
|
5
|
+
if (overrideDisplayText[correctKey] &&
|
|
6
|
+
overrideDisplayText[correctKey] !== defaultLivenessDisplayText[correctKey]) {
|
|
7
|
+
return overrideDisplayText[correctKey];
|
|
8
|
+
}
|
|
9
|
+
else if (overrideDisplayText[deprecatedKey] &&
|
|
10
|
+
overrideDisplayText[deprecatedKey] !==
|
|
11
|
+
defaultLivenessDisplayText[correctKey]) {
|
|
12
|
+
return overrideDisplayText[deprecatedKey];
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
return defaultLivenessDisplayText[correctKey];
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
photosensitivityWarningBodyText: mergeField('photosensitivityWarningBodyText', 'photosensitivyWarningBodyText'),
|
|
20
|
+
photosensitivityWarningHeadingText: mergeField('photosensitivityWarningHeadingText', 'photosensitivyWarningHeadingText'),
|
|
21
|
+
photosensitivityWarningInfoText: mergeField('photosensitivityWarningInfoText', 'photosensitivyWarningInfoText'),
|
|
22
|
+
photosensitivityWarningLabelText: mergeField('photosensitivityWarningLabelText', 'photosensitivyWarningLabelText'),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
3
25
|
/**
|
|
4
26
|
* Merges optional displayText prop with
|
|
5
27
|
* defaultLivenessDisplayText and returns more bite size portions to pass
|
|
@@ -8,9 +30,11 @@ import { defaultLivenessDisplayText } from '../displayText.mjs';
|
|
|
8
30
|
* @returns hintDisplayText, cameraDisplayText, instructionDisplayText, cancelLivenessCheckText
|
|
9
31
|
*/
|
|
10
32
|
function getDisplayText(overrideDisplayText) {
|
|
33
|
+
const mergedDisplayText = getMergedDisplayText(overrideDisplayText ?? {});
|
|
11
34
|
const displayText = {
|
|
12
35
|
...defaultLivenessDisplayText,
|
|
13
36
|
...overrideDisplayText,
|
|
37
|
+
...mergedDisplayText,
|
|
14
38
|
};
|
|
15
39
|
const { a11yVideoLabelText, cameraMinSpecificationsHeadingText, cameraMinSpecificationsMessageText, cameraNotFoundHeadingText, cameraNotFoundMessageText, cancelLivenessCheckText, connectionTimeoutHeaderText, connectionTimeoutMessageText, 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
40
|
const hintDisplayText = {
|
package/dist/esm/version.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -642,6 +642,42 @@ class BlazeFaceFaceDetection extends FaceDetection {
|
|
|
642
642
|
}
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
+
function isNewerIpad() {
|
|
646
|
+
// iPads on iOS13+ return as if a desktop Mac
|
|
647
|
+
// so check for maxTouchPoints also.
|
|
648
|
+
return (/Macintosh/i.test(navigator.userAgent) &&
|
|
649
|
+
!!navigator.maxTouchPoints &&
|
|
650
|
+
navigator.maxTouchPoints > 1);
|
|
651
|
+
}
|
|
652
|
+
function isMobileScreen() {
|
|
653
|
+
const isMobileDevice =
|
|
654
|
+
// Test Android/iPhone/iPad
|
|
655
|
+
/Android|iPhone|iPad/i.test(navigator.userAgent) || isNewerIpad();
|
|
656
|
+
return isMobileDevice;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Use window.matchMedia to direct landscape orientation
|
|
660
|
+
* screen.orientation is not supported in Safari so we will use
|
|
661
|
+
* media query detection to listen for changes instead.
|
|
662
|
+
* @returns MediaQueryList object
|
|
663
|
+
*/
|
|
664
|
+
function getLandscapeMediaQuery() {
|
|
665
|
+
return window.matchMedia('(orientation: landscape)');
|
|
666
|
+
}
|
|
667
|
+
// minor version 146+ is confirmed to have the fix https://issues.chromium.org/issues/343199623#comment34
|
|
668
|
+
function isAndroidChromeWithBrokenH264() {
|
|
669
|
+
const groups = /Chrome\/125\.[0-9]+\.[0-9]+\.([0-9]+)/i.exec(navigator.userAgent);
|
|
670
|
+
if (!groups) {
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
const minorVersion = groups[1];
|
|
674
|
+
return (/Android/i.test(navigator.userAgent) &&
|
|
675
|
+
/Chrome\/125/i.test(navigator.userAgent) &&
|
|
676
|
+
parseInt(minorVersion) < 146);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Only to be used with Chrome for the Android Chrome H264 Bug - https://issues.chromium.org/issues/343199623
|
|
680
|
+
const ALTERNATE_CHROME_MIME_TYPE = 'video/x-matroska;codecs=vp8';
|
|
645
681
|
/**
|
|
646
682
|
* Helper wrapper class over the native MediaRecorder.
|
|
647
683
|
*/
|
|
@@ -652,7 +688,12 @@ class VideoRecorder {
|
|
|
652
688
|
}
|
|
653
689
|
this._stream = stream;
|
|
654
690
|
this._chunks = [];
|
|
655
|
-
this._recorder = new MediaRecorder(stream, {
|
|
691
|
+
this._recorder = new MediaRecorder(stream, {
|
|
692
|
+
bitsPerSecond: 1000000,
|
|
693
|
+
mimeType: isAndroidChromeWithBrokenH264()
|
|
694
|
+
? ALTERNATE_CHROME_MIME_TYPE
|
|
695
|
+
: undefined,
|
|
696
|
+
});
|
|
656
697
|
this._setupCallbacks();
|
|
657
698
|
}
|
|
658
699
|
getState() {
|
|
@@ -792,7 +833,7 @@ function getFaceMatchStateInLivenessOval({ face, ovalDetails, initialFaceInterse
|
|
|
792
833
|
return { faceMatchState, faceMatchPercentage };
|
|
793
834
|
}
|
|
794
835
|
|
|
795
|
-
const VERSION = '3.0.
|
|
836
|
+
const VERSION = '3.0.24';
|
|
796
837
|
|
|
797
838
|
const BASE_USER_AGENT = `ui-react-liveness/${VERSION}`;
|
|
798
839
|
const getLivenessUserAgent = () => {
|
|
@@ -1491,6 +1532,7 @@ const livenessMachine = xstate.createMachine({
|
|
|
1491
1532
|
},
|
|
1492
1533
|
RUNTIME_ERROR: {
|
|
1493
1534
|
target: 'error',
|
|
1535
|
+
actions: 'updateErrorStateForRuntime',
|
|
1494
1536
|
},
|
|
1495
1537
|
MOBILE_LANDSCAPE_WARNING: {
|
|
1496
1538
|
target: 'mobileLandscapeWarning',
|
|
@@ -1595,11 +1637,14 @@ const livenessMachine = xstate.createMachine({
|
|
|
1595
1637
|
},
|
|
1596
1638
|
},
|
|
1597
1639
|
recording: {
|
|
1598
|
-
entry: [
|
|
1640
|
+
entry: [
|
|
1641
|
+
'clearErrorState',
|
|
1642
|
+
'startRecording',
|
|
1643
|
+
'sendTimeoutAfterOvalDrawingDelay',
|
|
1644
|
+
],
|
|
1599
1645
|
initial: 'ovalDrawing',
|
|
1600
1646
|
states: {
|
|
1601
1647
|
ovalDrawing: {
|
|
1602
|
-
entry: 'sendTimeoutAfterOvalDrawingDelay',
|
|
1603
1648
|
invoke: {
|
|
1604
1649
|
src: 'detectInitialFaceAndDrawOval',
|
|
1605
1650
|
onDone: {
|
|
@@ -1618,12 +1663,23 @@ const livenessMachine = xstate.createMachine({
|
|
|
1618
1663
|
checkFaceDetected: {
|
|
1619
1664
|
after: {
|
|
1620
1665
|
0: {
|
|
1621
|
-
target: '
|
|
1666
|
+
target: 'cancelOvalDrawingTimeout',
|
|
1622
1667
|
cond: 'hasSingleFace',
|
|
1623
1668
|
},
|
|
1624
1669
|
100: { target: 'ovalDrawing' },
|
|
1625
1670
|
},
|
|
1626
1671
|
},
|
|
1672
|
+
cancelOvalDrawingTimeout: {
|
|
1673
|
+
entry: [
|
|
1674
|
+
'cancelOvalDrawingTimeout',
|
|
1675
|
+
'sendTimeoutAfterRecordingDelay',
|
|
1676
|
+
],
|
|
1677
|
+
after: {
|
|
1678
|
+
0: {
|
|
1679
|
+
target: 'checkRecordingStarted',
|
|
1680
|
+
},
|
|
1681
|
+
},
|
|
1682
|
+
},
|
|
1627
1683
|
checkRecordingStarted: {
|
|
1628
1684
|
after: {
|
|
1629
1685
|
0: {
|
|
@@ -1637,7 +1693,7 @@ const livenessMachine = xstate.createMachine({
|
|
|
1637
1693
|
// Evaluates face match and moves to checkMatch
|
|
1638
1694
|
// which continually checks for match until either timeout or face match
|
|
1639
1695
|
ovalMatching: {
|
|
1640
|
-
entry: '
|
|
1696
|
+
entry: 'cancelRecordingTimeout',
|
|
1641
1697
|
invoke: {
|
|
1642
1698
|
src: 'detectFaceAndMatchOval',
|
|
1643
1699
|
onDone: {
|
|
@@ -1698,7 +1754,7 @@ const livenessMachine = xstate.createMachine({
|
|
|
1698
1754
|
initial: 'pending',
|
|
1699
1755
|
states: {
|
|
1700
1756
|
pending: {
|
|
1701
|
-
entry: ['
|
|
1757
|
+
entry: ['pauseVideoStream'],
|
|
1702
1758
|
invoke: {
|
|
1703
1759
|
src: 'stopVideo',
|
|
1704
1760
|
onDone: 'waitForDisconnectEvent',
|
|
@@ -1718,7 +1774,7 @@ const livenessMachine = xstate.createMachine({
|
|
|
1718
1774
|
},
|
|
1719
1775
|
},
|
|
1720
1776
|
getLivenessResult: {
|
|
1721
|
-
entry: ['
|
|
1777
|
+
entry: ['freezeStream'],
|
|
1722
1778
|
invoke: {
|
|
1723
1779
|
src: 'getLiveness',
|
|
1724
1780
|
onError: {
|
|
@@ -1755,8 +1811,8 @@ const livenessMachine = xstate.createMachine({
|
|
|
1755
1811
|
'cleanUpResources',
|
|
1756
1812
|
'callErrorCallback',
|
|
1757
1813
|
'cancelOvalDrawingTimeout',
|
|
1758
|
-
'cancelWaitForDisconnectTimeout',
|
|
1759
1814
|
'cancelOvalMatchTimeout',
|
|
1815
|
+
'cancelRecordingTimeout',
|
|
1760
1816
|
'freezeStream',
|
|
1761
1817
|
],
|
|
1762
1818
|
},
|
|
@@ -1945,6 +2001,7 @@ const livenessMachine = xstate.createMachine({
|
|
|
1945
2001
|
}),
|
|
1946
2002
|
updateErrorStateForTimeout: xstate.assign({
|
|
1947
2003
|
errorState: (_, event) => event.data?.errorState || LivenessErrorState.TIMEOUT,
|
|
2004
|
+
errorMessage: (_, event) => event.data?.message,
|
|
1948
2005
|
}),
|
|
1949
2006
|
updateErrorStateForRuntime: xstate.assign({
|
|
1950
2007
|
errorState: (_, event) => event.data?.errorState ||
|
|
@@ -1981,12 +2038,32 @@ const livenessMachine = xstate.createMachine({
|
|
|
1981
2038
|
},
|
|
1982
2039
|
}),
|
|
1983
2040
|
// timeouts
|
|
1984
|
-
sendTimeoutAfterOvalDrawingDelay: xstate.actions.send({
|
|
2041
|
+
sendTimeoutAfterOvalDrawingDelay: xstate.actions.send({
|
|
2042
|
+
type: 'RUNTIME_ERROR',
|
|
2043
|
+
data: {
|
|
2044
|
+
message: 'Client failed to draw oval.',
|
|
2045
|
+
},
|
|
2046
|
+
}, {
|
|
1985
2047
|
delay: 5000,
|
|
1986
2048
|
id: 'ovalDrawingTimeout',
|
|
1987
2049
|
}),
|
|
1988
2050
|
cancelOvalDrawingTimeout: xstate.actions.cancel('ovalDrawingTimeout'),
|
|
1989
|
-
|
|
2051
|
+
sendTimeoutAfterRecordingDelay: xstate.actions.send({
|
|
2052
|
+
type: 'RUNTIME_ERROR',
|
|
2053
|
+
data: {
|
|
2054
|
+
message: 'Client failed to start recording.',
|
|
2055
|
+
},
|
|
2056
|
+
}, {
|
|
2057
|
+
delay: 5000,
|
|
2058
|
+
id: 'recordingTimeout',
|
|
2059
|
+
}),
|
|
2060
|
+
cancelRecordingTimeout: xstate.actions.cancel('recordingTimeout'),
|
|
2061
|
+
sendTimeoutAfterOvalMatchDelay: xstate.actions.send({
|
|
2062
|
+
type: 'TIMEOUT',
|
|
2063
|
+
data: {
|
|
2064
|
+
message: 'Client timed out waiting for face to match oval.',
|
|
2065
|
+
},
|
|
2066
|
+
}, {
|
|
1990
2067
|
delay: (context) => {
|
|
1991
2068
|
return (context.serverSessionInformation?.Challenge
|
|
1992
2069
|
?.FaceMovementAndLightChallenge?.ChallengeConfig
|
|
@@ -1995,22 +2072,6 @@ const livenessMachine = xstate.createMachine({
|
|
|
1995
2072
|
id: 'ovalMatchTimeout',
|
|
1996
2073
|
}),
|
|
1997
2074
|
cancelOvalMatchTimeout: xstate.actions.cancel('ovalMatchTimeout'),
|
|
1998
|
-
sendTimeoutAfterWaitingForDisconnect: xstate.actions.send({
|
|
1999
|
-
type: 'TIMEOUT',
|
|
2000
|
-
data: { errorState: LivenessErrorState.SERVER_ERROR },
|
|
2001
|
-
}, {
|
|
2002
|
-
delay: 20000,
|
|
2003
|
-
id: 'waitForDisconnectTimeout',
|
|
2004
|
-
}),
|
|
2005
|
-
cancelWaitForDisconnectTimeout: xstate.actions.cancel('waitForDisconnectTimeout'),
|
|
2006
|
-
sendTimeoutAfterFaceDistanceDelay: xstate.actions.send({
|
|
2007
|
-
type: 'RUNTIME_ERROR',
|
|
2008
|
-
data: new Error('Avoid moving closer during countdown and ensure only one face is in front of camera.'),
|
|
2009
|
-
}, {
|
|
2010
|
-
delay: 0,
|
|
2011
|
-
id: 'faceDistanceTimeout',
|
|
2012
|
-
}),
|
|
2013
|
-
cancelFaceDistanceTimeout: xstate.actions.cancel('faceDistanceTimeout'),
|
|
2014
2075
|
// callbacks
|
|
2015
2076
|
callUserPermissionDeniedCallback: xstate.assign({
|
|
2016
2077
|
errorState: (context, event) => {
|
|
@@ -2038,7 +2099,7 @@ const livenessMachine = xstate.createMachine({
|
|
|
2038
2099
|
context.componentProps.onUserCancel?.();
|
|
2039
2100
|
},
|
|
2040
2101
|
callUserTimeoutCallback: (context) => {
|
|
2041
|
-
const error = new Error('Client Timeout');
|
|
2102
|
+
const error = new Error(context.errorMessage ?? 'Client Timeout');
|
|
2042
2103
|
error.name = context.errorState;
|
|
2043
2104
|
const livenessError = {
|
|
2044
2105
|
state: context.errorState,
|
|
@@ -3085,29 +3146,6 @@ const LivenessCameraModule = (props) => {
|
|
|
3085
3146
|
React__default["default"].createElement(uiReact.Button, { variation: "primary", type: "button", onClick: beginLivenessCheck }, instructionDisplayText.startScreenBeginCheckText)))));
|
|
3086
3147
|
};
|
|
3087
3148
|
|
|
3088
|
-
function isNewerIpad() {
|
|
3089
|
-
// iPads on iOS13+ return as if a desktop Mac
|
|
3090
|
-
// so check for maxTouchPoints also.
|
|
3091
|
-
return (/Macintosh/i.test(navigator.userAgent) &&
|
|
3092
|
-
!!navigator.maxTouchPoints &&
|
|
3093
|
-
navigator.maxTouchPoints > 1);
|
|
3094
|
-
}
|
|
3095
|
-
function isMobileScreen() {
|
|
3096
|
-
const isMobileDevice =
|
|
3097
|
-
// Test Android/iPhone/iPad
|
|
3098
|
-
/Android|iPhone|iPad/i.test(navigator.userAgent) || isNewerIpad();
|
|
3099
|
-
return isMobileDevice;
|
|
3100
|
-
}
|
|
3101
|
-
/**
|
|
3102
|
-
* Use window.matchMedia to direct landscape orientation
|
|
3103
|
-
* screen.orientation is not supported in Safari so we will use
|
|
3104
|
-
* media query detection to listen for changes instead.
|
|
3105
|
-
* @returns MediaQueryList object
|
|
3106
|
-
*/
|
|
3107
|
-
function getLandscapeMediaQuery() {
|
|
3108
|
-
return window.matchMedia('(orientation: landscape)');
|
|
3109
|
-
}
|
|
3110
|
-
|
|
3111
3149
|
const LandscapeErrorModal = (props) => {
|
|
3112
3150
|
const { onRetry, header, portraitMessage, landscapeMessage, tryAgainText } = props;
|
|
3113
3151
|
const [isLandscape, setIsLandscape] = React__namespace.useState(true);
|
|
@@ -3200,6 +3238,28 @@ const LivenessCheck = ({ instructionDisplayText, hintDisplayText, cameraDisplayT
|
|
|
3200
3238
|
return (React__namespace.createElement(uiReact.Flex, { direction: "column", position: "relative", testId: CHECK_CLASS_NAME, className: CHECK_CLASS_NAME, gap: "xl" }, renderCheck()));
|
|
3201
3239
|
};
|
|
3202
3240
|
|
|
3241
|
+
function getMergedDisplayText(overrideDisplayText) {
|
|
3242
|
+
const mergeField = (correctKey, deprecatedKey) => {
|
|
3243
|
+
if (overrideDisplayText[correctKey] &&
|
|
3244
|
+
overrideDisplayText[correctKey] !== defaultLivenessDisplayText[correctKey]) {
|
|
3245
|
+
return overrideDisplayText[correctKey];
|
|
3246
|
+
}
|
|
3247
|
+
else if (overrideDisplayText[deprecatedKey] &&
|
|
3248
|
+
overrideDisplayText[deprecatedKey] !==
|
|
3249
|
+
defaultLivenessDisplayText[correctKey]) {
|
|
3250
|
+
return overrideDisplayText[deprecatedKey];
|
|
3251
|
+
}
|
|
3252
|
+
else {
|
|
3253
|
+
return defaultLivenessDisplayText[correctKey];
|
|
3254
|
+
}
|
|
3255
|
+
};
|
|
3256
|
+
return {
|
|
3257
|
+
photosensitivityWarningBodyText: mergeField('photosensitivityWarningBodyText', 'photosensitivyWarningBodyText'),
|
|
3258
|
+
photosensitivityWarningHeadingText: mergeField('photosensitivityWarningHeadingText', 'photosensitivyWarningHeadingText'),
|
|
3259
|
+
photosensitivityWarningInfoText: mergeField('photosensitivityWarningInfoText', 'photosensitivyWarningInfoText'),
|
|
3260
|
+
photosensitivityWarningLabelText: mergeField('photosensitivityWarningLabelText', 'photosensitivyWarningLabelText'),
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3203
3263
|
/**
|
|
3204
3264
|
* Merges optional displayText prop with
|
|
3205
3265
|
* defaultLivenessDisplayText and returns more bite size portions to pass
|
|
@@ -3208,9 +3268,11 @@ const LivenessCheck = ({ instructionDisplayText, hintDisplayText, cameraDisplayT
|
|
|
3208
3268
|
* @returns hintDisplayText, cameraDisplayText, instructionDisplayText, cancelLivenessCheckText
|
|
3209
3269
|
*/
|
|
3210
3270
|
function getDisplayText(overrideDisplayText) {
|
|
3271
|
+
const mergedDisplayText = getMergedDisplayText(overrideDisplayText ?? {});
|
|
3211
3272
|
const displayText = {
|
|
3212
3273
|
...defaultLivenessDisplayText,
|
|
3213
3274
|
...overrideDisplayText,
|
|
3275
|
+
...mergedDisplayText,
|
|
3214
3276
|
};
|
|
3215
3277
|
const { a11yVideoLabelText, cameraMinSpecificationsHeadingText, cameraMinSpecificationsMessageText, cameraNotFoundHeadingText, cameraNotFoundMessageText, cancelLivenessCheckText, connectionTimeoutHeaderText, connectionTimeoutMessageText, 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;
|
|
3216
3278
|
const hintDisplayText = {
|
|
@@ -39,6 +39,7 @@ export interface LivenessContext {
|
|
|
39
39
|
challengeId?: string;
|
|
40
40
|
componentProps?: FaceLivenessDetectorCoreProps;
|
|
41
41
|
errorState?: ErrorState;
|
|
42
|
+
errorMessage?: string;
|
|
42
43
|
faceMatchAssociatedParams?: FaceMatchAssociatedParams;
|
|
43
44
|
faceMatchStateBeforeStart?: FaceMatchState;
|
|
44
45
|
failedAttempts?: number;
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "3.0.
|
|
1
|
+
export declare const VERSION = "3.0.24";
|