@aws-amplify/ui-react-liveness 3.0.21 → 3.0.23

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