Skip to content

Commit 23d1417

Browse files
riasatali42jasonfillmarajulEnosis
authored
feat(liveness): add default deviceId and deviceInfo (#6633)
* chore: clean up some formatting issues * chore: fix additional formatting issues * chore: fix more formatting * chore: fix some formatting items * chore: additional formatting changes * chore: some resolutions were removed, replaced them * chore: formatting * chore: remove duplicate items in package * chore: formatting * chore: add ability to provide default device label to select that device automatically * chore: added callback method for camera changed and if not default camera is found * chore: added selectableDevices array check * chore: added default device info in machine tests for unit testing * chore: default device info for unit testing default device pass in * chore: unit tests added for default device not found and camera switching * chore: added device selection priority unit tests * docs: added changeset for minor changes * fix: fixed mock device info after resolving merge conflicts * chore: added updated unit tests for passed in device label, callCameraNotFoundCallback and device change callback methods after resolving merge conflicts * fix: fixed ESlint issue on livenesscameramodule * fix: fixed eslint issue on adding type on error and replaced operator * fix: resolved PR feedback, added console error, removed device info from the onUserCancel callback * chore: merged with main branch and added e2e testing for react-liveness default device pass in * chore: Added step for I see the Device ID selectfield, added e2e test case for device info pass out * chore: removed deviceLabel, cameraNotFound callback and onCamerChange callback from types * chore: removed all logics of deviceLabel pass in, onCameraChange and onCameraNotFound callback * chore: removed onCameraChange and defaultCameraNotFound callback from example app * chore: added removed code for cameraChange * chore: DEFAULT_CAMERA_NOT_FOUND_ERROR error added for react-liveness * chore: added new unit tests for passing default device id and if not camera is availabel with the deviceId * chore: added device id related e2e tests, removed device label pass in e2e tests * chore: removed unnecessary lines * chore: replaced deviceId from props to config props of LivenessDetector * chore: added deviceId in the next example app of LivenessDetector to select default device * chore: updated next-example to support default deviceId using params * chore: removed unnecessary lines from next-example of pass in default device * chore: updated e2e test for invald device pass in in liveness detection * chore: changeset updated * chore: removed unnecessary onUserTimeout callback and update e2e testing for device not found * chore: added deviceInfo while calling the onError callback in liveness testing * chore: added unit tests for passing deviceInfo while calling onError callback * fix: fixed lint issue by removing quote sign and added &quot; tag * chore: removed unnecessary codes from liveness test code * fix: resolved unit test issue due to branch rebase and cleaned unnecessary code * fix: added deviceInfo type for liveness checking * fix: correct select field locator in liveness e2e test --------- Co-authored-by: Jason Fill <jason@smarterservices.com> Co-authored-by: marajulEnosis <marajul.islam@enosisbd.com>
1 parent 77765a8 commit 23d1417

File tree

20 files changed

+1084
-43
lines changed

20 files changed

+1084
-43
lines changed

.changeset/chilly-olives-dress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@aws-amplify/ui-react-liveness': minor
3+
---
4+
5+
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.

examples/angular/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@
5050
"@types/jasmine"
5151
]
5252
}
53-
}
53+
}

examples/next/pages/ui/components/liveness/components/LivenessDefault.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export default function LivenessDefault({
3333
} = useLiveness(challengeType);
3434

3535
if (createLivenessSessionApiError) {
36+
console.error(createLivenessSessionApiError);
3637
return <div>Some error occurred...</div>;
3738
}
3839

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import React from 'react';
2+
import {
3+
View,
4+
Flex,
5+
Loader,
6+
Text,
7+
Card,
8+
Heading,
9+
Divider,
10+
} from '@aws-amplify/ui-react';
11+
import { FaceLivenessDetectorCore } from '@aws-amplify/ui-react-liveness';
12+
import { useLiveness } from '../components/useLiveness';
13+
import { ChallengeSelection } from '../components/ChallengeSelection';
14+
import { SessionIdAlert } from '../components/SessionIdAlert';
15+
import LivenessInlineResults from '../components/LivenessInlineResults';
16+
17+
const FACE_MOVEMENT_AND_LIGHT_CHALLENGE = 'FaceMovementAndLightChallenge';
18+
const FACE_MOVEMENT_CHALLENGE = 'FaceMovementChallenge';
19+
20+
const SUPPORTED_CHALLENGES_TYPES = [
21+
FACE_MOVEMENT_AND_LIGHT_CHALLENGE,
22+
FACE_MOVEMENT_CHALLENGE,
23+
];
24+
25+
export default function PassInDefaultDeviceExample() {
26+
const [challengeType, setChallengeType] = React.useState(
27+
FACE_MOVEMENT_AND_LIGHT_CHALLENGE
28+
);
29+
30+
// Test hooks for e2e testing
31+
const [testDeviceId, setTestDeviceId] = React.useState<string | null>(null);
32+
const [currentDeviceInfo, setCurrentDeviceInfo] = React.useState<any>(null);
33+
34+
// Config to optionally preselect a camera by deviceId (e.g., via URL query).
35+
const livenessConfig = React.useMemo(() => {
36+
// Read from query string ?deviceId=... if present
37+
if (typeof window !== 'undefined') {
38+
const params = new URLSearchParams(window.location.search);
39+
const qDeviceId = params.get('deviceId');
40+
if (qDeviceId) {
41+
return { deviceId: qDeviceId } as { deviceId?: string };
42+
}
43+
}
44+
// No default; let SDK choose if not provided
45+
return { deviceId: undefined } as { deviceId?: string };
46+
}, []);
47+
48+
// Validate provided deviceId and expose to existing e2e hooks
49+
React.useEffect(() => {
50+
const configuredId = livenessConfig?.deviceId;
51+
if (!configuredId || typeof window === 'undefined') return;
52+
53+
let cancelled = false;
54+
(async () => {
55+
try {
56+
// Enumerate devices to verify presence
57+
if (navigator?.mediaDevices?.enumerateDevices) {
58+
const devices = await navigator.mediaDevices.enumerateDevices();
59+
const found = devices.some(
60+
(d) => d.kind === 'videoinput' && d.deviceId === configuredId
61+
);
62+
if (!cancelled) {
63+
(window as any).testDeviceIdIsValid = found;
64+
setTestDeviceId(configuredId);
65+
}
66+
} else {
67+
// Assume not valid if enumeration is unavailable
68+
(window as any).testDeviceIdIsValid = false;
69+
setTestDeviceId(configuredId);
70+
}
71+
} catch (e) {
72+
// Fallback to not valid on error
73+
(window as any).testDeviceIdIsValid = false;
74+
setTestDeviceId(configuredId);
75+
}
76+
})();
77+
}, [livenessConfig?.deviceId]);
78+
79+
// Setup e2e hooks
80+
React.useEffect(() => {
81+
if (typeof window !== 'undefined') {
82+
// Expose setters and current device info
83+
(window as any).setTestDeviceId = setTestDeviceId;
84+
(window as any).currentDeviceInfo = currentDeviceInfo;
85+
86+
// Check for a deviceId seeded by tests
87+
const testId = (window as any).testDeviceId;
88+
if (testId) {
89+
setTestDeviceId(testId);
90+
}
91+
}
92+
}, [currentDeviceInfo]);
93+
94+
const {
95+
getLivenessResponse,
96+
createLivenessSessionApiError,
97+
createLivenessSessionApiData,
98+
createLivenessSessionApiLoading,
99+
handleGetLivenessDetection,
100+
stopLiveness,
101+
} = useLiveness(challengeType);
102+
103+
if (createLivenessSessionApiError) {
104+
return <div>Some error occurred...</div>;
105+
}
106+
107+
function onUserCancel() {
108+
stopLiveness();
109+
}
110+
111+
return (
112+
<View maxWidth="800px" margin="0 auto">
113+
{createLivenessSessionApiLoading ? (
114+
<Flex justifyContent="center" alignItems="center">
115+
<Loader /> <Text as="span">Loading...</Text>
116+
</Flex>
117+
) : (
118+
<Flex direction="column" gap="xl">
119+
<Card variation="elevated" padding="large">
120+
<Heading level={3} marginBottom="medium">
121+
Pass-in Default Device Example
122+
</Heading>
123+
<Text marginBottom="large" color="gray">
124+
This example demonstrates passing a default camera via a config
125+
prop (for example, using the URL query &quot;?deviceId=...&quot;).
126+
If the provided deviceId matches an available camera, that device
127+
will be auto-selected for liveness. If not found, the example will
128+
fall back to another available camera.
129+
</Text>
130+
</Card>
131+
132+
<ChallengeSelection
133+
selectedChallenge={challengeType}
134+
onChange={setChallengeType}
135+
challengeList={SUPPORTED_CHALLENGES_TYPES}
136+
/>
137+
138+
<SessionIdAlert
139+
sessionId={createLivenessSessionApiData['sessionId']}
140+
/>
141+
142+
{/* Test Device Configuration Display */}
143+
{testDeviceId && (
144+
<Card variation="outlined" padding="medium">
145+
<Heading level={4} marginBottom="small">
146+
Test Device Configuration
147+
</Heading>
148+
<Text>Test Device ID: {testDeviceId}</Text>
149+
{currentDeviceInfo && (
150+
<div>
151+
<Text fontWeight="bold" marginTop="small">
152+
Current Device Info:
153+
</Text>
154+
<Text>Device ID: {currentDeviceInfo.deviceId}</Text>
155+
<Text>Label: {currentDeviceInfo.label}</Text>
156+
<Text>Group ID: {currentDeviceInfo.groupId}</Text>
157+
</div>
158+
)}
159+
</Card>
160+
)}
161+
162+
{!!getLivenessResponse ? (
163+
<LivenessInlineResults
164+
getLivenessResponse={getLivenessResponse}
165+
onUserCancel={onUserCancel}
166+
/>
167+
) : null}
168+
169+
<Divider />
170+
171+
<Flex gap="0" direction="column" position="relative">
172+
{!getLivenessResponse ? (
173+
<FaceLivenessDetectorCore
174+
sessionId={createLivenessSessionApiData['sessionId']}
175+
region={'us-east-1'}
176+
onUserCancel={onUserCancel}
177+
config={livenessConfig}
178+
onAnalysisComplete={async () => {
179+
// Mock device info for testing
180+
const mockDeviceInfo = {
181+
deviceId: testDeviceId || 'default-camera',
182+
label: testDeviceId
183+
? `Camera for ${testDeviceId}`
184+
: 'Default Camera',
185+
groupId: 'test-group-123',
186+
};
187+
188+
setCurrentDeviceInfo(mockDeviceInfo);
189+
190+
// Expose to window for e2e testing
191+
if (typeof window !== 'undefined') {
192+
(window as any).currentDeviceInfo = mockDeviceInfo;
193+
(window as any).onAnalysisComplete = () => mockDeviceInfo;
194+
}
195+
196+
await handleGetLivenessDetection(
197+
createLivenessSessionApiData['sessionId']
198+
);
199+
}}
200+
onError={(livenessError) => {
201+
console.error('Liveness error:', livenessError);
202+
}}
203+
/>
204+
) : null}
205+
</Flex>
206+
</Flex>
207+
)}
208+
</View>
209+
);
210+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import dynamic from 'next/dynamic';
2+
import React from 'react';
3+
4+
import { Amplify } from 'aws-amplify';
5+
import awsExports from '@environments/liveness/liveness-environment/src/aws-exports';
6+
7+
import Layout from '../components/Layout';
8+
import PassInDefaultDeviceExample from './PassInDefaultDeviceExample';
9+
10+
Amplify.configure({
11+
...awsExports,
12+
// Analytics: { autoSessionRecord: false },
13+
});
14+
15+
const App = () => {
16+
return (
17+
<Layout>
18+
<PassInDefaultDeviceExample />
19+
</Layout>
20+
);
21+
};
22+
23+
export default dynamic(() => Promise.resolve(App), {
24+
ssr: false,
25+
});

examples/vue/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@
2121
"rimraf": "^5.0.0",
2222
"vite-plugin-pages": "^0.25.0"
2323
}
24-
}
24+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"**/@aws-amplify/ui-angular-example/**/codelyzer/**/@angular/core": "10.2.5",
7878
"**/@angular-devkit/build-angular/minimatch": "3.0.5",
7979
"**/@angular-devkit/build-angular/webpack": "^5.76.0",
80+
"**/@angular-devkit/build-angular/webpack-dev-server/express/path-to-regexp": "0.1.12",
8081
"**/@size-limit/webpack/webpack": "^5.76.0",
8182
"**/serve/serve-handler/minimatch": "3.0.5",
8283
"**/serve/serve-handler/path-to-regexp": "3.3.0",

0 commit comments

Comments
 (0)