@aws-amplify/ui-react-liveness 3.3.9 → 3.4.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/FaceLivenessDetectorCore.mjs +4 -2
- package/dist/esm/components/FaceLivenessDetector/LivenessCheck/CameraSelector.mjs +13 -0
- package/dist/esm/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.mjs +50 -28
- package/dist/esm/components/FaceLivenessDetector/LivenessCheck/LivenessCheck.mjs +5 -4
- package/dist/esm/components/FaceLivenessDetector/service/machine/machine.mjs +247 -314
- package/dist/esm/components/FaceLivenessDetector/service/utils/ColorSequenceDisplay/ColorSequenceDisplay.mjs +140 -0
- package/dist/esm/components/FaceLivenessDetector/service/utils/StreamRecorder/StreamRecorder.mjs +171 -0
- package/dist/esm/components/FaceLivenessDetector/service/utils/TelemetryReporter/TelemetryReporter.mjs +27 -0
- package/dist/esm/components/FaceLivenessDetector/service/utils/constants.mjs +30 -7
- package/dist/esm/components/FaceLivenessDetector/service/utils/createRequestStreamGenerator/createRequestStreamGenerator.mjs +32 -0
- package/dist/esm/components/FaceLivenessDetector/service/utils/createRequestStreamGenerator/utils.mjs +148 -0
- package/dist/esm/components/FaceLivenessDetector/service/utils/createStreamingClient/Signer.mjs +2 -3
- package/dist/esm/components/FaceLivenessDetector/service/utils/createStreamingClient/createStreamingClient.mjs +36 -6
- package/dist/esm/components/FaceLivenessDetector/service/utils/createStreamingClient/resolveCredentials.mjs +7 -6
- package/dist/esm/components/FaceLivenessDetector/service/utils/getFaceMatchStateInLivenessOval.mjs +9 -5
- package/dist/esm/components/FaceLivenessDetector/service/utils/liveness.mjs +19 -34
- package/dist/esm/components/FaceLivenessDetector/service/utils/{eventUtils.mjs → responseStreamEvent.mjs} +2 -2
- package/dist/esm/components/FaceLivenessDetector/service/utils/sessionInformation.mjs +45 -0
- package/dist/esm/components/FaceLivenessDetector/shared/DefaultStartScreenComponents.mjs +3 -2
- package/dist/esm/components/FaceLivenessDetector/shared/FaceLivenessErrorModal.mjs +4 -2
- package/dist/esm/components/FaceLivenessDetector/shared/Hint.mjs +4 -7
- package/dist/esm/components/FaceLivenessDetector/types/classNames.mjs +3 -0
- package/dist/esm/components/FaceLivenessDetector/utils/device.mjs +12 -12
- package/dist/esm/index.mjs +12 -0
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +956 -775
- package/dist/styles.css +17 -2
- package/dist/types/components/FaceLivenessDetector/LivenessCheck/CameraSelector.d.ts +8 -0
- package/dist/types/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.d.ts +1 -0
- package/dist/types/components/FaceLivenessDetector/index.d.ts +1 -0
- package/dist/types/components/FaceLivenessDetector/service/types/machine.d.ts +37 -24
- package/dist/types/components/FaceLivenessDetector/service/utils/ColorSequenceDisplay/ColorSequenceDisplay.d.ts +55 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/ColorSequenceDisplay/index.d.ts +2 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/StreamRecorder/StreamRecorder.d.ts +15 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/StreamRecorder/index.d.ts +1 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/TelemetryReporter/TelemetryReporter.d.ts +8 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/TelemetryReporter/index.d.ts +2 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/constants.d.ts +27 -3
- package/dist/types/components/FaceLivenessDetector/service/utils/createRequestStreamGenerator/createRequestStreamGenerator.d.ts +15 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/createRequestStreamGenerator/index.d.ts +2 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/createRequestStreamGenerator/utils.d.ts +30 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/createStreamingClient/Signer.d.ts +0 -1
- package/dist/types/components/FaceLivenessDetector/service/utils/createStreamingClient/createStreamingClient.d.ts +27 -5
- package/dist/types/components/FaceLivenessDetector/service/utils/createStreamingClient/index.d.ts +1 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/getFaceMatchStateInLivenessOval.d.ts +3 -4
- package/dist/types/components/FaceLivenessDetector/service/utils/index.d.ts +7 -4
- package/dist/types/components/FaceLivenessDetector/service/utils/liveness.d.ts +15 -26
- package/dist/types/components/FaceLivenessDetector/service/utils/{eventUtils.d.ts → responseStreamEvent.d.ts} +1 -1
- package/dist/types/components/FaceLivenessDetector/service/utils/sessionInformation.d.ts +7 -0
- package/dist/types/components/FaceLivenessDetector/service/utils/types.d.ts +21 -0
- package/dist/types/components/FaceLivenessDetector/types/classNames.d.ts +3 -0
- package/dist/types/components/FaceLivenessDetector/utils/device.d.ts +1 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +8 -8
- package/dist/esm/components/FaceLivenessDetector/service/utils/freshnessColorDisplay.mjs +0 -131
- package/dist/esm/components/FaceLivenessDetector/service/utils/streamProvider.mjs +0 -126
- package/dist/esm/components/FaceLivenessDetector/service/utils/videoRecorder.mjs +0 -108
- package/dist/types/components/FaceLivenessDetector/service/types/service.d.ts +0 -5
- package/dist/types/components/FaceLivenessDetector/service/utils/freshnessColorDisplay.d.ts +0 -21
- package/dist/types/components/FaceLivenessDetector/service/utils/streamProvider.d.ts +0 -42
- package/dist/types/components/FaceLivenessDetector/service/utils/videoRecorder.d.ts +0 -27
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { v4 } from 'uuid';
|
|
2
2
|
import { createMachine, assign, actions, spawn } from 'xstate';
|
|
3
|
-
import {
|
|
3
|
+
import { fillOverlayCanvasFractional, generateBboxFromLandmarks, getOvalBoundingBox, getIntersectionOverUnion, estimateIllumination, getOvalDetailsFromSessionInformation, drawLivenessOvalInCanvas, getStaticLivenessOvalDetails, isFaceDistanceBelowThreshold, getFaceMatchState, isCameraDeviceVirtual, getColorsSequencesFromSessionInformation } from '../utils/liveness.mjs';
|
|
4
4
|
import { FaceMatchState } from '../types/liveness.mjs';
|
|
5
5
|
import { LivenessErrorState } from '../types/error.mjs';
|
|
6
6
|
import { BlazeFaceFaceDetection } from '../utils/blazefaceFaceDetection.mjs';
|
|
7
7
|
import { getFaceMatchStateInLivenessOval } from '../utils/getFaceMatchStateInLivenessOval.mjs';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { ColorSequenceDisplay } from '../utils/ColorSequenceDisplay/ColorSequenceDisplay.mjs';
|
|
9
|
+
import { FACE_MOVEMENT_AND_LIGHT_CHALLENGE, FACE_MOVEMENT_CHALLENGE, WS_CLOSURE_CODE } from '../utils/constants.mjs';
|
|
10
|
+
import { createRequestStreamGenerator } from '../utils/createRequestStreamGenerator/createRequestStreamGenerator.mjs';
|
|
11
|
+
import { createSessionEndEvent, getTrackDimensions, createColorDisplayEvent, createSessionStartEvent } from '../utils/createRequestStreamGenerator/utils.mjs';
|
|
12
|
+
import { createStreamingClient } from '../utils/createStreamingClient/createStreamingClient.mjs';
|
|
13
|
+
import { createSessionInfoFromServerSessionInformation } from '../utils/sessionInformation.mjs';
|
|
14
|
+
import { StreamRecorder } from '../utils/StreamRecorder/StreamRecorder.mjs';
|
|
15
|
+
import { isServerSessionInformationEvent, isDisconnectionEvent, isValidationExceptionEvent, isInternalServerExceptionEvent, isThrottlingExceptionEvent, isServiceQuotaExceededExceptionEvent, isInvalidSignatureRegionException, isConnectionTimeoutError } from '../utils/responseStreamEvent.mjs';
|
|
11
16
|
import { STATIC_VIDEO_CONSTRAINTS } from '../../utils/helpers.mjs';
|
|
12
|
-
import {
|
|
17
|
+
import { TelemetryReporter } from '../utils/TelemetryReporter/TelemetryReporter.mjs';
|
|
13
18
|
|
|
14
19
|
const CAMERA_ID_KEY = 'AmplifyLivenessCameraId';
|
|
15
20
|
const DEFAULT_FACE_FIT_TIMEOUT = 7000;
|
|
@@ -18,11 +23,11 @@ const responseStreamActor = async (callback) => {
|
|
|
18
23
|
try {
|
|
19
24
|
const stream = await responseStream;
|
|
20
25
|
for await (const event of stream) {
|
|
21
|
-
if (
|
|
26
|
+
if (isServerSessionInformationEvent(event)) {
|
|
22
27
|
callback({
|
|
23
28
|
type: 'SET_SESSION_INFO',
|
|
24
29
|
data: {
|
|
25
|
-
|
|
30
|
+
serverSessionInformation: event.ServerSessionInformationEvent.SessionInformation,
|
|
26
31
|
},
|
|
27
32
|
});
|
|
28
33
|
}
|
|
@@ -74,21 +79,20 @@ const responseStreamActor = async (callback) => {
|
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
};
|
|
77
|
-
function getLastSelectedCameraId() {
|
|
78
|
-
return localStorage.getItem(CAMERA_ID_KEY);
|
|
79
|
-
}
|
|
80
82
|
function setLastSelectedCameraId(deviceId) {
|
|
81
83
|
localStorage.setItem(CAMERA_ID_KEY, deviceId);
|
|
82
84
|
}
|
|
83
85
|
const livenessMachine = createMachine({
|
|
84
86
|
id: 'livenessMachine',
|
|
85
|
-
initial: '
|
|
87
|
+
initial: 'initCamera',
|
|
86
88
|
predictableActionArguments: true,
|
|
87
89
|
context: {
|
|
88
|
-
challengeId:
|
|
90
|
+
challengeId: v4(),
|
|
91
|
+
errorMessage: undefined,
|
|
89
92
|
maxFailedAttempts: 0,
|
|
90
93
|
failedAttempts: 0,
|
|
91
94
|
componentProps: undefined,
|
|
95
|
+
parsedSessionInformation: undefined,
|
|
92
96
|
serverSessionInformation: undefined,
|
|
93
97
|
videoAssociatedParams: {
|
|
94
98
|
videoConstraints: STATIC_VIDEO_CONSTRAINTS,
|
|
@@ -113,8 +117,8 @@ const livenessMachine = createMachine({
|
|
|
113
117
|
freshnessColorEl: undefined,
|
|
114
118
|
freshnessColors: [],
|
|
115
119
|
freshnessColorsComplete: false,
|
|
116
|
-
freshnessColorDisplay: undefined,
|
|
117
120
|
},
|
|
121
|
+
colorSequenceDisplay: undefined,
|
|
118
122
|
errorState: undefined,
|
|
119
123
|
livenessStreamProvider: undefined,
|
|
120
124
|
responseStreamActorRef: undefined,
|
|
@@ -129,71 +133,82 @@ const livenessMachine = createMachine({
|
|
|
129
133
|
target: 'retryableTimeout',
|
|
130
134
|
actions: 'updateErrorStateForTimeout',
|
|
131
135
|
},
|
|
132
|
-
SET_SESSION_INFO: {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
},
|
|
136
|
-
DISCONNECT_EVENT: {
|
|
137
|
-
internal: true,
|
|
138
|
-
actions: 'updateShouldDisconnect',
|
|
139
|
-
},
|
|
140
|
-
SET_DOM_AND_CAMERA_DETAILS: {
|
|
141
|
-
actions: 'setDOMAndCameraDetails',
|
|
142
|
-
},
|
|
136
|
+
SET_SESSION_INFO: { internal: true, actions: 'updateSessionInfo' },
|
|
137
|
+
DISCONNECT_EVENT: { internal: true, actions: 'updateShouldDisconnect' },
|
|
138
|
+
SET_DOM_AND_CAMERA_DETAILS: { actions: 'setDOMAndCameraDetails' },
|
|
143
139
|
UPDATE_DEVICE_AND_STREAM: {
|
|
144
140
|
actions: 'updateDeviceAndStream',
|
|
141
|
+
target: 'start',
|
|
145
142
|
},
|
|
146
|
-
SERVER_ERROR: {
|
|
147
|
-
target: 'error',
|
|
148
|
-
actions: 'updateErrorStateForServer',
|
|
149
|
-
},
|
|
143
|
+
SERVER_ERROR: { target: 'error', actions: 'updateErrorStateForServer' },
|
|
150
144
|
CONNECTION_TIMEOUT: {
|
|
151
145
|
target: 'error',
|
|
152
146
|
actions: 'updateErrorStateForConnectionTimeout',
|
|
153
147
|
},
|
|
154
|
-
RUNTIME_ERROR: {
|
|
155
|
-
target: 'error',
|
|
156
|
-
actions: 'updateErrorStateForRuntime',
|
|
157
|
-
},
|
|
148
|
+
RUNTIME_ERROR: { target: 'error', actions: 'updateErrorStateForRuntime' },
|
|
158
149
|
MOBILE_LANDSCAPE_WARNING: {
|
|
159
150
|
target: 'mobileLandscapeWarning',
|
|
160
151
|
actions: 'updateErrorStateForServer',
|
|
161
152
|
},
|
|
162
153
|
},
|
|
163
154
|
states: {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
target: '
|
|
170
|
-
actions: 'updateVideoMediaStream',
|
|
155
|
+
initCamera: {
|
|
156
|
+
initial: 'cameraCheck',
|
|
157
|
+
on: {
|
|
158
|
+
SET_DOM_AND_CAMERA_DETAILS: {
|
|
159
|
+
actions: 'setDOMAndCameraDetails',
|
|
160
|
+
target: '#livenessMachine.initWebsocket',
|
|
171
161
|
},
|
|
172
|
-
|
|
173
|
-
|
|
162
|
+
},
|
|
163
|
+
states: {
|
|
164
|
+
cameraCheck: {
|
|
165
|
+
entry: 'resetErrorState',
|
|
166
|
+
invoke: {
|
|
167
|
+
src: 'checkVirtualCameraAndGetStream',
|
|
168
|
+
onDone: {
|
|
169
|
+
target: 'waitForDOMAndCameraDetails',
|
|
170
|
+
actions: 'updateVideoMediaStream',
|
|
171
|
+
},
|
|
172
|
+
onError: { target: '#livenessMachine.permissionDenied' },
|
|
173
|
+
},
|
|
174
174
|
},
|
|
175
|
+
waitForDOMAndCameraDetails: {},
|
|
175
176
|
},
|
|
176
177
|
},
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
initWebsocket: {
|
|
179
|
+
entry: () => { },
|
|
180
|
+
initial: 'initializeLivenessStream',
|
|
181
|
+
states: {
|
|
182
|
+
initializeLivenessStream: {
|
|
183
|
+
invoke: {
|
|
184
|
+
src: 'openLivenessStreamConnection',
|
|
185
|
+
onDone: {
|
|
186
|
+
target: 'waitForSessionInfo',
|
|
187
|
+
actions: [
|
|
188
|
+
'updateLivenessStreamProvider',
|
|
189
|
+
'spawnResponseStreamActor',
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
waitForSessionInfo: {
|
|
195
|
+
entry: () => { },
|
|
196
|
+
after: {
|
|
197
|
+
0: {
|
|
198
|
+
target: '#livenessMachine.start',
|
|
199
|
+
cond: 'hasParsedSessionInfo',
|
|
200
|
+
},
|
|
201
|
+
100: { target: 'waitForSessionInfo' },
|
|
202
|
+
},
|
|
182
203
|
},
|
|
183
|
-
10: { target: 'waitForDOMAndCameraDetails' },
|
|
184
204
|
},
|
|
185
205
|
},
|
|
186
206
|
start: {
|
|
187
|
-
entry: ['
|
|
207
|
+
entry: ['initializeFaceDetector', () => { }],
|
|
188
208
|
always: [
|
|
189
|
-
{
|
|
190
|
-
target: 'detectFaceBeforeStart',
|
|
191
|
-
cond: 'shouldSkipStartScreen',
|
|
192
|
-
},
|
|
209
|
+
{ target: 'detectFaceBeforeStart', cond: 'shouldSkipStartScreen' },
|
|
193
210
|
],
|
|
194
|
-
on: {
|
|
195
|
-
BEGIN: 'detectFaceBeforeStart',
|
|
196
|
-
},
|
|
211
|
+
on: { BEGIN: 'detectFaceBeforeStart' },
|
|
197
212
|
},
|
|
198
213
|
detectFaceBeforeStart: {
|
|
199
214
|
invoke: {
|
|
@@ -225,38 +240,12 @@ const livenessMachine = createMachine({
|
|
|
225
240
|
checkFaceDistanceBeforeRecording: {
|
|
226
241
|
after: {
|
|
227
242
|
0: {
|
|
228
|
-
target: '
|
|
243
|
+
target: 'recording',
|
|
229
244
|
cond: 'hasEnoughFaceDistanceBeforeRecording',
|
|
230
245
|
},
|
|
231
246
|
100: { target: 'detectFaceDistanceBeforeRecording' },
|
|
232
247
|
},
|
|
233
248
|
},
|
|
234
|
-
initializeLivenessStream: {
|
|
235
|
-
invoke: {
|
|
236
|
-
src: 'openLivenessStreamConnection',
|
|
237
|
-
onDone: {
|
|
238
|
-
target: 'notRecording',
|
|
239
|
-
actions: [
|
|
240
|
-
'updateLivenessStreamProvider',
|
|
241
|
-
'spawnResponseStreamActor',
|
|
242
|
-
],
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
notRecording: {
|
|
247
|
-
initial: 'waitForSessionInfo',
|
|
248
|
-
states: {
|
|
249
|
-
waitForSessionInfo: {
|
|
250
|
-
after: {
|
|
251
|
-
0: {
|
|
252
|
-
target: '#livenessMachine.recording',
|
|
253
|
-
cond: 'hasServerSessionInfo',
|
|
254
|
-
},
|
|
255
|
-
100: { target: 'waitForSessionInfo' },
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
249
|
recording: {
|
|
261
250
|
entry: [
|
|
262
251
|
'clearErrorState',
|
|
@@ -283,10 +272,7 @@ const livenessMachine = createMachine({
|
|
|
283
272
|
},
|
|
284
273
|
checkFaceDetected: {
|
|
285
274
|
after: {
|
|
286
|
-
0: {
|
|
287
|
-
target: 'cancelOvalDrawingTimeout',
|
|
288
|
-
cond: 'hasSingleFace',
|
|
289
|
-
},
|
|
275
|
+
0: { target: 'cancelOvalDrawingTimeout', cond: 'hasSingleFace' },
|
|
290
276
|
100: { target: 'ovalDrawing' },
|
|
291
277
|
},
|
|
292
278
|
},
|
|
@@ -295,18 +281,15 @@ const livenessMachine = createMachine({
|
|
|
295
281
|
'cancelOvalDrawingTimeout',
|
|
296
282
|
'sendTimeoutAfterRecordingDelay',
|
|
297
283
|
],
|
|
298
|
-
after: {
|
|
299
|
-
0: {
|
|
300
|
-
target: 'checkRecordingStarted',
|
|
301
|
-
},
|
|
302
|
-
},
|
|
284
|
+
after: { 0: { target: 'checkRecordingStarted' } },
|
|
303
285
|
},
|
|
304
286
|
checkRecordingStarted: {
|
|
287
|
+
entry: () => { },
|
|
305
288
|
after: {
|
|
306
289
|
0: {
|
|
307
290
|
target: 'ovalMatching',
|
|
308
291
|
cond: 'hasRecordingStarted',
|
|
309
|
-
actions: '
|
|
292
|
+
actions: 'updateRecordingStartTimestamp',
|
|
310
293
|
},
|
|
311
294
|
100: { target: 'checkRecordingStarted' },
|
|
312
295
|
},
|
|
@@ -327,36 +310,41 @@ const livenessMachine = createMachine({
|
|
|
327
310
|
// for one second to show "Hold still" text before moving to `flashFreshnessColors`.
|
|
328
311
|
// If not, move back to ovalMatching and re-evaluate match state
|
|
329
312
|
checkMatch: {
|
|
313
|
+
entry: () => { },
|
|
330
314
|
after: {
|
|
331
315
|
0: {
|
|
332
|
-
target: '
|
|
316
|
+
target: 'handleChallenge',
|
|
333
317
|
cond: 'hasFaceMatchedInOval',
|
|
334
318
|
actions: [
|
|
335
319
|
'setFaceMatchTimeAndStartFace',
|
|
336
320
|
'updateEndFaceMatch',
|
|
337
|
-
'
|
|
321
|
+
'setColorDisplay',
|
|
338
322
|
'cancelOvalMatchTimeout',
|
|
339
323
|
'cancelOvalDrawingTimeout',
|
|
340
324
|
],
|
|
341
325
|
},
|
|
342
|
-
1: {
|
|
343
|
-
target: 'ovalMatching',
|
|
344
|
-
},
|
|
326
|
+
1: { target: 'ovalMatching' },
|
|
345
327
|
},
|
|
346
328
|
},
|
|
329
|
+
handleChallenge: {
|
|
330
|
+
entry: () => { },
|
|
331
|
+
always: [
|
|
332
|
+
{
|
|
333
|
+
target: 'delayBeforeFlash',
|
|
334
|
+
cond: 'isFaceMovementAndLightChallenge',
|
|
335
|
+
},
|
|
336
|
+
{ target: 'success', cond: 'isFaceMovementChallenge' },
|
|
337
|
+
],
|
|
338
|
+
},
|
|
347
339
|
delayBeforeFlash: {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
},
|
|
340
|
+
entry: () => { },
|
|
341
|
+
after: { 1000: 'flashFreshnessColors' },
|
|
351
342
|
},
|
|
352
343
|
flashFreshnessColors: {
|
|
353
344
|
invoke: {
|
|
354
345
|
src: 'flashColors',
|
|
355
346
|
onDone: [
|
|
356
|
-
{
|
|
357
|
-
target: 'success',
|
|
358
|
-
cond: 'hasFreshnessColorShown',
|
|
359
|
-
},
|
|
347
|
+
{ target: 'success', cond: 'hasFreshnessColorShown' },
|
|
360
348
|
{
|
|
361
349
|
target: 'flashFreshnessColors',
|
|
362
350
|
actions: 'updateFreshnessDetails',
|
|
@@ -364,10 +352,7 @@ const livenessMachine = createMachine({
|
|
|
364
352
|
],
|
|
365
353
|
},
|
|
366
354
|
},
|
|
367
|
-
success: {
|
|
368
|
-
entry: 'stopRecording',
|
|
369
|
-
type: 'final',
|
|
370
|
-
},
|
|
355
|
+
success: { entry: 'stopRecording', type: 'final' },
|
|
371
356
|
},
|
|
372
357
|
onDone: 'uploading',
|
|
373
358
|
},
|
|
@@ -386,11 +371,9 @@ const livenessMachine = createMachine({
|
|
|
386
371
|
},
|
|
387
372
|
},
|
|
388
373
|
waitForDisconnectEvent: {
|
|
374
|
+
entry: () => { },
|
|
389
375
|
after: {
|
|
390
|
-
0: {
|
|
391
|
-
target: 'getLivenessResult',
|
|
392
|
-
cond: 'getShouldDisconnect',
|
|
393
|
-
},
|
|
376
|
+
0: { cond: 'getShouldDisconnect', target: 'getLivenessResult' },
|
|
394
377
|
100: { target: 'waitForDisconnectEvent' },
|
|
395
378
|
},
|
|
396
379
|
},
|
|
@@ -409,16 +392,13 @@ const livenessMachine = createMachine({
|
|
|
409
392
|
retryableTimeout: {
|
|
410
393
|
entry: 'updateFailedAttempts',
|
|
411
394
|
always: [
|
|
412
|
-
{
|
|
413
|
-
|
|
414
|
-
cond: 'shouldTimeoutOnFailedAttempts',
|
|
415
|
-
},
|
|
416
|
-
{ target: 'notRecording' },
|
|
395
|
+
{ target: 'timeout', cond: 'shouldTimeoutOnFailedAttempts' },
|
|
396
|
+
{ target: 'start' },
|
|
417
397
|
],
|
|
418
398
|
},
|
|
419
399
|
permissionDenied: {
|
|
420
400
|
entry: 'callUserPermissionDeniedCallback',
|
|
421
|
-
on: { RETRY_CAMERA_CHECK: '
|
|
401
|
+
on: { RETRY_CAMERA_CHECK: 'initCamera' },
|
|
422
402
|
},
|
|
423
403
|
mobileLandscapeWarning: {
|
|
424
404
|
entry: 'callMobileLandscapeWarningCallback',
|
|
@@ -439,7 +419,7 @@ const livenessMachine = createMachine({
|
|
|
439
419
|
},
|
|
440
420
|
userCancel: {
|
|
441
421
|
entry: ['cleanUpResources', 'callUserCancelCallback', 'resetContext'],
|
|
442
|
-
always: { target: '
|
|
422
|
+
always: { target: 'initCamera' },
|
|
443
423
|
},
|
|
444
424
|
},
|
|
445
425
|
}, {
|
|
@@ -489,6 +469,7 @@ const livenessMachine = createMachine({
|
|
|
489
469
|
updateDeviceAndStream: assign({
|
|
490
470
|
videoAssociatedParams: (context, event) => {
|
|
491
471
|
setLastSelectedCameraId(event.data?.newDeviceId);
|
|
472
|
+
context.livenessStreamProvider?.setNewVideoStream(event.data?.newStream);
|
|
492
473
|
return {
|
|
493
474
|
...context.videoAssociatedParams,
|
|
494
475
|
selectedDeviceId: event.data
|
|
@@ -498,62 +479,32 @@ const livenessMachine = createMachine({
|
|
|
498
479
|
};
|
|
499
480
|
},
|
|
500
481
|
}),
|
|
501
|
-
|
|
502
|
-
const { canvasEl, videoEl, videoMediaStream } = context.videoAssociatedParams;
|
|
503
|
-
drawStaticOval(canvasEl, videoEl, videoMediaStream);
|
|
504
|
-
},
|
|
505
|
-
updateRecordingStartTimestampMs: assign({
|
|
482
|
+
updateRecordingStartTimestamp: assign({
|
|
506
483
|
videoAssociatedParams: (context) => {
|
|
507
|
-
const { challengeId,
|
|
508
|
-
const { recordingStartApiTimestamp, recorderStartTimestamp } = livenessStreamProvider.videoRecorder;
|
|
484
|
+
const { challengeId, ovalAssociatedParams, videoAssociatedParams, livenessStreamProvider, parsedSessionInformation, } = context;
|
|
509
485
|
const { videoMediaStream } = videoAssociatedParams;
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
.getTracks()[0]
|
|
521
|
-
.getSettings();
|
|
522
|
-
const flippedInitialFaceLeft = width - initialFace.left - initialFace.width;
|
|
523
|
-
context.livenessStreamProvider.sendClientInfo({
|
|
524
|
-
Challenge: {
|
|
525
|
-
FaceMovementAndLightChallenge: {
|
|
526
|
-
ChallengeId: challengeId,
|
|
527
|
-
VideoStartTimestamp: timestamp,
|
|
528
|
-
InitialFace: {
|
|
529
|
-
InitialFaceDetectedTimestamp: initialFace.timestampMs,
|
|
530
|
-
BoundingBox: getBoundingBox({
|
|
531
|
-
deviceHeight: height,
|
|
532
|
-
deviceWidth: width,
|
|
533
|
-
height: initialFace.height,
|
|
534
|
-
width: initialFace.width,
|
|
535
|
-
top: initialFace.top,
|
|
536
|
-
left: flippedInitialFaceLeft,
|
|
537
|
-
}),
|
|
538
|
-
},
|
|
539
|
-
},
|
|
540
|
-
},
|
|
486
|
+
const recordingStartedTimestamp = livenessStreamProvider.getRecordingStartTimestamp();
|
|
487
|
+
context.livenessStreamProvider.dispatchStreamEvent({
|
|
488
|
+
type: 'sessionInfo',
|
|
489
|
+
data: createSessionStartEvent({
|
|
490
|
+
parsedSessionInformation: parsedSessionInformation,
|
|
491
|
+
...getTrackDimensions(videoMediaStream),
|
|
492
|
+
challengeId: challengeId,
|
|
493
|
+
ovalAssociatedParams: ovalAssociatedParams,
|
|
494
|
+
recordingStartedTimestamp,
|
|
495
|
+
}),
|
|
541
496
|
});
|
|
542
497
|
return {
|
|
543
498
|
...context.videoAssociatedParams,
|
|
544
|
-
|
|
499
|
+
recordingStartedTimestamp,
|
|
545
500
|
};
|
|
546
501
|
},
|
|
547
502
|
}),
|
|
548
503
|
startRecording: assign({
|
|
549
504
|
videoAssociatedParams: (context) => {
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (context.livenessStreamProvider.videoRecorder &&
|
|
554
|
-
context.livenessStreamProvider.videoRecorder.getState() !==
|
|
555
|
-
'recording') {
|
|
556
|
-
context.livenessStreamProvider.startRecordingLivenessVideo();
|
|
505
|
+
if (context.livenessStreamProvider &&
|
|
506
|
+
!context.livenessStreamProvider.isRecording()) {
|
|
507
|
+
context.livenessStreamProvider.startRecording();
|
|
557
508
|
}
|
|
558
509
|
return { ...context.videoAssociatedParams };
|
|
559
510
|
},
|
|
@@ -633,8 +584,9 @@ const livenessMachine = createMachine({
|
|
|
633
584
|
}),
|
|
634
585
|
clearErrorState: assign({ errorState: (_) => undefined }),
|
|
635
586
|
updateSessionInfo: assign({
|
|
636
|
-
|
|
637
|
-
|
|
587
|
+
parsedSessionInformation: (_, event) => {
|
|
588
|
+
const { serverSessionInformation } = event.data;
|
|
589
|
+
return createSessionInfoFromServerSessionInformation(serverSessionInformation);
|
|
638
590
|
},
|
|
639
591
|
}),
|
|
640
592
|
updateShouldDisconnect: assign({ shouldDisconnect: () => true }),
|
|
@@ -647,47 +599,26 @@ const livenessMachine = createMachine({
|
|
|
647
599
|
};
|
|
648
600
|
},
|
|
649
601
|
}),
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const { serverSessionInformation } = context;
|
|
653
|
-
const freshnessColors = getColorsSequencesFromSessionInformation(serverSessionInformation);
|
|
654
|
-
const freshnessColorDisplay = new FreshnessColorDisplay(context, freshnessColors);
|
|
655
|
-
return {
|
|
656
|
-
...context.freshnessColorAssociatedParams,
|
|
657
|
-
freshnessColorDisplay,
|
|
658
|
-
};
|
|
659
|
-
},
|
|
602
|
+
setColorDisplay: assign({
|
|
603
|
+
colorSequenceDisplay: ({ parsedSessionInformation }) => new ColorSequenceDisplay(getColorsSequencesFromSessionInformation(parsedSessionInformation)),
|
|
660
604
|
}),
|
|
661
605
|
// timeouts
|
|
662
606
|
sendTimeoutAfterOvalDrawingDelay: actions.send({
|
|
663
607
|
type: 'RUNTIME_ERROR',
|
|
664
|
-
data: {
|
|
665
|
-
|
|
666
|
-
},
|
|
667
|
-
}, {
|
|
668
|
-
delay: 5000,
|
|
669
|
-
id: 'ovalDrawingTimeout',
|
|
670
|
-
}),
|
|
608
|
+
data: { message: 'Client failed to draw oval.' },
|
|
609
|
+
}, { delay: 5000, id: 'ovalDrawingTimeout' }),
|
|
671
610
|
cancelOvalDrawingTimeout: actions.cancel('ovalDrawingTimeout'),
|
|
672
611
|
sendTimeoutAfterRecordingDelay: actions.send({
|
|
673
612
|
type: 'RUNTIME_ERROR',
|
|
674
|
-
data: {
|
|
675
|
-
|
|
676
|
-
},
|
|
677
|
-
}, {
|
|
678
|
-
delay: 5000,
|
|
679
|
-
id: 'recordingTimeout',
|
|
680
|
-
}),
|
|
613
|
+
data: { message: 'Client failed to start recording.' },
|
|
614
|
+
}, { delay: 5000, id: 'recordingTimeout' }),
|
|
681
615
|
cancelRecordingTimeout: actions.cancel('recordingTimeout'),
|
|
682
616
|
sendTimeoutAfterOvalMatchDelay: actions.send({
|
|
683
617
|
type: 'TIMEOUT',
|
|
684
|
-
data: {
|
|
685
|
-
message: 'Client timed out waiting for face to match oval.',
|
|
686
|
-
},
|
|
618
|
+
data: { message: 'Client timed out waiting for face to match oval.' },
|
|
687
619
|
}, {
|
|
688
620
|
delay: (context) => {
|
|
689
|
-
return (context.
|
|
690
|
-
?.FaceMovementAndLightChallenge?.ChallengeConfig
|
|
621
|
+
return (context.parsedSessionInformation.Challenge.ChallengeConfig
|
|
691
622
|
?.OvalFitTimeout ?? DEFAULT_FACE_FIT_TIMEOUT);
|
|
692
623
|
},
|
|
693
624
|
id: 'ovalMatchTimeout',
|
|
@@ -740,21 +671,26 @@ const livenessMachine = createMachine({
|
|
|
740
671
|
if (freshnessColorEl) {
|
|
741
672
|
freshnessColorEl.style.display = 'none';
|
|
742
673
|
}
|
|
743
|
-
let
|
|
674
|
+
let closeCode = WS_CLOSURE_CODE.DEFAULT_ERROR_CODE;
|
|
744
675
|
if (context.errorState === LivenessErrorState.TIMEOUT) {
|
|
745
|
-
|
|
676
|
+
closeCode = WS_CLOSURE_CODE.FACE_FIT_TIMEOUT;
|
|
746
677
|
}
|
|
747
678
|
else if (context.errorState === LivenessErrorState.RUNTIME_ERROR) {
|
|
748
|
-
|
|
679
|
+
closeCode = WS_CLOSURE_CODE.RUNTIME_ERROR;
|
|
749
680
|
}
|
|
750
681
|
else if (context.errorState === LivenessErrorState.FACE_DISTANCE_ERROR ||
|
|
751
682
|
context.errorState === LivenessErrorState.MULTIPLE_FACES_ERROR) {
|
|
752
|
-
|
|
683
|
+
closeCode = WS_CLOSURE_CODE.USER_ERROR_DURING_CONNECTION;
|
|
753
684
|
}
|
|
754
685
|
else if (context.errorState === undefined) {
|
|
755
|
-
|
|
686
|
+
closeCode = WS_CLOSURE_CODE.USER_CANCEL;
|
|
756
687
|
}
|
|
757
|
-
context.livenessStreamProvider?.
|
|
688
|
+
context.livenessStreamProvider?.stopRecording().then(() => {
|
|
689
|
+
context.livenessStreamProvider?.dispatchStreamEvent({
|
|
690
|
+
type: 'closeCode',
|
|
691
|
+
data: { closeCode },
|
|
692
|
+
});
|
|
693
|
+
});
|
|
758
694
|
},
|
|
759
695
|
freezeStream: (context) => {
|
|
760
696
|
const { videoMediaStream, videoEl } = context.videoAssociatedParams;
|
|
@@ -770,15 +706,13 @@ const livenessMachine = createMachine({
|
|
|
770
706
|
videoEl.pause();
|
|
771
707
|
},
|
|
772
708
|
resetContext: assign({
|
|
773
|
-
challengeId:
|
|
709
|
+
challengeId: v4(),
|
|
774
710
|
maxFailedAttempts: 0,
|
|
775
711
|
failedAttempts: 0,
|
|
776
712
|
componentProps: (context) => context.componentProps,
|
|
777
|
-
|
|
713
|
+
parsedSessionInformation: (_) => undefined,
|
|
778
714
|
videoAssociatedParams: (_) => {
|
|
779
|
-
return {
|
|
780
|
-
videoConstraints: STATIC_VIDEO_CONSTRAINTS,
|
|
781
|
-
};
|
|
715
|
+
return { videoConstraints: STATIC_VIDEO_CONSTRAINTS };
|
|
782
716
|
},
|
|
783
717
|
ovalAssociatedParams: (_) => undefined,
|
|
784
718
|
errorState: (_) => undefined,
|
|
@@ -809,21 +743,30 @@ const livenessMachine = createMachine({
|
|
|
809
743
|
hasNotEnoughFaceDistanceBeforeRecording: (context) => {
|
|
810
744
|
return !context.isFaceFarEnoughBeforeRecording;
|
|
811
745
|
},
|
|
812
|
-
hasFreshnessColorShown: (context) =>
|
|
813
|
-
|
|
814
|
-
|
|
746
|
+
hasFreshnessColorShown: (context) => {
|
|
747
|
+
return context.freshnessColorAssociatedParams.freshnessColorsComplete;
|
|
748
|
+
},
|
|
749
|
+
hasParsedSessionInfo: (context) => {
|
|
750
|
+
return context.parsedSessionInformation !== undefined;
|
|
815
751
|
},
|
|
816
752
|
hasDOMAndCameraDetails: (context) => {
|
|
817
753
|
return (context.videoAssociatedParams.videoEl !== undefined &&
|
|
818
754
|
context.videoAssociatedParams.canvasEl !== undefined &&
|
|
819
755
|
context.freshnessColorAssociatedParams.freshnessColorEl !== undefined);
|
|
820
756
|
},
|
|
757
|
+
isFaceMovementChallenge: (context) => {
|
|
758
|
+
return (context.parsedSessionInformation?.Challenge?.Name ===
|
|
759
|
+
FACE_MOVEMENT_CHALLENGE.type);
|
|
760
|
+
},
|
|
761
|
+
isFaceMovementAndLightChallenge: (context) => {
|
|
762
|
+
return (context.parsedSessionInformation?.Challenge?.Name ===
|
|
763
|
+
FACE_MOVEMENT_AND_LIGHT_CHALLENGE.type);
|
|
764
|
+
},
|
|
821
765
|
getShouldDisconnect: (context) => {
|
|
822
766
|
return !!context.shouldDisconnect;
|
|
823
767
|
},
|
|
824
768
|
hasRecordingStarted: (context) => {
|
|
825
|
-
return
|
|
826
|
-
undefined);
|
|
769
|
+
return context.livenessStreamProvider.hasRecordingStarted();
|
|
827
770
|
},
|
|
828
771
|
shouldSkipStartScreen: (context) => {
|
|
829
772
|
return !!context.componentProps?.disableStartScreen;
|
|
@@ -833,12 +776,8 @@ const livenessMachine = createMachine({
|
|
|
833
776
|
async checkVirtualCameraAndGetStream(context) {
|
|
834
777
|
const { videoConstraints } = context.videoAssociatedParams;
|
|
835
778
|
// Get initial stream to enumerate devices with non-empty labels
|
|
836
|
-
const existingDeviceId = getLastSelectedCameraId();
|
|
837
779
|
const initialStream = await navigator.mediaDevices.getUserMedia({
|
|
838
|
-
video: {
|
|
839
|
-
...videoConstraints,
|
|
840
|
-
...(existingDeviceId ? { deviceId: existingDeviceId } : {}),
|
|
841
|
-
},
|
|
780
|
+
video: { ...videoConstraints },
|
|
842
781
|
audio: false,
|
|
843
782
|
});
|
|
844
783
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
@@ -867,10 +806,7 @@ const livenessMachine = createMachine({
|
|
|
867
806
|
let realVideoDeviceStream = initialStream;
|
|
868
807
|
if (!isInitialStreamFromRealDevice) {
|
|
869
808
|
realVideoDeviceStream = await navigator.mediaDevices.getUserMedia({
|
|
870
|
-
video: {
|
|
871
|
-
...videoConstraints,
|
|
872
|
-
deviceId: { exact: deviceId },
|
|
873
|
-
},
|
|
809
|
+
video: { ...videoConstraints, deviceId: { exact: deviceId } },
|
|
874
810
|
audio: false,
|
|
875
811
|
});
|
|
876
812
|
}
|
|
@@ -883,18 +819,25 @@ const livenessMachine = createMachine({
|
|
|
883
819
|
},
|
|
884
820
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
885
821
|
async openLivenessStreamConnection(context) {
|
|
886
|
-
const { config } = context.componentProps;
|
|
822
|
+
const { config, disableStartScreen } = context.componentProps;
|
|
887
823
|
const { credentialProvider, endpointOverride, systemClockOffset } = config;
|
|
888
|
-
const
|
|
889
|
-
|
|
824
|
+
const { videoHeight, videoWidth } = context.videoAssociatedParams.videoEl;
|
|
825
|
+
const livenessStreamProvider = new StreamRecorder(context.videoAssociatedParams.videoMediaStream);
|
|
826
|
+
const requestStream = createRequestStreamGenerator(livenessStreamProvider.getVideoStream()).getRequestStream();
|
|
827
|
+
const { getResponseStream } = await createStreamingClient({
|
|
828
|
+
credentialsProvider: credentialProvider,
|
|
829
|
+
endpointOverride,
|
|
890
830
|
region: context.componentProps.region,
|
|
831
|
+
attemptCount: TelemetryReporter.getAttemptCountAndUpdateTimestamp(),
|
|
832
|
+
preCheckViewEnabled: !disableStartScreen,
|
|
891
833
|
systemClockOffset,
|
|
892
|
-
stream: context.videoAssociatedParams.videoMediaStream,
|
|
893
|
-
videoEl: context.videoAssociatedParams.videoEl,
|
|
894
|
-
credentialProvider,
|
|
895
|
-
endpointOverride,
|
|
896
834
|
});
|
|
897
|
-
responseStream =
|
|
835
|
+
responseStream = getResponseStream({
|
|
836
|
+
requestStream,
|
|
837
|
+
sessionId: context.componentProps.sessionId,
|
|
838
|
+
videoHeight: videoHeight.toString(),
|
|
839
|
+
videoWidth: videoWidth.toString(),
|
|
840
|
+
});
|
|
898
841
|
return { livenessStreamProvider };
|
|
899
842
|
},
|
|
900
843
|
async detectFace(context) {
|
|
@@ -913,54 +856,34 @@ const livenessMachine = createMachine({
|
|
|
913
856
|
return { faceMatchState };
|
|
914
857
|
},
|
|
915
858
|
async detectFaceDistance(context) {
|
|
916
|
-
const { isFaceFarEnoughBeforeRecording: faceDistanceCheckBeforeRecording, } = context;
|
|
917
|
-
const { videoEl, videoMediaStream
|
|
859
|
+
const { parsedSessionInformation, isFaceFarEnoughBeforeRecording: faceDistanceCheckBeforeRecording, } = context;
|
|
860
|
+
const { videoEl, videoMediaStream } = context.videoAssociatedParams;
|
|
918
861
|
const { faceDetector } = context.ovalAssociatedParams;
|
|
919
862
|
const { width, height } = videoMediaStream
|
|
920
863
|
.getTracks()[0]
|
|
921
864
|
.getSettings();
|
|
865
|
+
const challengeConfig = parsedSessionInformation.Challenge.ChallengeConfig;
|
|
922
866
|
const ovalDetails = getStaticLivenessOvalDetails({
|
|
923
867
|
width: width,
|
|
924
868
|
height: height,
|
|
869
|
+
ovalHeightWidthRatio: challengeConfig.OvalHeightWidthRatio,
|
|
925
870
|
});
|
|
926
871
|
const { isDistanceBelowThreshold: isFaceFarEnoughBeforeRecording } = await isFaceDistanceBelowThreshold({
|
|
872
|
+
parsedSessionInformation: parsedSessionInformation,
|
|
927
873
|
faceDetector: faceDetector,
|
|
928
874
|
videoEl: videoEl,
|
|
929
875
|
ovalDetails,
|
|
930
|
-
reduceThreshold: faceDistanceCheckBeforeRecording,
|
|
931
|
-
isMobile,
|
|
876
|
+
reduceThreshold: faceDistanceCheckBeforeRecording, // if this is the second face distance check reduce the threshold
|
|
932
877
|
});
|
|
933
878
|
return { isFaceFarEnoughBeforeRecording };
|
|
934
879
|
},
|
|
935
|
-
async detectFaceDistanceWhileLoading(context) {
|
|
936
|
-
const { isFaceFarEnoughBeforeRecording: faceDistanceCheckBeforeRecording, } = context;
|
|
937
|
-
const { videoEl, videoMediaStream, isMobile } = context.videoAssociatedParams;
|
|
938
|
-
const { faceDetector } = context.ovalAssociatedParams;
|
|
939
|
-
const { width, height } = videoMediaStream
|
|
940
|
-
.getTracks()[0]
|
|
941
|
-
.getSettings();
|
|
942
|
-
const ovalDetails = getStaticLivenessOvalDetails({
|
|
943
|
-
width: width,
|
|
944
|
-
height: height,
|
|
945
|
-
});
|
|
946
|
-
const { isDistanceBelowThreshold: isFaceFarEnoughBeforeRecording, error, } = await isFaceDistanceBelowThreshold({
|
|
947
|
-
faceDetector: faceDetector,
|
|
948
|
-
isMobile,
|
|
949
|
-
ovalDetails,
|
|
950
|
-
videoEl: videoEl,
|
|
951
|
-
// if this is the second face distance check reduce the threshold
|
|
952
|
-
reduceThreshold: faceDistanceCheckBeforeRecording,
|
|
953
|
-
});
|
|
954
|
-
return { isFaceFarEnoughBeforeRecording, error };
|
|
955
|
-
},
|
|
956
880
|
async detectInitialFaceAndDrawOval(context) {
|
|
957
|
-
const {
|
|
881
|
+
const { parsedSessionInformation } = context;
|
|
958
882
|
const { videoEl, canvasEl, isMobile } = context.videoAssociatedParams;
|
|
959
883
|
const { faceDetector } = context.ovalAssociatedParams;
|
|
960
884
|
// initialize models
|
|
961
885
|
try {
|
|
962
886
|
await faceDetector.modelLoadingPromise;
|
|
963
|
-
await livenessStreamProvider.videoRecorder.recorderStarted;
|
|
964
887
|
}
|
|
965
888
|
catch (err) {
|
|
966
889
|
// eslint-disable-next-line no-console
|
|
@@ -1009,11 +932,17 @@ const livenessMachine = createMachine({
|
|
|
1009
932
|
const scaleFactor = videoScaledWidth / videoEl.videoWidth;
|
|
1010
933
|
// generate oval details from initialFace and video dimensions
|
|
1011
934
|
const ovalDetails = getOvalDetailsFromSessionInformation({
|
|
1012
|
-
|
|
935
|
+
parsedSessionInformation: parsedSessionInformation,
|
|
1013
936
|
videoWidth: videoEl.width,
|
|
1014
937
|
});
|
|
938
|
+
const challengeConfig = parsedSessionInformation.Challenge.ChallengeConfig;
|
|
1015
939
|
// renormalize initial face
|
|
1016
|
-
const renormalizedFace = generateBboxFromLandmarks(
|
|
940
|
+
const renormalizedFace = generateBboxFromLandmarks({
|
|
941
|
+
ovalHeightWidthRatio: challengeConfig.OvalHeightWidthRatio,
|
|
942
|
+
face: initialFace,
|
|
943
|
+
oval: ovalDetails,
|
|
944
|
+
frameHeight: videoEl.videoHeight,
|
|
945
|
+
});
|
|
1017
946
|
initialFace.top = renormalizedFace.top;
|
|
1018
947
|
initialFace.left = renormalizedFace.left;
|
|
1019
948
|
initialFace.height = renormalizedFace.bottom - renormalizedFace.top;
|
|
@@ -1025,15 +954,10 @@ const livenessMachine = createMachine({
|
|
|
1025
954
|
scaleFactor,
|
|
1026
955
|
videoEl: videoEl,
|
|
1027
956
|
});
|
|
1028
|
-
return {
|
|
1029
|
-
faceMatchState,
|
|
1030
|
-
ovalDetails,
|
|
1031
|
-
scaleFactor,
|
|
1032
|
-
initialFace,
|
|
1033
|
-
};
|
|
957
|
+
return { faceMatchState, ovalDetails, scaleFactor, initialFace };
|
|
1034
958
|
},
|
|
1035
959
|
async detectFaceAndMatchOval(context) {
|
|
1036
|
-
const {
|
|
960
|
+
const { parsedSessionInformation } = context;
|
|
1037
961
|
const { videoEl } = context.videoAssociatedParams;
|
|
1038
962
|
const { faceDetector, ovalDetails, initialFace } = context.ovalAssociatedParams;
|
|
1039
963
|
// detect face
|
|
@@ -1042,7 +966,13 @@ const livenessMachine = createMachine({
|
|
|
1042
966
|
let faceMatchPercentage = 0;
|
|
1043
967
|
let detectedFace;
|
|
1044
968
|
let illuminationState;
|
|
1045
|
-
const
|
|
969
|
+
const challengeConfig = parsedSessionInformation.Challenge.ChallengeConfig;
|
|
970
|
+
const initialFaceBoundingBox = generateBboxFromLandmarks({
|
|
971
|
+
ovalHeightWidthRatio: challengeConfig.OvalHeightWidthRatio,
|
|
972
|
+
face: initialFace,
|
|
973
|
+
oval: ovalDetails,
|
|
974
|
+
frameHeight: videoEl.videoHeight,
|
|
975
|
+
});
|
|
1046
976
|
const { ovalBoundingBox } = getOvalBoundingBox(ovalDetails);
|
|
1047
977
|
const initialFaceIntersection = getIntersectionOverUnion(initialFaceBoundingBox, ovalBoundingBox);
|
|
1048
978
|
switch (detectedFaces.length) {
|
|
@@ -1059,7 +989,7 @@ const livenessMachine = createMachine({
|
|
|
1059
989
|
face: detectedFace,
|
|
1060
990
|
ovalDetails: ovalDetails,
|
|
1061
991
|
initialFaceIntersection,
|
|
1062
|
-
|
|
992
|
+
parsedSessionInformation: parsedSessionInformation,
|
|
1063
993
|
frameHeight: videoEl.videoHeight,
|
|
1064
994
|
});
|
|
1065
995
|
faceMatchState = faceMatchStateInLivenessOval;
|
|
@@ -1079,60 +1009,63 @@ const livenessMachine = createMachine({
|
|
|
1079
1009
|
detectedFace,
|
|
1080
1010
|
};
|
|
1081
1011
|
},
|
|
1082
|
-
async flashColors(
|
|
1083
|
-
const { freshnessColorsComplete,
|
|
1012
|
+
async flashColors({ challengeId, colorSequenceDisplay, freshnessColorAssociatedParams, livenessStreamProvider, ovalAssociatedParams, videoAssociatedParams, }) {
|
|
1013
|
+
const { freshnessColorsComplete, freshnessColorEl } = freshnessColorAssociatedParams;
|
|
1084
1014
|
if (freshnessColorsComplete) {
|
|
1085
1015
|
return;
|
|
1086
1016
|
}
|
|
1087
|
-
const
|
|
1017
|
+
const { ovalDetails, scaleFactor } = ovalAssociatedParams;
|
|
1018
|
+
const { videoEl } = videoAssociatedParams;
|
|
1019
|
+
const completed = await colorSequenceDisplay.startSequences({
|
|
1020
|
+
onSequenceColorChange: ({ sequenceColor, prevSequenceColor, heightFraction, }) => {
|
|
1021
|
+
fillOverlayCanvasFractional({
|
|
1022
|
+
heightFraction,
|
|
1023
|
+
overlayCanvas: freshnessColorEl,
|
|
1024
|
+
ovalDetails: ovalDetails,
|
|
1025
|
+
nextColor: sequenceColor,
|
|
1026
|
+
prevColor: prevSequenceColor,
|
|
1027
|
+
scaleFactor: scaleFactor,
|
|
1028
|
+
videoEl: videoEl,
|
|
1029
|
+
});
|
|
1030
|
+
},
|
|
1031
|
+
onSequenceStart: () => {
|
|
1032
|
+
freshnessColorEl.style.display = 'block';
|
|
1033
|
+
},
|
|
1034
|
+
onSequencesComplete: () => {
|
|
1035
|
+
freshnessColorEl.style.display = 'none';
|
|
1036
|
+
},
|
|
1037
|
+
onSequenceChange: (params) => {
|
|
1038
|
+
livenessStreamProvider.dispatchStreamEvent({
|
|
1039
|
+
type: 'sessionInfo',
|
|
1040
|
+
data: createColorDisplayEvent({
|
|
1041
|
+
...params,
|
|
1042
|
+
challengeId: challengeId,
|
|
1043
|
+
}),
|
|
1044
|
+
});
|
|
1045
|
+
},
|
|
1046
|
+
});
|
|
1088
1047
|
return { freshnessColorsComplete: completed };
|
|
1089
1048
|
},
|
|
1090
1049
|
async stopVideo(context) {
|
|
1091
|
-
const { challengeId, livenessStreamProvider } = context;
|
|
1092
|
-
const { videoMediaStream } =
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
.getTracks()[0]
|
|
1097
|
-
.getSettings();
|
|
1098
|
-
const flippedInitialFaceLeft = width - initialFace.left - initialFace.width;
|
|
1099
|
-
await livenessStreamProvider.stopVideo();
|
|
1100
|
-
const livenessActionDocument = {
|
|
1101
|
-
Challenge: {
|
|
1102
|
-
FaceMovementAndLightChallenge: {
|
|
1103
|
-
ChallengeId: challengeId,
|
|
1104
|
-
InitialFace: {
|
|
1105
|
-
InitialFaceDetectedTimestamp: initialFace.timestampMs,
|
|
1106
|
-
BoundingBox: getBoundingBox({
|
|
1107
|
-
deviceHeight: height,
|
|
1108
|
-
deviceWidth: width,
|
|
1109
|
-
height: initialFace.height,
|
|
1110
|
-
width: initialFace.width,
|
|
1111
|
-
top: initialFace.top,
|
|
1112
|
-
left: flippedInitialFaceLeft,
|
|
1113
|
-
}),
|
|
1114
|
-
},
|
|
1115
|
-
TargetFace: {
|
|
1116
|
-
FaceDetectedInTargetPositionStartTimestamp: startFace.timestampMs,
|
|
1117
|
-
FaceDetectedInTargetPositionEndTimestamp: endFace.timestampMs,
|
|
1118
|
-
BoundingBox: getBoundingBox({
|
|
1119
|
-
deviceHeight: height,
|
|
1120
|
-
deviceWidth: width,
|
|
1121
|
-
height: ovalDetails.height,
|
|
1122
|
-
width: ovalDetails.width,
|
|
1123
|
-
top: ovalDetails.centerY - ovalDetails.height / 2,
|
|
1124
|
-
left: ovalDetails.centerX - ovalDetails.width / 2,
|
|
1125
|
-
}),
|
|
1126
|
-
},
|
|
1127
|
-
VideoEndTimestamp: livenessStreamProvider.videoRecorder.recorderEndTimestamp,
|
|
1128
|
-
},
|
|
1129
|
-
},
|
|
1130
|
-
};
|
|
1131
|
-
if (livenessStreamProvider.videoRecorder.getVideoChunkSize() === 0) {
|
|
1050
|
+
const { challengeId, parsedSessionInformation, faceMatchAssociatedParams, ovalAssociatedParams, livenessStreamProvider, videoAssociatedParams, } = context;
|
|
1051
|
+
const { videoMediaStream } = videoAssociatedParams;
|
|
1052
|
+
// if not awaited, `getRecordingEndTimestamp` will throw
|
|
1053
|
+
await livenessStreamProvider.stopRecording();
|
|
1054
|
+
if (livenessStreamProvider.getChunksLength() === 0) {
|
|
1132
1055
|
throw new Error('Video chunks not recorded successfully.');
|
|
1133
1056
|
}
|
|
1134
|
-
livenessStreamProvider.
|
|
1135
|
-
|
|
1057
|
+
livenessStreamProvider.dispatchStreamEvent({
|
|
1058
|
+
type: 'sessionInfo',
|
|
1059
|
+
data: createSessionEndEvent({
|
|
1060
|
+
...getTrackDimensions(videoMediaStream),
|
|
1061
|
+
parsedSessionInformation: parsedSessionInformation,
|
|
1062
|
+
challengeId: challengeId,
|
|
1063
|
+
faceMatchAssociatedParams: faceMatchAssociatedParams,
|
|
1064
|
+
ovalAssociatedParams: ovalAssociatedParams,
|
|
1065
|
+
recordingEndedTimestamp: livenessStreamProvider.getRecordingEndedTimestamp(),
|
|
1066
|
+
}),
|
|
1067
|
+
});
|
|
1068
|
+
livenessStreamProvider.dispatchStreamEvent({ type: 'streamStop' });
|
|
1136
1069
|
},
|
|
1137
1070
|
async getLiveness(context) {
|
|
1138
1071
|
const { onAnalysisComplete } = context.componentProps;
|