@aws-amplify/ui-react-liveness 3.6.6 → 3.6.7
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 +1 -1
- package/dist/esm/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.mjs +1 -1
- package/dist/esm/components/FaceLivenessDetector/LivenessCheck/LivenessCheck.mjs +1 -1
- package/dist/esm/components/FaceLivenessDetector/service/machine/machine.mjs +15 -15
- package/dist/esm/components/FaceLivenessDetector/service/types/faceDetection.mjs +27 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/createStreamingClient/CustomWebSocketFetchHandler.mjs +68 -20
- package/dist/esm/components/FaceLivenessDetector/service/utils/getFaceMatchStateInLivenessOval.mjs +1 -1
- package/dist/esm/components/FaceLivenessDetector/service/utils/liveness.mjs +1 -1
- package/dist/esm/components/FaceLivenessDetector/shared/DefaultStartScreenComponents.mjs +1 -1
- package/dist/esm/components/FaceLivenessDetector/shared/FaceLivenessErrorModal.mjs +1 -1
- package/dist/esm/components/FaceLivenessDetector/shared/Hint.mjs +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +156 -84
- package/dist/types/components/FaceLivenessDetector/service/types/faceDetection.d.ts +3 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useInterpret } from '@xstate/react';
|
|
3
3
|
import { livenessMachine } from './service/machine/machine.mjs';
|
|
4
|
+
import './service/utils/constants.mjs';
|
|
4
5
|
import './service/types/liveness.mjs';
|
|
5
6
|
import '@tensorflow/tfjs-core';
|
|
6
7
|
import '../../packages/react-liveness/node_modules/@tensorflow-models/face-detection/dist/face-detection.esm.mjs';
|
|
7
8
|
import '@tensorflow/tfjs-backend-wasm';
|
|
8
9
|
import '@tensorflow/tfjs-backend-cpu';
|
|
9
10
|
import '@aws-amplify/core/internals/utils';
|
|
10
|
-
import './service/utils/constants.mjs';
|
|
11
11
|
import './service/utils/ColorSequenceDisplay/ColorSequenceDisplay.mjs';
|
|
12
12
|
import '@aws-amplify/ui';
|
|
13
13
|
import './service/utils/createStreamingClient/createStreamingClient.mjs';
|
|
@@ -3,6 +3,7 @@ import { classNames } from '@aws-amplify/ui';
|
|
|
3
3
|
import { View, Flex, Loader, Text, Button } from '@aws-amplify/ui-react';
|
|
4
4
|
import { useColorMode } from '@aws-amplify/ui-react/internal';
|
|
5
5
|
import '../service/machine/machine.mjs';
|
|
6
|
+
import { FACE_MOVEMENT_CHALLENGE } from '../service/utils/constants.mjs';
|
|
6
7
|
import { FaceMatchState } from '../service/types/liveness.mjs';
|
|
7
8
|
import '@tensorflow/tfjs-core';
|
|
8
9
|
import '../../../packages/react-liveness/node_modules/@tensorflow-models/face-detection/dist/face-detection.esm.mjs';
|
|
@@ -10,7 +11,6 @@ import '@tensorflow/tfjs-backend-wasm';
|
|
|
10
11
|
import '@tensorflow/tfjs-backend-cpu';
|
|
11
12
|
import '@aws-amplify/core/internals/utils';
|
|
12
13
|
import { drawStaticOval, clearOvalCanvas } from '../service/utils/liveness.mjs';
|
|
13
|
-
import { FACE_MOVEMENT_CHALLENGE } from '../service/utils/constants.mjs';
|
|
14
14
|
import '../service/utils/ColorSequenceDisplay/ColorSequenceDisplay.mjs';
|
|
15
15
|
import '../service/utils/createStreamingClient/createStreamingClient.mjs';
|
|
16
16
|
import '../service/utils/StreamRecorder/StreamRecorder.mjs';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { Flex, Text, Button, View } from '@aws-amplify/ui-react';
|
|
3
3
|
import '../service/machine/machine.mjs';
|
|
4
|
+
import '../service/utils/constants.mjs';
|
|
4
5
|
import '../service/types/liveness.mjs';
|
|
5
6
|
import { LivenessErrorState } from '../service/types/error.mjs';
|
|
6
7
|
import '@tensorflow/tfjs-core';
|
|
@@ -8,7 +9,6 @@ import '../../../packages/react-liveness/node_modules/@tensorflow-models/face-de
|
|
|
8
9
|
import '@tensorflow/tfjs-backend-wasm';
|
|
9
10
|
import '@tensorflow/tfjs-backend-cpu';
|
|
10
11
|
import '@aws-amplify/core/internals/utils';
|
|
11
|
-
import '../service/utils/constants.mjs';
|
|
12
12
|
import '../service/utils/ColorSequenceDisplay/ColorSequenceDisplay.mjs';
|
|
13
13
|
import '@aws-amplify/ui';
|
|
14
14
|
import '../service/utils/createStreamingClient/createStreamingClient.mjs';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { v4 } from 'uuid';
|
|
2
2
|
import { createMachine, assign, actions, spawn } from 'xstate';
|
|
3
3
|
import { fillOverlayCanvasFractional, generateBboxFromLandmarks, getOvalBoundingBox, getIntersectionOverUnion, estimateIllumination, getOvalDetailsFromSessionInformation, drawLivenessOvalInCanvas, getStaticLivenessOvalDetails, isFaceDistanceBelowThreshold, getFaceMatchState, isCameraDeviceVirtual, getColorsSequencesFromSessionInformation } from '../utils/liveness.mjs';
|
|
4
|
+
import { FACE_MOVEMENT_AND_LIGHT_CHALLENGE, FACE_MOVEMENT_CHALLENGE, WS_CLOSURE_CODE } from '../utils/constants.mjs';
|
|
4
5
|
import { FaceMatchState } from '../types/liveness.mjs';
|
|
5
6
|
import { LivenessErrorState } from '../types/error.mjs';
|
|
6
7
|
import { BlazeFaceFaceDetection } from '../utils/blazefaceFaceDetection.mjs';
|
|
7
8
|
import { getFaceMatchStateInLivenessOval } from '../utils/getFaceMatchStateInLivenessOval.mjs';
|
|
8
9
|
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
10
|
import { createRequestStreamGenerator } from '../utils/createRequestStreamGenerator/createRequestStreamGenerator.mjs';
|
|
11
11
|
import { createSessionEndEvent, getTrackDimensions, createColorDisplayEvent, createSessionStartEvent } from '../utils/createRequestStreamGenerator/utils.mjs';
|
|
12
12
|
import { createStreamingClient } from '../utils/createStreamingClient/createStreamingClient.mjs';
|
|
@@ -203,6 +203,10 @@ const livenessMachine = createMachine({
|
|
|
203
203
|
'spawnResponseStreamActor',
|
|
204
204
|
],
|
|
205
205
|
},
|
|
206
|
+
onError: {
|
|
207
|
+
target: '#livenessMachine.error',
|
|
208
|
+
actions: 'updateErrorStateForRuntime',
|
|
209
|
+
},
|
|
206
210
|
},
|
|
207
211
|
},
|
|
208
212
|
waitForSessionInfo: {
|
|
@@ -231,6 +235,10 @@ const livenessMachine = createMachine({
|
|
|
231
235
|
target: 'checkFaceDetectedBeforeStart',
|
|
232
236
|
actions: 'updateFaceMatchBeforeStartDetails',
|
|
233
237
|
},
|
|
238
|
+
onError: {
|
|
239
|
+
target: 'error',
|
|
240
|
+
actions: 'updateErrorStateForRuntime',
|
|
241
|
+
},
|
|
234
242
|
},
|
|
235
243
|
},
|
|
236
244
|
checkFaceDetectedBeforeStart: {
|
|
@@ -249,6 +257,10 @@ const livenessMachine = createMachine({
|
|
|
249
257
|
target: 'checkFaceDistanceBeforeRecording',
|
|
250
258
|
actions: 'updateFaceDistanceBeforeRecording',
|
|
251
259
|
},
|
|
260
|
+
onError: {
|
|
261
|
+
target: 'error',
|
|
262
|
+
actions: 'updateErrorStateForRuntime',
|
|
263
|
+
},
|
|
252
264
|
},
|
|
253
265
|
},
|
|
254
266
|
checkFaceDistanceBeforeRecording: {
|
|
@@ -926,13 +938,7 @@ const livenessMachine = createMachine({
|
|
|
926
938
|
const { videoEl } = context.videoAssociatedParams;
|
|
927
939
|
const { faceDetector } = context.ovalAssociatedParams;
|
|
928
940
|
// initialize models
|
|
929
|
-
|
|
930
|
-
await faceDetector.modelLoadingPromise;
|
|
931
|
-
}
|
|
932
|
-
catch (err) {
|
|
933
|
-
// eslint-disable-next-line no-console
|
|
934
|
-
console.log({ err });
|
|
935
|
-
}
|
|
941
|
+
await faceDetector.modelLoadingPromise;
|
|
936
942
|
// detect face
|
|
937
943
|
const faceMatchState = await getFaceMatchState(faceDetector, videoEl);
|
|
938
944
|
return { faceMatchState };
|
|
@@ -964,13 +970,7 @@ const livenessMachine = createMachine({
|
|
|
964
970
|
const { videoEl, canvasEl, isMobile } = context.videoAssociatedParams;
|
|
965
971
|
const { faceDetector } = context.ovalAssociatedParams;
|
|
966
972
|
// initialize models
|
|
967
|
-
|
|
968
|
-
await faceDetector.modelLoadingPromise;
|
|
969
|
-
}
|
|
970
|
-
catch (err) {
|
|
971
|
-
// eslint-disable-next-line no-console
|
|
972
|
-
console.log({ err });
|
|
973
|
-
}
|
|
973
|
+
await faceDetector.modelLoadingPromise;
|
|
974
974
|
// detect face
|
|
975
975
|
const detectedFaces = await faceDetector.detectFaces(videoEl);
|
|
976
976
|
let initialFace;
|
|
@@ -1,14 +1,40 @@
|
|
|
1
|
+
import { CONNECTION_TIMEOUT } from '../utils/constants.mjs';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The abstract class representing FaceDetection
|
|
3
5
|
* to be implemented for different libraries.
|
|
4
6
|
*/
|
|
5
7
|
class FaceDetection {
|
|
8
|
+
constructor() {
|
|
9
|
+
this._modelLoadTriggered = false;
|
|
10
|
+
}
|
|
6
11
|
/**
|
|
7
12
|
* Triggers the `loadModels` method and stores
|
|
8
13
|
* the corresponding promise to be awaited later.
|
|
14
|
+
* Applies a timeout to prevent indefinite hangs if the
|
|
15
|
+
* network request for model assets stalls.
|
|
9
16
|
*/
|
|
10
17
|
triggerModelLoading() {
|
|
11
|
-
|
|
18
|
+
if (this._modelLoadTriggered) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this._modelLoadTriggered = true;
|
|
22
|
+
let timeoutId;
|
|
23
|
+
this.modelLoadingPromise = Promise.race([
|
|
24
|
+
// `finally` ensures the timeout is cleared whether loadModels
|
|
25
|
+
// resolves or rejects, preventing a leaked timer on fast failures.
|
|
26
|
+
this.loadModels().finally(() => {
|
|
27
|
+
clearTimeout(timeoutId);
|
|
28
|
+
}),
|
|
29
|
+
new Promise((_, reject) => {
|
|
30
|
+
timeoutId = setTimeout(() => reject(new Error('Face detection model loading timed out. Check your network connection and try again.')),
|
|
31
|
+
// Uses the same 10s timeout as the WebSocket connection phase.
|
|
32
|
+
// The WASM binary (~425KB) + model (~100KB) should load well
|
|
33
|
+
// within 10s on any reasonable connection. If it hasn't loaded
|
|
34
|
+
// by then, the request is likely stalled (not just slow).
|
|
35
|
+
CONNECTION_TIMEOUT);
|
|
36
|
+
}),
|
|
37
|
+
]);
|
|
12
38
|
}
|
|
13
39
|
}
|
|
14
40
|
|
|
@@ -142,48 +142,97 @@ class CustomWebSocketFetchHandler {
|
|
|
142
142
|
let streamError = undefined;
|
|
143
143
|
// To notify onclose event that error has occurred.
|
|
144
144
|
let socketErrorOccurred = false;
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
// Buffer for messages that arrive before the consumer calls .next().
|
|
146
|
+
// This prevents dropped messages when the server responds faster than
|
|
147
|
+
// the client sets up its async iterator (race condition).
|
|
148
|
+
const messageQueue = [];
|
|
149
|
+
let waitingResolve = null;
|
|
150
|
+
let waitingReject = null;
|
|
151
|
+
let done = false;
|
|
152
|
+
let endError = undefined;
|
|
148
153
|
socket.onmessage = (event) => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
const chunk = new Uint8Array(event.data);
|
|
155
|
+
if (waitingResolve) {
|
|
156
|
+
// Consumer is waiting for data — deliver immediately
|
|
157
|
+
const resolve = waitingResolve;
|
|
158
|
+
waitingResolve = null;
|
|
159
|
+
waitingReject = null;
|
|
160
|
+
resolve({ done: false, value: chunk });
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// Consumer hasn't called .next() yet — buffer the message
|
|
164
|
+
messageQueue.push(chunk);
|
|
165
|
+
}
|
|
153
166
|
};
|
|
154
167
|
socket.onerror = (error) => {
|
|
155
168
|
socketErrorOccurred = true;
|
|
156
169
|
socket.close();
|
|
157
|
-
|
|
170
|
+
done = true;
|
|
171
|
+
endError = error;
|
|
172
|
+
if (waitingReject) {
|
|
173
|
+
const reject = waitingReject;
|
|
174
|
+
waitingResolve = null;
|
|
175
|
+
waitingReject = null;
|
|
176
|
+
reject(error);
|
|
177
|
+
}
|
|
158
178
|
};
|
|
159
179
|
socket.onclose = (event) => {
|
|
160
180
|
this.removeNotUsableSockets(socket.url);
|
|
161
181
|
if (socketErrorOccurred)
|
|
162
182
|
return;
|
|
183
|
+
done = true;
|
|
163
184
|
if (streamError) {
|
|
164
|
-
|
|
185
|
+
endError = streamError;
|
|
165
186
|
}
|
|
166
187
|
else if (event.code !== WS_CLOSURE_CODE.SUCCESS_CODE &&
|
|
167
188
|
event.code !== 1001) {
|
|
168
189
|
// Server closed the connection with an abnormal code (e.g. 4001
|
|
169
190
|
// StreamIdleTimeout, 4003 SessionExpired, 1006 abnormal closure).
|
|
170
|
-
|
|
191
|
+
endError = new Error(`Server ended the connection unexpectedly (code ${event.code}` +
|
|
171
192
|
(event.reason ? `: ${event.reason}` : '') +
|
|
172
|
-
')')
|
|
193
|
+
')');
|
|
173
194
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
195
|
+
if (waitingResolve) {
|
|
196
|
+
if (endError) {
|
|
197
|
+
const reject = waitingReject;
|
|
198
|
+
waitingResolve = null;
|
|
199
|
+
waitingReject = null;
|
|
200
|
+
reject(endError);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
const resolve = waitingResolve;
|
|
204
|
+
waitingResolve = null;
|
|
205
|
+
waitingReject = null;
|
|
206
|
+
resolve({ done: true, value: undefined });
|
|
207
|
+
}
|
|
179
208
|
}
|
|
180
209
|
};
|
|
181
210
|
const outputStream = {
|
|
182
211
|
[Symbol.asyncIterator]: () => ({
|
|
183
212
|
next: () => {
|
|
213
|
+
// If there are buffered messages, deliver the next one immediately
|
|
214
|
+
if (messageQueue.length > 0) {
|
|
215
|
+
return Promise.resolve({
|
|
216
|
+
done: false,
|
|
217
|
+
value: messageQueue.shift(),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// If the stream has ended, resolve/reject accordingly
|
|
221
|
+
if (done) {
|
|
222
|
+
if (endError) {
|
|
223
|
+
return Promise.reject(endError instanceof Error
|
|
224
|
+
? endError
|
|
225
|
+
: new Error('Stream ended with an error'));
|
|
226
|
+
}
|
|
227
|
+
return Promise.resolve({
|
|
228
|
+
done: true,
|
|
229
|
+
value: undefined,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// Otherwise wait for the next message or close event
|
|
184
233
|
return new Promise((_resolve, _reject) => {
|
|
185
|
-
|
|
186
|
-
|
|
234
|
+
waitingResolve = _resolve;
|
|
235
|
+
waitingReject = _reject;
|
|
187
236
|
});
|
|
188
237
|
},
|
|
189
238
|
}),
|
|
@@ -206,8 +255,7 @@ class CustomWebSocketFetchHandler {
|
|
|
206
255
|
catch (err) {
|
|
207
256
|
// We don't throw the error here because the send()'s returned
|
|
208
257
|
// would already be settled by the time sending chunk throws error.
|
|
209
|
-
// Instead,
|
|
210
|
-
// exceptions
|
|
258
|
+
// Instead, notify the output stream to throw if there's exceptions
|
|
211
259
|
if (err instanceof Error) {
|
|
212
260
|
streamError = err;
|
|
213
261
|
}
|
package/dist/esm/components/FaceLivenessDetector/service/utils/getFaceMatchStateInLivenessOval.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { FACE_MATCH_RANGE_MAX, FACE_MATCH_WEIGHT_MAX, FACE_MATCH_WEIGHT_MIN, FACE_MATCH_RANGE_MIN } from './constants.mjs';
|
|
1
2
|
import { FaceMatchState } from '../types/liveness.mjs';
|
|
2
3
|
import { generateBboxFromLandmarks, getOvalBoundingBox, getIntersectionOverUnion } from './liveness.mjs';
|
|
3
|
-
import { FACE_MATCH_RANGE_MAX, FACE_MATCH_WEIGHT_MAX, FACE_MATCH_WEIGHT_MIN, FACE_MATCH_RANGE_MIN } from './constants.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Returns the state of the provided face with respect to the provided liveness oval.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { OVAL_HEIGHT_WIDTH_RATIO, PUPIL_DISTANCE_WEIGHT, FACE_HEIGHT_WEIGHT } from './constants.mjs';
|
|
1
2
|
import { IlluminationState, FaceMatchState } from '../types/liveness.mjs';
|
|
2
3
|
import { LivenessErrorState } from '../types/error.mjs';
|
|
3
|
-
import { OVAL_HEIGHT_WIDTH_RATIO, PUPIL_DISTANCE_WEIGHT, FACE_HEIGHT_WEIGHT } from './constants.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Returns the random number between min and max
|
|
@@ -3,13 +3,13 @@ import { ComponentClassName } from '@aws-amplify/ui';
|
|
|
3
3
|
import { Flex, View } from '@aws-amplify/ui-react';
|
|
4
4
|
import { CancelButton } from './CancelButton.mjs';
|
|
5
5
|
import '../service/machine/machine.mjs';
|
|
6
|
+
import '../service/utils/constants.mjs';
|
|
6
7
|
import '../service/types/liveness.mjs';
|
|
7
8
|
import '@tensorflow/tfjs-core';
|
|
8
9
|
import '../../../packages/react-liveness/node_modules/@tensorflow-models/face-detection/dist/face-detection.esm.mjs';
|
|
9
10
|
import '@tensorflow/tfjs-backend-wasm';
|
|
10
11
|
import '@tensorflow/tfjs-backend-cpu';
|
|
11
12
|
import '@aws-amplify/core/internals/utils';
|
|
12
|
-
import '../service/utils/constants.mjs';
|
|
13
13
|
import '../service/utils/ColorSequenceDisplay/ColorSequenceDisplay.mjs';
|
|
14
14
|
import '../service/utils/createStreamingClient/createStreamingClient.mjs';
|
|
15
15
|
import '../service/utils/StreamRecorder/StreamRecorder.mjs';
|
|
@@ -2,6 +2,7 @@ import React__default from 'react';
|
|
|
2
2
|
import { Flex, Button, Text } from '@aws-amplify/ui-react';
|
|
3
3
|
import { AlertIcon } from '@aws-amplify/ui-react/internal';
|
|
4
4
|
import '../service/machine/machine.mjs';
|
|
5
|
+
import '../service/utils/constants.mjs';
|
|
5
6
|
import '../service/types/liveness.mjs';
|
|
6
7
|
import { LivenessErrorState } from '../service/types/error.mjs';
|
|
7
8
|
import '@tensorflow/tfjs-core';
|
|
@@ -9,7 +10,6 @@ import '../../../packages/react-liveness/node_modules/@tensorflow-models/face-de
|
|
|
9
10
|
import '@tensorflow/tfjs-backend-wasm';
|
|
10
11
|
import '@tensorflow/tfjs-backend-cpu';
|
|
11
12
|
import '@aws-amplify/core/internals/utils';
|
|
12
|
-
import '../service/utils/constants.mjs';
|
|
13
13
|
import '../service/utils/ColorSequenceDisplay/ColorSequenceDisplay.mjs';
|
|
14
14
|
import '@aws-amplify/ui';
|
|
15
15
|
import '../service/utils/createStreamingClient/createStreamingClient.mjs';
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { VisuallyHidden, View } from '@aws-amplify/ui-react';
|
|
3
3
|
import '../service/machine/machine.mjs';
|
|
4
|
+
import '../service/utils/constants.mjs';
|
|
4
5
|
import { FaceMatchState, IlluminationState } from '../service/types/liveness.mjs';
|
|
5
6
|
import '@tensorflow/tfjs-core';
|
|
6
7
|
import '../../../packages/react-liveness/node_modules/@tensorflow-models/face-detection/dist/face-detection.esm.mjs';
|
|
7
8
|
import '@tensorflow/tfjs-backend-wasm';
|
|
8
9
|
import '@tensorflow/tfjs-backend-cpu';
|
|
9
10
|
import '@aws-amplify/core/internals/utils';
|
|
10
|
-
import '../service/utils/constants.mjs';
|
|
11
11
|
import '../service/utils/ColorSequenceDisplay/ColorSequenceDisplay.mjs';
|
|
12
12
|
import '@aws-amplify/ui';
|
|
13
13
|
import '../service/utils/createStreamingClient/createStreamingClient.mjs';
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export { default as FaceLivenessDetector } from './components/FaceLivenessDetector/FaceLivenessDetector.mjs';
|
|
2
2
|
export { default as FaceLivenessDetectorCore } from './components/FaceLivenessDetector/FaceLivenessDetectorCore.mjs';
|
|
3
3
|
import './components/FaceLivenessDetector/service/machine/machine.mjs';
|
|
4
|
+
import './components/FaceLivenessDetector/service/utils/constants.mjs';
|
|
4
5
|
import './components/FaceLivenessDetector/service/types/liveness.mjs';
|
|
5
6
|
import '@tensorflow/tfjs-core';
|
|
6
7
|
import './packages/react-liveness/node_modules/@tensorflow-models/face-detection/dist/face-detection.esm.mjs';
|
|
7
8
|
import '@tensorflow/tfjs-backend-wasm';
|
|
8
9
|
import '@tensorflow/tfjs-backend-cpu';
|
|
9
10
|
import '@aws-amplify/core/internals/utils';
|
|
10
|
-
import './components/FaceLivenessDetector/service/utils/constants.mjs';
|
|
11
11
|
import './components/FaceLivenessDetector/service/utils/ColorSequenceDisplay/ColorSequenceDisplay.mjs';
|
|
12
12
|
import '@aws-amplify/ui';
|
|
13
13
|
import './components/FaceLivenessDetector/service/utils/createStreamingClient/createStreamingClient.mjs';
|
package/dist/esm/version.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -43,17 +43,89 @@ function _interopNamespace(e) {
|
|
|
43
43
|
|
|
44
44
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
45
45
|
|
|
46
|
+
// Face distance is calculated as pupilDistance / ovalWidth.
|
|
47
|
+
// The further away you are from the camera the distance between your pupils will decrease, thus lowering the threshold values.
|
|
48
|
+
// Constants from science team to determine ocular distance (space between eyes)
|
|
49
|
+
const PUPIL_DISTANCE_WEIGHT = 2.0;
|
|
50
|
+
const FACE_HEIGHT_WEIGHT = 1.8;
|
|
51
|
+
// Constants from science team to find face match percentage
|
|
52
|
+
const FACE_MATCH_RANGE_MIN = 0;
|
|
53
|
+
const FACE_MATCH_RANGE_MAX = 1;
|
|
54
|
+
const FACE_MATCH_WEIGHT_MIN = 0.25;
|
|
55
|
+
const FACE_MATCH_WEIGHT_MAX = 0.75;
|
|
56
|
+
const OVAL_HEIGHT_WIDTH_RATIO = 1.618;
|
|
57
|
+
const WS_CLOSURE_CODE = {
|
|
58
|
+
SUCCESS_CODE: 1000,
|
|
59
|
+
DEFAULT_ERROR_CODE: 4000,
|
|
60
|
+
FACE_FIT_TIMEOUT: 4001,
|
|
61
|
+
USER_CANCEL: 4003,
|
|
62
|
+
RUNTIME_ERROR: 4005,
|
|
63
|
+
USER_ERROR_DURING_CONNECTION: 4007,
|
|
64
|
+
};
|
|
65
|
+
// number in milliseconds to record into each video chunk.
|
|
66
|
+
// see https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/start#timeslice
|
|
67
|
+
const TIME_SLICE = 1000;
|
|
68
|
+
// in MS, the rate at which colors are rendered/checked
|
|
69
|
+
const TICK_RATE = 10;
|
|
70
|
+
/**
|
|
71
|
+
* The number of seconds before the presigned URL expires.
|
|
72
|
+
* Used to override aws sdk default value of 60
|
|
73
|
+
*/
|
|
74
|
+
const REQUEST_EXPIRY = 299;
|
|
75
|
+
/**
|
|
76
|
+
* The maximum time in milliseconds that the connection phase of a request
|
|
77
|
+
* may take before the connection attempt is abandoned.
|
|
78
|
+
*/
|
|
79
|
+
const CONNECTION_TIMEOUT = 10000;
|
|
80
|
+
const FACE_MOVEMENT_AND_LIGHT_CHALLENGE = {
|
|
81
|
+
type: 'FaceMovementAndLightChallenge',
|
|
82
|
+
version: '2.0.0',
|
|
83
|
+
};
|
|
84
|
+
const FACE_MOVEMENT_CHALLENGE = {
|
|
85
|
+
type: 'FaceMovementChallenge',
|
|
86
|
+
version: '1.0.0',
|
|
87
|
+
};
|
|
88
|
+
const SUPPORTED_CHALLENGES = [
|
|
89
|
+
FACE_MOVEMENT_AND_LIGHT_CHALLENGE,
|
|
90
|
+
FACE_MOVEMENT_CHALLENGE,
|
|
91
|
+
];
|
|
92
|
+
const queryParameterString = SUPPORTED_CHALLENGES.map((challenge) => `${challenge.type}_${challenge.version}`).join(',');
|
|
93
|
+
|
|
46
94
|
/**
|
|
47
95
|
* The abstract class representing FaceDetection
|
|
48
96
|
* to be implemented for different libraries.
|
|
49
97
|
*/
|
|
50
98
|
let FaceDetection$1 = class FaceDetection {
|
|
99
|
+
constructor() {
|
|
100
|
+
this._modelLoadTriggered = false;
|
|
101
|
+
}
|
|
51
102
|
/**
|
|
52
103
|
* Triggers the `loadModels` method and stores
|
|
53
104
|
* the corresponding promise to be awaited later.
|
|
105
|
+
* Applies a timeout to prevent indefinite hangs if the
|
|
106
|
+
* network request for model assets stalls.
|
|
54
107
|
*/
|
|
55
108
|
triggerModelLoading() {
|
|
56
|
-
|
|
109
|
+
if (this._modelLoadTriggered) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this._modelLoadTriggered = true;
|
|
113
|
+
let timeoutId;
|
|
114
|
+
this.modelLoadingPromise = Promise.race([
|
|
115
|
+
// `finally` ensures the timeout is cleared whether loadModels
|
|
116
|
+
// resolves or rejects, preventing a leaked timer on fast failures.
|
|
117
|
+
this.loadModels().finally(() => {
|
|
118
|
+
clearTimeout(timeoutId);
|
|
119
|
+
}),
|
|
120
|
+
new Promise((_, reject) => {
|
|
121
|
+
timeoutId = setTimeout(() => reject(new Error('Face detection model loading timed out. Check your network connection and try again.')),
|
|
122
|
+
// Uses the same 10s timeout as the WebSocket connection phase.
|
|
123
|
+
// The WASM binary (~425KB) + model (~100KB) should load well
|
|
124
|
+
// within 10s on any reasonable connection. If it hasn't loaded
|
|
125
|
+
// by then, the request is likely stalled (not just slow).
|
|
126
|
+
CONNECTION_TIMEOUT);
|
|
127
|
+
}),
|
|
128
|
+
]);
|
|
57
129
|
}
|
|
58
130
|
};
|
|
59
131
|
|
|
@@ -95,54 +167,6 @@ const LivenessErrorState = {
|
|
|
95
167
|
DEFAULT_CAMERA_NOT_FOUND_ERROR: 'DEFAULT_CAMERA_NOT_FOUND_ERROR',
|
|
96
168
|
};
|
|
97
169
|
|
|
98
|
-
// Face distance is calculated as pupilDistance / ovalWidth.
|
|
99
|
-
// The further away you are from the camera the distance between your pupils will decrease, thus lowering the threshold values.
|
|
100
|
-
// Constants from science team to determine ocular distance (space between eyes)
|
|
101
|
-
const PUPIL_DISTANCE_WEIGHT = 2.0;
|
|
102
|
-
const FACE_HEIGHT_WEIGHT = 1.8;
|
|
103
|
-
// Constants from science team to find face match percentage
|
|
104
|
-
const FACE_MATCH_RANGE_MIN = 0;
|
|
105
|
-
const FACE_MATCH_RANGE_MAX = 1;
|
|
106
|
-
const FACE_MATCH_WEIGHT_MIN = 0.25;
|
|
107
|
-
const FACE_MATCH_WEIGHT_MAX = 0.75;
|
|
108
|
-
const OVAL_HEIGHT_WIDTH_RATIO = 1.618;
|
|
109
|
-
const WS_CLOSURE_CODE = {
|
|
110
|
-
SUCCESS_CODE: 1000,
|
|
111
|
-
DEFAULT_ERROR_CODE: 4000,
|
|
112
|
-
FACE_FIT_TIMEOUT: 4001,
|
|
113
|
-
USER_CANCEL: 4003,
|
|
114
|
-
RUNTIME_ERROR: 4005,
|
|
115
|
-
USER_ERROR_DURING_CONNECTION: 4007,
|
|
116
|
-
};
|
|
117
|
-
// number in milliseconds to record into each video chunk.
|
|
118
|
-
// see https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/start#timeslice
|
|
119
|
-
const TIME_SLICE = 1000;
|
|
120
|
-
// in MS, the rate at which colors are rendered/checked
|
|
121
|
-
const TICK_RATE = 10;
|
|
122
|
-
/**
|
|
123
|
-
* The number of seconds before the presigned URL expires.
|
|
124
|
-
* Used to override aws sdk default value of 60
|
|
125
|
-
*/
|
|
126
|
-
const REQUEST_EXPIRY = 299;
|
|
127
|
-
/**
|
|
128
|
-
* The maximum time in milliseconds that the connection phase of a request
|
|
129
|
-
* may take before the connection attempt is abandoned.
|
|
130
|
-
*/
|
|
131
|
-
const CONNECTION_TIMEOUT = 10000;
|
|
132
|
-
const FACE_MOVEMENT_AND_LIGHT_CHALLENGE = {
|
|
133
|
-
type: 'FaceMovementAndLightChallenge',
|
|
134
|
-
version: '2.0.0',
|
|
135
|
-
};
|
|
136
|
-
const FACE_MOVEMENT_CHALLENGE = {
|
|
137
|
-
type: 'FaceMovementChallenge',
|
|
138
|
-
version: '1.0.0',
|
|
139
|
-
};
|
|
140
|
-
const SUPPORTED_CHALLENGES = [
|
|
141
|
-
FACE_MOVEMENT_AND_LIGHT_CHALLENGE,
|
|
142
|
-
FACE_MOVEMENT_CHALLENGE,
|
|
143
|
-
];
|
|
144
|
-
const queryParameterString = SUPPORTED_CHALLENGES.map((challenge) => `${challenge.type}_${challenge.version}`).join(',');
|
|
145
|
-
|
|
146
170
|
/**
|
|
147
171
|
* Returns the random number between min and max
|
|
148
172
|
* seeded with the provided random seed.
|
|
@@ -1227,7 +1251,7 @@ function createRequestStreamGenerator(stream) {
|
|
|
1227
1251
|
};
|
|
1228
1252
|
}
|
|
1229
1253
|
|
|
1230
|
-
const VERSION = '3.6.
|
|
1254
|
+
const VERSION = '3.6.7';
|
|
1231
1255
|
|
|
1232
1256
|
const BASE_USER_AGENT = `ui-react-liveness/${VERSION}`;
|
|
1233
1257
|
const getLivenessUserAgent = () => {
|
|
@@ -1372,48 +1396,97 @@ class CustomWebSocketFetchHandler {
|
|
|
1372
1396
|
let streamError = undefined;
|
|
1373
1397
|
// To notify onclose event that error has occurred.
|
|
1374
1398
|
let socketErrorOccurred = false;
|
|
1375
|
-
//
|
|
1376
|
-
|
|
1377
|
-
|
|
1399
|
+
// Buffer for messages that arrive before the consumer calls .next().
|
|
1400
|
+
// This prevents dropped messages when the server responds faster than
|
|
1401
|
+
// the client sets up its async iterator (race condition).
|
|
1402
|
+
const messageQueue = [];
|
|
1403
|
+
let waitingResolve = null;
|
|
1404
|
+
let waitingReject = null;
|
|
1405
|
+
let done = false;
|
|
1406
|
+
let endError = undefined;
|
|
1378
1407
|
socket.onmessage = (event) => {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1408
|
+
const chunk = new Uint8Array(event.data);
|
|
1409
|
+
if (waitingResolve) {
|
|
1410
|
+
// Consumer is waiting for data — deliver immediately
|
|
1411
|
+
const resolve = waitingResolve;
|
|
1412
|
+
waitingResolve = null;
|
|
1413
|
+
waitingReject = null;
|
|
1414
|
+
resolve({ done: false, value: chunk });
|
|
1415
|
+
}
|
|
1416
|
+
else {
|
|
1417
|
+
// Consumer hasn't called .next() yet — buffer the message
|
|
1418
|
+
messageQueue.push(chunk);
|
|
1419
|
+
}
|
|
1383
1420
|
};
|
|
1384
1421
|
socket.onerror = (error) => {
|
|
1385
1422
|
socketErrorOccurred = true;
|
|
1386
1423
|
socket.close();
|
|
1387
|
-
|
|
1424
|
+
done = true;
|
|
1425
|
+
endError = error;
|
|
1426
|
+
if (waitingReject) {
|
|
1427
|
+
const reject = waitingReject;
|
|
1428
|
+
waitingResolve = null;
|
|
1429
|
+
waitingReject = null;
|
|
1430
|
+
reject(error);
|
|
1431
|
+
}
|
|
1388
1432
|
};
|
|
1389
1433
|
socket.onclose = (event) => {
|
|
1390
1434
|
this.removeNotUsableSockets(socket.url);
|
|
1391
1435
|
if (socketErrorOccurred)
|
|
1392
1436
|
return;
|
|
1437
|
+
done = true;
|
|
1393
1438
|
if (streamError) {
|
|
1394
|
-
|
|
1439
|
+
endError = streamError;
|
|
1395
1440
|
}
|
|
1396
1441
|
else if (event.code !== WS_CLOSURE_CODE.SUCCESS_CODE &&
|
|
1397
1442
|
event.code !== 1001) {
|
|
1398
1443
|
// Server closed the connection with an abnormal code (e.g. 4001
|
|
1399
1444
|
// StreamIdleTimeout, 4003 SessionExpired, 1006 abnormal closure).
|
|
1400
|
-
|
|
1445
|
+
endError = new Error(`Server ended the connection unexpectedly (code ${event.code}` +
|
|
1401
1446
|
(event.reason ? `: ${event.reason}` : '') +
|
|
1402
|
-
')')
|
|
1447
|
+
')');
|
|
1403
1448
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1449
|
+
if (waitingResolve) {
|
|
1450
|
+
if (endError) {
|
|
1451
|
+
const reject = waitingReject;
|
|
1452
|
+
waitingResolve = null;
|
|
1453
|
+
waitingReject = null;
|
|
1454
|
+
reject(endError);
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
const resolve = waitingResolve;
|
|
1458
|
+
waitingResolve = null;
|
|
1459
|
+
waitingReject = null;
|
|
1460
|
+
resolve({ done: true, value: undefined });
|
|
1461
|
+
}
|
|
1409
1462
|
}
|
|
1410
1463
|
};
|
|
1411
1464
|
const outputStream = {
|
|
1412
1465
|
[Symbol.asyncIterator]: () => ({
|
|
1413
1466
|
next: () => {
|
|
1467
|
+
// If there are buffered messages, deliver the next one immediately
|
|
1468
|
+
if (messageQueue.length > 0) {
|
|
1469
|
+
return Promise.resolve({
|
|
1470
|
+
done: false,
|
|
1471
|
+
value: messageQueue.shift(),
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
// If the stream has ended, resolve/reject accordingly
|
|
1475
|
+
if (done) {
|
|
1476
|
+
if (endError) {
|
|
1477
|
+
return Promise.reject(endError instanceof Error
|
|
1478
|
+
? endError
|
|
1479
|
+
: new Error('Stream ended with an error'));
|
|
1480
|
+
}
|
|
1481
|
+
return Promise.resolve({
|
|
1482
|
+
done: true,
|
|
1483
|
+
value: undefined,
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
// Otherwise wait for the next message or close event
|
|
1414
1487
|
return new Promise((_resolve, _reject) => {
|
|
1415
|
-
|
|
1416
|
-
|
|
1488
|
+
waitingResolve = _resolve;
|
|
1489
|
+
waitingReject = _reject;
|
|
1417
1490
|
});
|
|
1418
1491
|
},
|
|
1419
1492
|
}),
|
|
@@ -1436,8 +1509,7 @@ class CustomWebSocketFetchHandler {
|
|
|
1436
1509
|
catch (err) {
|
|
1437
1510
|
// We don't throw the error here because the send()'s returned
|
|
1438
1511
|
// would already be settled by the time sending chunk throws error.
|
|
1439
|
-
// Instead,
|
|
1440
|
-
// exceptions
|
|
1512
|
+
// Instead, notify the output stream to throw if there's exceptions
|
|
1441
1513
|
if (err instanceof Error) {
|
|
1442
1514
|
streamError = err;
|
|
1443
1515
|
}
|
|
@@ -1973,6 +2045,10 @@ const livenessMachine = xstate.createMachine({
|
|
|
1973
2045
|
'spawnResponseStreamActor',
|
|
1974
2046
|
],
|
|
1975
2047
|
},
|
|
2048
|
+
onError: {
|
|
2049
|
+
target: '#livenessMachine.error',
|
|
2050
|
+
actions: 'updateErrorStateForRuntime',
|
|
2051
|
+
},
|
|
1976
2052
|
},
|
|
1977
2053
|
},
|
|
1978
2054
|
waitForSessionInfo: {
|
|
@@ -2001,6 +2077,10 @@ const livenessMachine = xstate.createMachine({
|
|
|
2001
2077
|
target: 'checkFaceDetectedBeforeStart',
|
|
2002
2078
|
actions: 'updateFaceMatchBeforeStartDetails',
|
|
2003
2079
|
},
|
|
2080
|
+
onError: {
|
|
2081
|
+
target: 'error',
|
|
2082
|
+
actions: 'updateErrorStateForRuntime',
|
|
2083
|
+
},
|
|
2004
2084
|
},
|
|
2005
2085
|
},
|
|
2006
2086
|
checkFaceDetectedBeforeStart: {
|
|
@@ -2019,6 +2099,10 @@ const livenessMachine = xstate.createMachine({
|
|
|
2019
2099
|
target: 'checkFaceDistanceBeforeRecording',
|
|
2020
2100
|
actions: 'updateFaceDistanceBeforeRecording',
|
|
2021
2101
|
},
|
|
2102
|
+
onError: {
|
|
2103
|
+
target: 'error',
|
|
2104
|
+
actions: 'updateErrorStateForRuntime',
|
|
2105
|
+
},
|
|
2022
2106
|
},
|
|
2023
2107
|
},
|
|
2024
2108
|
checkFaceDistanceBeforeRecording: {
|
|
@@ -2696,13 +2780,7 @@ const livenessMachine = xstate.createMachine({
|
|
|
2696
2780
|
const { videoEl } = context.videoAssociatedParams;
|
|
2697
2781
|
const { faceDetector } = context.ovalAssociatedParams;
|
|
2698
2782
|
// initialize models
|
|
2699
|
-
|
|
2700
|
-
await faceDetector.modelLoadingPromise;
|
|
2701
|
-
}
|
|
2702
|
-
catch (err) {
|
|
2703
|
-
// eslint-disable-next-line no-console
|
|
2704
|
-
console.log({ err });
|
|
2705
|
-
}
|
|
2783
|
+
await faceDetector.modelLoadingPromise;
|
|
2706
2784
|
// detect face
|
|
2707
2785
|
const faceMatchState = await getFaceMatchState(faceDetector, videoEl);
|
|
2708
2786
|
return { faceMatchState };
|
|
@@ -2734,13 +2812,7 @@ const livenessMachine = xstate.createMachine({
|
|
|
2734
2812
|
const { videoEl, canvasEl, isMobile } = context.videoAssociatedParams;
|
|
2735
2813
|
const { faceDetector } = context.ovalAssociatedParams;
|
|
2736
2814
|
// initialize models
|
|
2737
|
-
|
|
2738
|
-
await faceDetector.modelLoadingPromise;
|
|
2739
|
-
}
|
|
2740
|
-
catch (err) {
|
|
2741
|
-
// eslint-disable-next-line no-console
|
|
2742
|
-
console.log({ err });
|
|
2743
|
-
}
|
|
2815
|
+
await faceDetector.modelLoadingPromise;
|
|
2744
2816
|
// detect face
|
|
2745
2817
|
const detectedFaces = await faceDetector.detectFaces(videoEl);
|
|
2746
2818
|
let initialFace;
|
|
@@ -22,9 +22,12 @@ export type Coordinate = [number, number];
|
|
|
22
22
|
*/
|
|
23
23
|
export declare abstract class FaceDetection {
|
|
24
24
|
modelLoadingPromise: Promise<void>;
|
|
25
|
+
private _modelLoadTriggered;
|
|
25
26
|
/**
|
|
26
27
|
* Triggers the `loadModels` method and stores
|
|
27
28
|
* the corresponding promise to be awaited later.
|
|
29
|
+
* Applies a timeout to prevent indefinite hangs if the
|
|
30
|
+
* network request for model assets stalls.
|
|
28
31
|
*/
|
|
29
32
|
triggerModelLoading(): void;
|
|
30
33
|
/**
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "3.6.
|
|
1
|
+
export declare const VERSION = "3.6.7";
|