-
Notifications
You must be signed in to change notification settings - Fork 342
React liveness/provide default device info #6633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
soberm
merged 46 commits into
aws-amplify:main
from
SmarterServices:react-liveness/provide-default-device-info
Nov 6, 2025
Merged
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
17beabe
chore: clean up some formatting issues
jasonfill 340d6f7
chore: fix additional formatting issues
jasonfill 1eec65d
chore: fix more formatting
jasonfill e0f224f
chore: fix some formatting items
jasonfill a1d933a
chore: additional formatting changes
jasonfill e6e03b5
chore: some resolutions were removed, replaced them
jasonfill 3c451ba
chore: formatting
jasonfill 970b2cd
chore: remove duplicate items in package
jasonfill 221e986
chore: formatting
jasonfill 982d2fb
chore: add ability to provide default device label to select that dev…
riasatali42 e1afc54
chore: added callback method for camera changed and if not default ca…
riasatali42 d9e9829
chore: added selectableDevices array check
riasatali42 9e50956
chore: added default device info in machine tests for unit testing
riasatali42 cb855f1
chore: default device info for unit testing default device pass in
riasatali42 7bf2668
chore: unit tests added for default device not found and camera switc…
riasatali42 be062ed
chore: added device selection priority unit tests
riasatali42 7120b36
docs: added changeset for minor changes
riasatali42 a89bfd9
fix: fixed mock device info after resolving merge conflicts
riasatali42 f752e2d
chore: added updated unit tests for passed in device label, callCamer…
riasatali42 66d59c3
fix: fixed ESlint issue on livenesscameramodule
riasatali42 eef17a2
fix: fixed eslint issue on adding type on error and replaced operator
riasatali42 b92d62d
fix: resolved PR feedback, added console error, removed device info f…
riasatali42 c33f155
chore: merged with main branch and added e2e testing for react-livene…
riasatali42 c5e9cd9
chore: Added step for I see the Device ID selectfield, added e2e test…
riasatali42 12ccec5
chore: removed deviceLabel, cameraNotFound callback and onCamerChange…
riasatali42 c095a5a
chore: removed all logics of deviceLabel pass in, onCameraChange and …
riasatali42 77b2dfc
chore: removed onCameraChange and defaultCameraNotFound callback from…
riasatali42 19a8bae
chore: added removed code for cameraChange
riasatali42 2bc3e21
chore: DEFAULT_CAMERA_NOT_FOUND_ERROR error added for react-liveness
riasatali42 3b89f7e
chore: added new unit tests for passing default device id and if not …
riasatali42 032bc12
chore: added device id related e2e tests, removed device label pass i…
riasatali42 9fb0ce9
chore: removed unnecessary lines
riasatali42 c40406a
chore: replaced deviceId from props to config props of LivenessDetector
riasatali42 5dc17c5
chore: added deviceId in the next example app of LivenessDetector to …
riasatali42 d6c3638
chore: updated next-example to support default deviceId using params
riasatali42 801a5da
chore: removed unnecessary lines from next-example of pass in default…
riasatali42 32e3175
chore: updated e2e test for invald device pass in in liveness detection
riasatali42 ac4e22f
chore: changeset updated
riasatali42 40e9086
chore: removed unnecessary onUserTimeout callback and update e2e test…
riasatali42 c9f6882
chore: added deviceInfo while calling the onError callback in livenes…
riasatali42 083b11e
chore: added unit tests for passing deviceInfo while calling onError …
riasatali42 2ec7e77
fix: fixed lint issue by removing quote sign and added " tag
riasatali42 ae9671b
chore: removed unnecessary codes from liveness test code
riasatali42 8c9325e
fix: resolved unit test issue due to branch rebase and cleaned unnece…
riasatali42 03dd642
fix: added deviceInfo type for liveness checking
riasatali42 6a7bf88
fix: correct select field locator in liveness e2e test
marajulEnosis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@aws-amplify/ui-react-liveness': minor | ||
| --- | ||
|
|
||
| Pass default device info using ID. Emit detailed device info on liveness complete. DEFAULT_CAMERA_NOT_FOUND_ERROR added in onError callback if device not found with deviceId. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,4 +50,4 @@ | |
| "@types/jasmine" | ||
| ] | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
210 changes: 210 additions & 0 deletions
210
...s/next/pages/ui/components/liveness/pass-in-default-device/PassInDefaultDeviceExample.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,210 @@ | ||
| import React from 'react'; | ||
| import { | ||
| View, | ||
| Flex, | ||
| Loader, | ||
| Text, | ||
| Card, | ||
| Heading, | ||
| Divider, | ||
| } from '@aws-amplify/ui-react'; | ||
| import { FaceLivenessDetectorCore } from '@aws-amplify/ui-react-liveness'; | ||
| import { useLiveness } from '../components/useLiveness'; | ||
| import { ChallengeSelection } from '../components/ChallengeSelection'; | ||
| import { SessionIdAlert } from '../components/SessionIdAlert'; | ||
| import LivenessInlineResults from '../components/LivenessInlineResults'; | ||
|
|
||
| const FACE_MOVEMENT_AND_LIGHT_CHALLENGE = 'FaceMovementAndLightChallenge'; | ||
| const FACE_MOVEMENT_CHALLENGE = 'FaceMovementChallenge'; | ||
|
|
||
| const SUPPORTED_CHALLENGES_TYPES = [ | ||
| FACE_MOVEMENT_AND_LIGHT_CHALLENGE, | ||
| FACE_MOVEMENT_CHALLENGE, | ||
| ]; | ||
|
|
||
| export default function PassInDefaultDeviceExample() { | ||
| const [challengeType, setChallengeType] = React.useState( | ||
| FACE_MOVEMENT_AND_LIGHT_CHALLENGE | ||
| ); | ||
|
|
||
| // Test hooks for e2e testing | ||
| const [testDeviceId, setTestDeviceId] = React.useState<string | null>(null); | ||
| const [currentDeviceInfo, setCurrentDeviceInfo] = React.useState<any>(null); | ||
|
|
||
| // Config to optionally preselect a camera by deviceId (e.g., via URL query). | ||
| const livenessConfig = React.useMemo(() => { | ||
| // Read from query string ?deviceId=... if present | ||
| if (typeof window !== 'undefined') { | ||
| const params = new URLSearchParams(window.location.search); | ||
| const qDeviceId = params.get('deviceId'); | ||
| if (qDeviceId) { | ||
| return { deviceId: qDeviceId } as { deviceId?: string }; | ||
| } | ||
| } | ||
| // No default; let SDK choose if not provided | ||
| return { deviceId: undefined } as { deviceId?: string }; | ||
| }, []); | ||
|
|
||
| // Validate provided deviceId and expose to existing e2e hooks | ||
| React.useEffect(() => { | ||
| const configuredId = livenessConfig?.deviceId; | ||
| if (!configuredId || typeof window === 'undefined') return; | ||
|
|
||
| let cancelled = false; | ||
| (async () => { | ||
| try { | ||
| // Enumerate devices to verify presence | ||
| if (navigator?.mediaDevices?.enumerateDevices) { | ||
| const devices = await navigator.mediaDevices.enumerateDevices(); | ||
| const found = devices.some( | ||
| (d) => d.kind === 'videoinput' && d.deviceId === configuredId | ||
| ); | ||
| if (!cancelled) { | ||
| (window as any).testDeviceIdIsValid = found; | ||
| setTestDeviceId(configuredId); | ||
| } | ||
| } else { | ||
| // Assume not valid if enumeration is unavailable | ||
| (window as any).testDeviceIdIsValid = false; | ||
| setTestDeviceId(configuredId); | ||
| } | ||
| } catch (e) { | ||
| // Fallback to not valid on error | ||
| (window as any).testDeviceIdIsValid = false; | ||
| setTestDeviceId(configuredId); | ||
| } | ||
| })(); | ||
| }, [livenessConfig?.deviceId]); | ||
|
|
||
| // Setup e2e hooks | ||
| React.useEffect(() => { | ||
| if (typeof window !== 'undefined') { | ||
| // Expose setters and current device info | ||
| (window as any).setTestDeviceId = setTestDeviceId; | ||
| (window as any).currentDeviceInfo = currentDeviceInfo; | ||
|
|
||
| // Check for a deviceId seeded by tests | ||
| const testId = (window as any).testDeviceId; | ||
| if (testId) { | ||
| setTestDeviceId(testId); | ||
| } | ||
| } | ||
| }, [currentDeviceInfo]); | ||
|
|
||
| const { | ||
| getLivenessResponse, | ||
| createLivenessSessionApiError, | ||
| createLivenessSessionApiData, | ||
| createLivenessSessionApiLoading, | ||
| handleGetLivenessDetection, | ||
| stopLiveness, | ||
| } = useLiveness(challengeType); | ||
|
|
||
| if (createLivenessSessionApiError) { | ||
| return <div>Some error occurred...</div>; | ||
| } | ||
|
|
||
| function onUserCancel() { | ||
| stopLiveness(); | ||
| } | ||
|
|
||
| return ( | ||
| <View maxWidth="800px" margin="0 auto"> | ||
| {createLivenessSessionApiLoading ? ( | ||
| <Flex justifyContent="center" alignItems="center"> | ||
| <Loader /> <Text as="span">Loading...</Text> | ||
| </Flex> | ||
| ) : ( | ||
| <Flex direction="column" gap="xl"> | ||
| <Card variation="elevated" padding="large"> | ||
| <Heading level={3} marginBottom="medium"> | ||
| Pass-in Default Device Example | ||
| </Heading> | ||
| <Text marginBottom="large" color="gray"> | ||
| This example demonstrates passing a default camera via a config | ||
| prop (for example, using the URL query "?deviceId=..."). | ||
| If the provided deviceId matches an available camera, that device | ||
| will be auto-selected for liveness. If not found, the example will | ||
| fall back to another available camera. | ||
| </Text> | ||
| </Card> | ||
|
|
||
| <ChallengeSelection | ||
| selectedChallenge={challengeType} | ||
| onChange={setChallengeType} | ||
| challengeList={SUPPORTED_CHALLENGES_TYPES} | ||
| /> | ||
|
|
||
| <SessionIdAlert | ||
| sessionId={createLivenessSessionApiData['sessionId']} | ||
| /> | ||
|
|
||
| {/* Test Device Configuration Display */} | ||
| {testDeviceId && ( | ||
| <Card variation="outlined" padding="medium"> | ||
| <Heading level={4} marginBottom="small"> | ||
| Test Device Configuration | ||
| </Heading> | ||
| <Text>Test Device ID: {testDeviceId}</Text> | ||
| {currentDeviceInfo && ( | ||
| <div> | ||
| <Text fontWeight="bold" marginTop="small"> | ||
| Current Device Info: | ||
| </Text> | ||
| <Text>Device ID: {currentDeviceInfo.deviceId}</Text> | ||
| <Text>Label: {currentDeviceInfo.label}</Text> | ||
| <Text>Group ID: {currentDeviceInfo.groupId}</Text> | ||
| </div> | ||
| )} | ||
| </Card> | ||
| )} | ||
|
|
||
| {!!getLivenessResponse ? ( | ||
| <LivenessInlineResults | ||
| getLivenessResponse={getLivenessResponse} | ||
| onUserCancel={onUserCancel} | ||
| /> | ||
| ) : null} | ||
|
|
||
| <Divider /> | ||
|
|
||
| <Flex gap="0" direction="column" position="relative"> | ||
| {!getLivenessResponse ? ( | ||
| <FaceLivenessDetectorCore | ||
| sessionId={createLivenessSessionApiData['sessionId']} | ||
| region={'us-east-1'} | ||
| onUserCancel={onUserCancel} | ||
| config={livenessConfig} | ||
| onAnalysisComplete={async () => { | ||
| // Mock device info for testing | ||
| const mockDeviceInfo = { | ||
| deviceId: testDeviceId || 'default-camera', | ||
| label: testDeviceId | ||
| ? `Camera for ${testDeviceId}` | ||
| : 'Default Camera', | ||
| groupId: 'test-group-123', | ||
| }; | ||
|
|
||
| setCurrentDeviceInfo(mockDeviceInfo); | ||
|
|
||
| // Expose to window for e2e testing | ||
| if (typeof window !== 'undefined') { | ||
| (window as any).currentDeviceInfo = mockDeviceInfo; | ||
| (window as any).onAnalysisComplete = () => mockDeviceInfo; | ||
| } | ||
|
|
||
| await handleGetLivenessDetection( | ||
| createLivenessSessionApiData['sessionId'] | ||
| ); | ||
| }} | ||
| onError={(livenessError) => { | ||
| console.error('Liveness error:', livenessError); | ||
| }} | ||
| /> | ||
| ) : null} | ||
| </Flex> | ||
| </Flex> | ||
| )} | ||
| </View> | ||
| ); | ||
| } | ||
25 changes: 25 additions & 0 deletions
25
examples/next/pages/ui/components/liveness/pass-in-default-device/index.page.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import dynamic from 'next/dynamic'; | ||
| import React from 'react'; | ||
|
|
||
| import { Amplify } from 'aws-amplify'; | ||
| import awsExports from '@environments/liveness/liveness-environment/src/aws-exports'; | ||
|
|
||
| import Layout from '../components/Layout'; | ||
| import PassInDefaultDeviceExample from './PassInDefaultDeviceExample'; | ||
|
|
||
| Amplify.configure({ | ||
| ...awsExports, | ||
| // Analytics: { autoSessionRecord: false }, | ||
| }); | ||
|
|
||
| const App = () => { | ||
| return ( | ||
| <Layout> | ||
| <PassInDefaultDeviceExample /> | ||
| </Layout> | ||
| ); | ||
| }; | ||
|
|
||
| export default dynamic(() => Promise.resolve(App), { | ||
| ssr: false, | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,4 +21,4 @@ | |
| "rimraf": "^5.0.0", | ||
| "vite-plugin-pages": "^0.25.0" | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.