@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.
Files changed (61) hide show
  1. package/dist/esm/components/FaceLivenessDetector/FaceLivenessDetectorCore.mjs +4 -2
  2. package/dist/esm/components/FaceLivenessDetector/LivenessCheck/CameraSelector.mjs +13 -0
  3. package/dist/esm/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.mjs +50 -28
  4. package/dist/esm/components/FaceLivenessDetector/LivenessCheck/LivenessCheck.mjs +5 -4
  5. package/dist/esm/components/FaceLivenessDetector/service/machine/machine.mjs +247 -314
  6. package/dist/esm/components/FaceLivenessDetector/service/utils/ColorSequenceDisplay/ColorSequenceDisplay.mjs +140 -0
  7. package/dist/esm/components/FaceLivenessDetector/service/utils/StreamRecorder/StreamRecorder.mjs +171 -0
  8. package/dist/esm/components/FaceLivenessDetector/service/utils/TelemetryReporter/TelemetryReporter.mjs +27 -0
  9. package/dist/esm/components/FaceLivenessDetector/service/utils/constants.mjs +30 -7
  10. package/dist/esm/components/FaceLivenessDetector/service/utils/createRequestStreamGenerator/createRequestStreamGenerator.mjs +32 -0
  11. package/dist/esm/components/FaceLivenessDetector/service/utils/createRequestStreamGenerator/utils.mjs +148 -0
  12. package/dist/esm/components/FaceLivenessDetector/service/utils/createStreamingClient/Signer.mjs +2 -3
  13. package/dist/esm/components/FaceLivenessDetector/service/utils/createStreamingClient/createStreamingClient.mjs +36 -6
  14. package/dist/esm/components/FaceLivenessDetector/service/utils/createStreamingClient/resolveCredentials.mjs +7 -6
  15. package/dist/esm/components/FaceLivenessDetector/service/utils/getFaceMatchStateInLivenessOval.mjs +9 -5
  16. package/dist/esm/components/FaceLivenessDetector/service/utils/liveness.mjs +19 -34
  17. package/dist/esm/components/FaceLivenessDetector/service/utils/{eventUtils.mjs → responseStreamEvent.mjs} +2 -2
  18. package/dist/esm/components/FaceLivenessDetector/service/utils/sessionInformation.mjs +45 -0
  19. package/dist/esm/components/FaceLivenessDetector/shared/DefaultStartScreenComponents.mjs +3 -2
  20. package/dist/esm/components/FaceLivenessDetector/shared/FaceLivenessErrorModal.mjs +4 -2
  21. package/dist/esm/components/FaceLivenessDetector/shared/Hint.mjs +4 -7
  22. package/dist/esm/components/FaceLivenessDetector/types/classNames.mjs +3 -0
  23. package/dist/esm/components/FaceLivenessDetector/utils/device.mjs +12 -12
  24. package/dist/esm/index.mjs +12 -0
  25. package/dist/esm/version.mjs +1 -1
  26. package/dist/index.js +956 -775
  27. package/dist/styles.css +17 -2
  28. package/dist/types/components/FaceLivenessDetector/LivenessCheck/CameraSelector.d.ts +8 -0
  29. package/dist/types/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.d.ts +1 -0
  30. package/dist/types/components/FaceLivenessDetector/index.d.ts +1 -0
  31. package/dist/types/components/FaceLivenessDetector/service/types/machine.d.ts +37 -24
  32. package/dist/types/components/FaceLivenessDetector/service/utils/ColorSequenceDisplay/ColorSequenceDisplay.d.ts +55 -0
  33. package/dist/types/components/FaceLivenessDetector/service/utils/ColorSequenceDisplay/index.d.ts +2 -0
  34. package/dist/types/components/FaceLivenessDetector/service/utils/StreamRecorder/StreamRecorder.d.ts +15 -0
  35. package/dist/types/components/FaceLivenessDetector/service/utils/StreamRecorder/index.d.ts +1 -0
  36. package/dist/types/components/FaceLivenessDetector/service/utils/TelemetryReporter/TelemetryReporter.d.ts +8 -0
  37. package/dist/types/components/FaceLivenessDetector/service/utils/TelemetryReporter/index.d.ts +2 -0
  38. package/dist/types/components/FaceLivenessDetector/service/utils/constants.d.ts +27 -3
  39. package/dist/types/components/FaceLivenessDetector/service/utils/createRequestStreamGenerator/createRequestStreamGenerator.d.ts +15 -0
  40. package/dist/types/components/FaceLivenessDetector/service/utils/createRequestStreamGenerator/index.d.ts +2 -0
  41. package/dist/types/components/FaceLivenessDetector/service/utils/createRequestStreamGenerator/utils.d.ts +30 -0
  42. package/dist/types/components/FaceLivenessDetector/service/utils/createStreamingClient/Signer.d.ts +0 -1
  43. package/dist/types/components/FaceLivenessDetector/service/utils/createStreamingClient/createStreamingClient.d.ts +27 -5
  44. package/dist/types/components/FaceLivenessDetector/service/utils/createStreamingClient/index.d.ts +1 -0
  45. package/dist/types/components/FaceLivenessDetector/service/utils/getFaceMatchStateInLivenessOval.d.ts +3 -4
  46. package/dist/types/components/FaceLivenessDetector/service/utils/index.d.ts +7 -4
  47. package/dist/types/components/FaceLivenessDetector/service/utils/liveness.d.ts +15 -26
  48. package/dist/types/components/FaceLivenessDetector/service/utils/{eventUtils.d.ts → responseStreamEvent.d.ts} +1 -1
  49. package/dist/types/components/FaceLivenessDetector/service/utils/sessionInformation.d.ts +7 -0
  50. package/dist/types/components/FaceLivenessDetector/service/utils/types.d.ts +21 -0
  51. package/dist/types/components/FaceLivenessDetector/types/classNames.d.ts +3 -0
  52. package/dist/types/components/FaceLivenessDetector/utils/device.d.ts +1 -0
  53. package/dist/types/version.d.ts +1 -1
  54. package/package.json +8 -8
  55. package/dist/esm/components/FaceLivenessDetector/service/utils/freshnessColorDisplay.mjs +0 -131
  56. package/dist/esm/components/FaceLivenessDetector/service/utils/streamProvider.mjs +0 -126
  57. package/dist/esm/components/FaceLivenessDetector/service/utils/videoRecorder.mjs +0 -108
  58. package/dist/types/components/FaceLivenessDetector/service/types/service.d.ts +0 -5
  59. package/dist/types/components/FaceLivenessDetector/service/utils/freshnessColorDisplay.d.ts +0 -21
  60. package/dist/types/components/FaceLivenessDetector/service/utils/streamProvider.d.ts +0 -42
  61. package/dist/types/components/FaceLivenessDetector/service/utils/videoRecorder.d.ts +0 -27
@@ -1,15 +1,20 @@
1
- import { nanoid } from 'nanoid';
1
+ import { v4 } from 'uuid';
2
2
  import { createMachine, assign, actions, spawn } from 'xstate';
3
- import { getBoundingBox, generateBboxFromLandmarks, getOvalBoundingBox, getIntersectionOverUnion, estimateIllumination, getOvalDetailsFromSessionInformation, drawLivenessOvalInCanvas, isFaceDistanceBelowThreshold, getFaceMatchState, isCameraDeviceVirtual, getColorsSequencesFromSessionInformation, drawStaticOval, getStaticLivenessOvalDetails } from '../utils/liveness.mjs';
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 { LivenessStreamProvider } from '../utils/streamProvider.mjs';
9
- import { FreshnessColorDisplay } from '../utils/freshnessColorDisplay.mjs';
10
- import { isServerSesssionInformationEvent, isDisconnectionEvent, isValidationExceptionEvent, isInternalServerExceptionEvent, isThrottlingExceptionEvent, isServiceQuotaExceededExceptionEvent, isInvalidSignatureRegionException, isConnectionTimeoutError } from '../utils/eventUtils.mjs';
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 { WS_CLOSURE_CODE } from '../utils/constants.mjs';
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 (isServerSesssionInformationEvent(event)) {
26
+ if (isServerSessionInformationEvent(event)) {
22
27
  callback({
23
28
  type: 'SET_SESSION_INFO',
24
29
  data: {
25
- sessionInfo: event.ServerSessionInformationEvent.SessionInformation,
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: 'cameraCheck',
87
+ initial: 'initCamera',
86
88
  predictableActionArguments: true,
87
89
  context: {
88
- challengeId: nanoid(),
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
- internal: true,
134
- actions: 'updateSessionInfo',
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
- cameraCheck: {
165
- entry: 'resetErrorState',
166
- invoke: {
167
- src: 'checkVirtualCameraAndGetStream',
168
- onDone: {
169
- target: 'waitForDOMAndCameraDetails',
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
- onError: {
173
- target: 'permissionDenied',
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
- waitForDOMAndCameraDetails: {
178
- after: {
179
- 0: {
180
- target: 'start',
181
- cond: 'hasDOMAndCameraDetails',
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: ['drawStaticOval', 'initializeFaceDetector'],
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: 'initializeLivenessStream',
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: 'updateRecordingStartTimestampMs',
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: 'delayBeforeFlash',
316
+ target: 'handleChallenge',
333
317
  cond: 'hasFaceMatchedInOval',
334
318
  actions: [
335
319
  'setFaceMatchTimeAndStartFace',
336
320
  'updateEndFaceMatch',
337
- 'setupFlashFreshnessColors',
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
- after: {
349
- 1000: 'flashFreshnessColors',
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
- target: 'timeout',
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: 'cameraCheck' },
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: 'cameraCheck' },
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
- drawStaticOval: (context) => {
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, videoAssociatedParams, ovalAssociatedParams, livenessStreamProvider, } = context;
508
- const { recordingStartApiTimestamp, recorderStartTimestamp } = livenessStreamProvider.videoRecorder;
484
+ const { challengeId, ovalAssociatedParams, videoAssociatedParams, livenessStreamProvider, parsedSessionInformation, } = context;
509
485
  const { videoMediaStream } = videoAssociatedParams;
510
- const { initialFace } = ovalAssociatedParams;
511
- /**
512
- * This calculation is provided by Science team after doing analysis
513
- * of unreliable .onstart() (recorderStartTimestamp) timestamp that is
514
- * returned from mediaRecorder.
515
- */
516
- const timestamp = Math.round(0.73 * (recorderStartTimestamp - recordingStartApiTimestamp) +
517
- recordingStartApiTimestamp);
518
- // Send client info for initial face position
519
- const { width, height } = videoMediaStream
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
- recordingStartTimestampMs: timestamp,
499
+ recordingStartedTimestamp,
545
500
  };
546
501
  },
547
502
  }),
548
503
  startRecording: assign({
549
504
  videoAssociatedParams: (context) => {
550
- if (!context.serverSessionInformation) {
551
- throw new Error('Session information was not received from response stream');
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
- serverSessionInformation: (_, event) => {
637
- return event.data.sessionInfo;
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
- setupFlashFreshnessColors: assign({
651
- freshnessColorAssociatedParams: (context) => {
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
- message: 'Client failed to draw oval.',
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
- message: 'Client failed to start recording.',
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.serverSessionInformation?.Challenge
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 closureCode = WS_CLOSURE_CODE.DEFAULT_ERROR_CODE;
674
+ let closeCode = WS_CLOSURE_CODE.DEFAULT_ERROR_CODE;
744
675
  if (context.errorState === LivenessErrorState.TIMEOUT) {
745
- closureCode = WS_CLOSURE_CODE.FACE_FIT_TIMEOUT;
676
+ closeCode = WS_CLOSURE_CODE.FACE_FIT_TIMEOUT;
746
677
  }
747
678
  else if (context.errorState === LivenessErrorState.RUNTIME_ERROR) {
748
- closureCode = WS_CLOSURE_CODE.RUNTIME_ERROR;
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
- closureCode = WS_CLOSURE_CODE.USER_ERROR_DURING_CONNECTION;
683
+ closeCode = WS_CLOSURE_CODE.USER_ERROR_DURING_CONNECTION;
753
684
  }
754
685
  else if (context.errorState === undefined) {
755
- closureCode = WS_CLOSURE_CODE.USER_CANCEL;
686
+ closeCode = WS_CLOSURE_CODE.USER_CANCEL;
756
687
  }
757
- context.livenessStreamProvider?.endStreamWithCode(closureCode);
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: nanoid(),
709
+ challengeId: v4(),
774
710
  maxFailedAttempts: 0,
775
711
  failedAttempts: 0,
776
712
  componentProps: (context) => context.componentProps,
777
- serverSessionInformation: (_) => undefined,
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) => context.freshnessColorAssociatedParams.freshnessColorsComplete,
813
- hasServerSessionInfo: (context) => {
814
- return context.serverSessionInformation !== undefined;
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 (context.livenessStreamProvider.videoRecorder.firstChunkTimestamp !==
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 livenessStreamProvider = new LivenessStreamProvider({
889
- sessionId: context.componentProps.sessionId,
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 = livenessStreamProvider.getResponseStream();
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, isMobile } = context.videoAssociatedParams;
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 { serverSessionInformation, livenessStreamProvider } = context;
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
- sessionInformation: serverSessionInformation,
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(initialFace, ovalDetails, videoEl.videoHeight);
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 { serverSessionInformation } = context;
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 initialFaceBoundingBox = generateBboxFromLandmarks(initialFace, ovalDetails, videoEl.videoHeight);
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
- sessionInformation: serverSessionInformation,
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(context) {
1083
- const { freshnessColorsComplete, freshnessColorDisplay } = context.freshnessColorAssociatedParams;
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 completed = await freshnessColorDisplay.displayColorTick();
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 } = context.videoAssociatedParams;
1093
- const { initialFace, ovalDetails } = context.ovalAssociatedParams;
1094
- const { startFace, endFace } = context.faceMatchAssociatedParams;
1095
- const { width, height } = videoMediaStream
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.sendClientInfo(livenessActionDocument);
1135
- livenessStreamProvider.dispatchStopVideoEvent();
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;