@smileid/web-components 2.0.2 → 10.0.2
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/DocumentCaptureScreens-ChpBVvNE.js +1532 -0
- package/dist/esm/DocumentCaptureScreens-ChpBVvNE.js.map +1 -0
- package/dist/esm/EndUserConsent-CMHp-34-.js +715 -0
- package/dist/esm/EndUserConsent-CMHp-34-.js.map +1 -0
- package/dist/esm/Navigation-juBE4qOw.js +136 -0
- package/dist/esm/Navigation-juBE4qOw.js.map +1 -0
- package/dist/esm/PoweredBySmileId-CxbaihMu.js +33 -0
- package/dist/esm/PoweredBySmileId-CxbaihMu.js.map +1 -0
- package/dist/esm/SelfieCaptureScreens-DMo-z-32.js +7618 -0
- package/dist/esm/SelfieCaptureScreens-DMo-z-32.js.map +1 -0
- package/dist/esm/SignaturePad-C7MtmT8m.js +324 -0
- package/dist/esm/SignaturePad-C7MtmT8m.js.map +1 -0
- package/dist/esm/TotpConsent-CQU5jQi4.js +730 -0
- package/dist/esm/TotpConsent-CQU5jQi4.js.map +1 -0
- package/dist/esm/combobox.js +300 -0
- package/dist/esm/combobox.js.map +1 -0
- package/dist/esm/document.js +5 -0
- package/dist/esm/document.js.map +1 -0
- package/dist/esm/end-user-consent.js +5 -0
- package/dist/esm/end-user-consent.js.map +1 -0
- package/dist/esm/main.js +22 -0
- package/dist/esm/main.js.map +1 -0
- package/dist/esm/navigation.js +5 -0
- package/dist/esm/navigation.js.map +1 -0
- package/dist/esm/package-D_4zJ9ck.js +105 -0
- package/dist/esm/package-D_4zJ9ck.js.map +1 -0
- package/dist/esm/selfie.js +5 -0
- package/dist/esm/selfie.js.map +1 -0
- package/dist/esm/signature-pad.js +5 -0
- package/dist/esm/signature-pad.js.map +1 -0
- package/dist/esm/smart-camera-web.js +302 -0
- package/dist/esm/smart-camera-web.js.map +1 -0
- package/dist/esm/styles-D2i3GFLK.js +405 -0
- package/dist/esm/styles-D2i3GFLK.js.map +1 -0
- package/dist/esm/totp-consent.js +5 -0
- package/dist/esm/totp-consent.js.map +1 -0
- package/dist/smart-camera-web.js +4130 -0
- package/dist/smart-camera-web.js.map +1 -0
- package/dist/types/combobox.d.ts +21 -0
- package/dist/types/document.d.ts +21 -0
- package/dist/types/end-user-consent.d.ts +21 -0
- package/dist/types/main.d.ts +331 -0
- package/dist/types/navigation.d.ts +21 -0
- package/dist/types/selfie.d.ts +21 -0
- package/dist/types/signature-pad.d.ts +21 -0
- package/dist/types/smart-camera-web.d.ts +21 -0
- package/dist/types/totp-consent.d.ts +21 -0
- package/{src → lib}/components/camera-permission/CameraPermission.js +0 -1
- package/{src → lib}/components/document/src/document-capture/DocumentCapture.js +0 -1
- package/{src → lib}/components/document/src/document-capture-review/DocumentCaptureReview.js +0 -1
- package/{src → lib}/components/selfie/src/SelfieCaptureScreens.js +188 -37
- package/{src → lib}/components/selfie/src/index.js +0 -2
- package/{src → lib}/components/selfie/src/selfie-capture/SelfieCapture.js +3 -4
- package/{src → lib}/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +0 -1
- package/{src → lib}/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +0 -8
- package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +226 -0
- package/lib/components/selfie/src/selfie-capture-wrapper/index.ts +1 -0
- package/lib/components/selfie/src/smartselfie-capture/OvalProgress.tsx +81 -0
- package/lib/components/selfie/src/smartselfie-capture/SmartSelfieCapture.tsx +223 -0
- package/lib/components/selfie/src/smartselfie-capture/components/AlertDisplay.tsx +34 -0
- package/lib/components/selfie/src/smartselfie-capture/components/CameraPreview.tsx +97 -0
- package/lib/components/selfie/src/smartselfie-capture/components/CaptureControls.tsx +74 -0
- package/lib/components/selfie/src/smartselfie-capture/components/index.ts +3 -0
- package/lib/components/selfie/src/smartselfie-capture/constants.ts +23 -0
- package/lib/components/selfie/src/smartselfie-capture/hooks/index.ts +2 -0
- package/lib/components/selfie/src/smartselfie-capture/hooks/useCamera.ts +94 -0
- package/lib/components/selfie/src/smartselfie-capture/hooks/useFaceCapture.ts +558 -0
- package/lib/components/selfie/src/smartselfie-capture/index.ts +1 -0
- package/lib/components/selfie/src/smartselfie-capture/utils/alertMessages.ts +12 -0
- package/lib/components/selfie/src/smartselfie-capture/utils/canvas.ts +105 -0
- package/lib/components/selfie/src/smartselfie-capture/utils/faceDetection.ts +129 -0
- package/lib/components/selfie/src/smartselfie-capture/utils/imageCapture.ts +65 -0
- package/lib/components/selfie/src/smartselfie-capture/utils/index.ts +4 -0
- package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +60 -0
- package/{src → lib}/components/signature-pad/package.json +1 -1
- package/{src → lib}/components/smart-camera-web/src/README.md +0 -1
- package/{src → lib}/styles/src/styles.js +0 -1
- package/package.json +76 -24
- package/src/index.js +0 -5
- /package/{src → lib}/components/README.md +0 -0
- /package/{src → lib}/components/attribution/PoweredBySmileId.js +0 -0
- /package/{src → lib}/components/camera-permission/CameraPermission.stories.js +0 -0
- /package/{src → lib}/components/combobox/src/Combobox.js +0 -0
- /package/{src → lib}/components/combobox/src/index.js +0 -0
- /package/{src → lib}/components/document/src/DocumentCaptureScreens.js +0 -0
- /package/{src → lib}/components/document/src/DocumentCaptureScreens.stories.js +0 -0
- /package/{src → lib}/components/document/src/README.md +0 -0
- /package/{src → lib}/components/document/src/document-capture/DocumentCapture.stories.js +0 -0
- /package/{src → lib}/components/document/src/document-capture/README.md +0 -0
- /package/{src → lib}/components/document/src/document-capture/index.js +0 -0
- /package/{src → lib}/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +0 -0
- /package/{src → lib}/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +0 -0
- /package/{src → lib}/components/document/src/document-capture-instructions/README.md +0 -0
- /package/{src → lib}/components/document/src/document-capture-instructions/index.js +0 -0
- /package/{src → lib}/components/document/src/document-capture-review/DocumentCaptureReview.stories.js +0 -0
- /package/{src → lib}/components/document/src/document-capture-review/README.md +0 -0
- /package/{src → lib}/components/document/src/document-capture-review/index.js +0 -0
- /package/{src → lib}/components/document/src/index.js +0 -0
- /package/{src → lib}/components/end-user-consent/src/EndUserConsent.js +0 -0
- /package/{src → lib}/components/end-user-consent/src/EndUserConsent.stories.js +0 -0
- /package/{src → lib}/components/end-user-consent/src/index.js +0 -0
- /package/{src → lib}/components/navigation/src/Navigation.js +0 -0
- /package/{src → lib}/components/navigation/src/Navigation.stories.js +0 -0
- /package/{src → lib}/components/navigation/src/index.js +0 -0
- /package/{src → lib}/components/selfie/README.md +0 -0
- /package/{src → lib}/components/selfie/src/SelfieCaptureScreens.stories.js +0 -0
- /package/{src → lib}/components/selfie/src/selfie-capture/SelfieCapture.stories.js +0 -0
- /package/{src → lib}/components/selfie/src/selfie-capture/index.js +0 -0
- /package/{src → lib}/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.stories.js +0 -0
- /package/{src → lib}/components/selfie/src/selfie-capture-instructions/index.js +0 -0
- /package/{src → lib}/components/selfie/src/selfie-capture-review/SelfieCaptureReview.stories.js +0 -0
- /package/{src → lib}/components/selfie/src/selfie-capture-review/index.js +0 -0
- /package/{src → lib}/components/signature-pad/package-lock.json +0 -0
- /package/{src → lib}/components/signature-pad/src/SignaturePad.js +0 -0
- /package/{src → lib}/components/signature-pad/src/SignaturePad.stories.js +0 -0
- /package/{src → lib}/components/signature-pad/src/index.js +0 -0
- /package/{src → lib}/components/smart-camera-web/src/SmartCameraWeb.js +0 -0
- /package/{src → lib}/components/smart-camera-web/src/SmartCameraWeb.stories.js +0 -0
- /package/{src → lib}/components/totp-consent/src/TotpConsent.js +0 -0
- /package/{src → lib}/components/totp-consent/src/index.js +0 -0
- /package/{src → lib}/domain/camera/src/README.md +0 -0
- /package/{src → lib}/domain/camera/src/SmartCamera.js +0 -0
- /package/{src → lib}/domain/constants/src/Constants.js +0 -0
- /package/{src → lib}/domain/file-upload/README.md +0 -0
- /package/{src → lib}/domain/file-upload/src/SmartFileUpload.js +0 -0
- /package/{src → lib}/styles/README.md +0 -0
- /package/{src → lib}/styles/src/typography.js +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'preact/hooks';
|
|
2
|
+
|
|
3
|
+
interface OvalProgressProps {
|
|
4
|
+
progress: number;
|
|
5
|
+
duration?: number;
|
|
6
|
+
themeColor?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const OvalProgress = ({
|
|
10
|
+
progress,
|
|
11
|
+
duration = 1000,
|
|
12
|
+
themeColor = '#001096',
|
|
13
|
+
}: OvalProgressProps) => {
|
|
14
|
+
const pathRef = useRef<SVGPathElement>(null);
|
|
15
|
+
const prevProgress = useRef(progress);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const path = pathRef.current;
|
|
19
|
+
if (!path) return;
|
|
20
|
+
|
|
21
|
+
const pathLength = path.getTotalLength();
|
|
22
|
+
path.style.opacity = progress > 0 ? '1' : '0';
|
|
23
|
+
const fromOffset = pathLength * (1 - prevProgress.current);
|
|
24
|
+
const toOffset = pathLength * (1 - progress);
|
|
25
|
+
|
|
26
|
+
// If no change, skip
|
|
27
|
+
if (fromOffset === toOffset) return;
|
|
28
|
+
|
|
29
|
+
path.style.transition = 'none';
|
|
30
|
+
path.style.strokeDasharray = `${pathLength} ${pathLength}`;
|
|
31
|
+
path.style.strokeDashoffset = `${fromOffset}`;
|
|
32
|
+
|
|
33
|
+
// Force style flush
|
|
34
|
+
path.getBoundingClientRect();
|
|
35
|
+
|
|
36
|
+
path.style.transition = `stroke-dashoffset ${duration}ms ease-out`;
|
|
37
|
+
path.style.strokeDashoffset = `${toOffset}`;
|
|
38
|
+
|
|
39
|
+
prevProgress.current = progress;
|
|
40
|
+
}, [progress, duration]);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
style={{
|
|
45
|
+
position: 'absolute',
|
|
46
|
+
top: '0',
|
|
47
|
+
left: '0',
|
|
48
|
+
width: '100%',
|
|
49
|
+
height: '100%',
|
|
50
|
+
pointerEvents: 'none',
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<svg
|
|
54
|
+
viewBox="0 0 285 380"
|
|
55
|
+
preserveAspectRatio="xMidYMid meet"
|
|
56
|
+
style={{ width: '100%', height: '100%' }}
|
|
57
|
+
>
|
|
58
|
+
{/* id is referenced by parent */}
|
|
59
|
+
<clipPath id="selfie-clip-path" clipPathUnits="objectBoundingBox">
|
|
60
|
+
<path d="M0.085 0.382 C0.087 0.357 0.092 0.294 0.131 0.236 C0.200 0.133 0.340 0.063 0.501 0.063 C0.730 0.063 0.915 0.205 0.915 0.382 C0.915 0.424 0.899 0.513 0.891 0.549 C0.882 0.588 0.871 0.626 0.857 0.664 C0.792 0.825 0.639 0.937 0.501 0.937 C0.314 0.937 0.182 0.755 0.144 0.666 C0.126 0.624 0.110 0.557 0.107 0.547 C0.092 0.485 0.081 0.439 0.085 0.382 Z" />
|
|
61
|
+
</clipPath>
|
|
62
|
+
<path
|
|
63
|
+
d="M142.693 24C208.319 24 261 77.97 261 145.008C261 160.97 256.319 194.788 254.129 208.356C251.64 223.188 248.348 237.875 244.27 252.35C225.747 313.203 182.328 356 142.693 356C89.414 356 51.871 286.667 41.016 252.948C35.937 236.987 31.356 211.748 30.559 207.857C26.277 184.114 22.991 166.556 24.285 145.008C24.883 135.631 26.277 111.789 37.431 89.742C57.049 50.636 96.983 24 142.693 24Z"
|
|
64
|
+
stroke={themeColor}
|
|
65
|
+
fill="none"
|
|
66
|
+
style={{ strokeWidth: '8' }}
|
|
67
|
+
/>
|
|
68
|
+
<path
|
|
69
|
+
ref={pathRef}
|
|
70
|
+
d="M142.693 24C208.319 24 261 77.97 261 145.008C261 160.97 256.319 194.788 254.129 208.356C251.64 223.188 248.348 237.875 244.27 252.35C225.747 313.203 182.328 356 142.693 356C89.414 356 51.871 286.667 41.016 252.948C35.937 236.987 31.356 211.748 30.559 207.857C26.277 184.114 22.991 166.556 24.285 145.008C24.883 135.631 26.277 111.789 37.431 89.742C57.049 50.636 96.983 24 142.693 24Z"
|
|
71
|
+
stroke="#22c55e"
|
|
72
|
+
style={{ strokeWidth: '8' }}
|
|
73
|
+
fill="none"
|
|
74
|
+
strokeLinecap="round"
|
|
75
|
+
/>
|
|
76
|
+
</svg>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default OvalProgress;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { useRef, useEffect } from 'preact/hooks';
|
|
2
|
+
import register from 'preact-custom-element';
|
|
3
|
+
import type { FunctionComponent } from 'preact';
|
|
4
|
+
|
|
5
|
+
import { getBoolProp } from '../../../../utils/props';
|
|
6
|
+
import { useFaceCapture, useCamera } from './hooks';
|
|
7
|
+
import { CameraPreview } from './components/CameraPreview';
|
|
8
|
+
import { AlertDisplay } from './components/AlertDisplay';
|
|
9
|
+
import { CaptureControls } from './components/CaptureControls';
|
|
10
|
+
|
|
11
|
+
import '../../../navigation/src';
|
|
12
|
+
import '../../../attribution/PoweredBySmileId';
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
interval?: number;
|
|
16
|
+
duration?: number;
|
|
17
|
+
'theme-color'?: string;
|
|
18
|
+
'show-navigation'?: string | boolean;
|
|
19
|
+
'allow-agent-mode'?: string | boolean;
|
|
20
|
+
'show-agent-mode-for-tests'?: string | boolean;
|
|
21
|
+
'hide-attribution'?: string | boolean;
|
|
22
|
+
'disable-image-tests'?: string | boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const SmartSelfieCapture: FunctionComponent<Props> = ({
|
|
26
|
+
interval = 350,
|
|
27
|
+
duration = 2800,
|
|
28
|
+
'theme-color': themeColor = '#001096',
|
|
29
|
+
'show-navigation': showNavigationProp = false,
|
|
30
|
+
'allow-agent-mode': allowAgentModeProp = false,
|
|
31
|
+
'show-agent-mode-for-tests': showAgentModeForTestsProp = false,
|
|
32
|
+
'hide-attribution': hideAttributionProp = false,
|
|
33
|
+
}) => {
|
|
34
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
35
|
+
const navigationRef = useRef<HTMLElement | null>(null);
|
|
36
|
+
|
|
37
|
+
const showNavigation = getBoolProp(showNavigationProp);
|
|
38
|
+
const allowAgentMode = getBoolProp(allowAgentModeProp);
|
|
39
|
+
const showAgentModeForTests = getBoolProp(showAgentModeForTestsProp);
|
|
40
|
+
const hideAttribution = getBoolProp(hideAttributionProp);
|
|
41
|
+
|
|
42
|
+
const smileCooldown = 300;
|
|
43
|
+
const smileThreshold = 0.25;
|
|
44
|
+
const mouthOpenThreshold = 0.05;
|
|
45
|
+
const minFaceSize = 0.35;
|
|
46
|
+
const maxFaceSize = 0.5;
|
|
47
|
+
|
|
48
|
+
const camera = useCamera();
|
|
49
|
+
|
|
50
|
+
const faceCapture = useFaceCapture({
|
|
51
|
+
videoRef: camera.videoRef,
|
|
52
|
+
canvasRef,
|
|
53
|
+
interval,
|
|
54
|
+
duration,
|
|
55
|
+
smileThreshold,
|
|
56
|
+
mouthOpenThreshold,
|
|
57
|
+
minFaceSize,
|
|
58
|
+
maxFaceSize,
|
|
59
|
+
smileCooldown,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const initializeCamera = async () => {
|
|
64
|
+
await camera.startCamera();
|
|
65
|
+
await camera.checkAgentSupport();
|
|
66
|
+
await faceCapture.initializeFaceLandmarker();
|
|
67
|
+
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
faceCapture.setupCanvas();
|
|
70
|
+
faceCapture.startDetectionLoop();
|
|
71
|
+
}, 500);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
initializeCamera();
|
|
75
|
+
|
|
76
|
+
return () => {
|
|
77
|
+
faceCapture.stopDetectionLoop();
|
|
78
|
+
camera.stopCamera();
|
|
79
|
+
faceCapture.cleanup();
|
|
80
|
+
};
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const navigation = navigationRef.current;
|
|
85
|
+
|
|
86
|
+
if (navigation && showNavigation) {
|
|
87
|
+
const handleNavigationBack = () => {
|
|
88
|
+
faceCapture.handleCancel();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleNavigationClose = () => {
|
|
92
|
+
faceCapture.handleClose();
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
navigation.addEventListener('navigation.back', handleNavigationBack);
|
|
96
|
+
navigation.addEventListener('navigation.close', handleNavigationClose);
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
navigation.removeEventListener('navigation.back', handleNavigationBack);
|
|
100
|
+
navigation.removeEventListener(
|
|
101
|
+
'navigation.close',
|
|
102
|
+
handleNavigationClose,
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}, [showNavigation]);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div className="smartselfie-capture">
|
|
111
|
+
{showNavigation && (
|
|
112
|
+
// @ts-ignore
|
|
113
|
+
<smileid-navigation ref={navigationRef} theme-color={themeColor} />
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
<CameraPreview
|
|
117
|
+
videoRef={camera.videoRef}
|
|
118
|
+
canvasRef={canvasRef}
|
|
119
|
+
facingMode={camera.facingMode}
|
|
120
|
+
multipleFaces={faceCapture.multipleFaces.value}
|
|
121
|
+
progress={
|
|
122
|
+
faceCapture.capturesTaken.value > 0
|
|
123
|
+
? faceCapture.capturesTaken.value / faceCapture.totalCaptures.value
|
|
124
|
+
: 0
|
|
125
|
+
}
|
|
126
|
+
interval={interval}
|
|
127
|
+
themeColor={themeColor}
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
<AlertDisplay alertTitle={faceCapture.alertTitle.value} />
|
|
131
|
+
|
|
132
|
+
{!faceCapture.isCapturing.value &&
|
|
133
|
+
!faceCapture.hasFinishedCapture.value && (
|
|
134
|
+
<CaptureControls
|
|
135
|
+
isCapturing={faceCapture.isCapturing.value}
|
|
136
|
+
hasFinishedCapture={faceCapture.hasFinishedCapture.value}
|
|
137
|
+
isReadyToCapture={faceCapture.isReadyToCapture.value}
|
|
138
|
+
allowAgentMode={allowAgentMode}
|
|
139
|
+
agentSupported={camera.agentSupported}
|
|
140
|
+
showAgentModeForTests={showAgentModeForTests}
|
|
141
|
+
facingMode={camera.facingMode}
|
|
142
|
+
themeColor={themeColor}
|
|
143
|
+
onStartCapture={faceCapture.startCapture}
|
|
144
|
+
onSwitchCamera={camera.switchCamera}
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{/* @ts-ignore */}
|
|
149
|
+
{!hideAttribution && <powered-by-smile-id />}
|
|
150
|
+
|
|
151
|
+
<style>{`
|
|
152
|
+
* {
|
|
153
|
+
box-sizing: border-box;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
button {
|
|
157
|
+
padding: 10px 20px;
|
|
158
|
+
background: ${themeColor || '#001096'};
|
|
159
|
+
color: white;
|
|
160
|
+
border: none;
|
|
161
|
+
border-radius: 4px;
|
|
162
|
+
cursor: pointer;
|
|
163
|
+
font-size: 16px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
button:disabled {
|
|
167
|
+
background: #ccc;
|
|
168
|
+
cursor: not-allowed;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
button.btn-primary {
|
|
172
|
+
background-color: ${themeColor || '#001096'};
|
|
173
|
+
border-radius: 2.5rem;
|
|
174
|
+
color: white;
|
|
175
|
+
border: none;
|
|
176
|
+
height: 3.125rem;
|
|
177
|
+
display: inline-block;
|
|
178
|
+
padding: 0.75rem 1.5rem;
|
|
179
|
+
text-align: center;
|
|
180
|
+
font-size: 1.125rem;
|
|
181
|
+
font-weight: 600;
|
|
182
|
+
font-family: "DM Sans", sans-serif;
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
button.btn-primary:hover {
|
|
187
|
+
background-color: #2d2b2a;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
button.btn-primary:disabled {
|
|
191
|
+
background-color: #666;
|
|
192
|
+
cursor: not-allowed;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.smartselfie-capture {
|
|
196
|
+
font-family: sans-serif;
|
|
197
|
+
max-width: 356px;
|
|
198
|
+
margin: 0 auto;
|
|
199
|
+
}
|
|
200
|
+
`}</style>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
if (!customElements.get('smartselfie-capture')) {
|
|
206
|
+
register(
|
|
207
|
+
SmartSelfieCapture,
|
|
208
|
+
'smartselfie-capture',
|
|
209
|
+
[
|
|
210
|
+
'interval',
|
|
211
|
+
'duration',
|
|
212
|
+
'theme-color',
|
|
213
|
+
'show-navigation',
|
|
214
|
+
'allow-agent-mode',
|
|
215
|
+
'show-agent-mode-for-tests',
|
|
216
|
+
'hide-attribution',
|
|
217
|
+
'disable-image-tests',
|
|
218
|
+
],
|
|
219
|
+
{ shadow: true },
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export default SmartSelfieCapture;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
interface AlertDisplayProps {
|
|
4
|
+
alertTitle: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const AlertDisplay: FunctionComponent<AlertDisplayProps> = ({
|
|
8
|
+
alertTitle,
|
|
9
|
+
}) =>
|
|
10
|
+
alertTitle ? (
|
|
11
|
+
<>
|
|
12
|
+
<div className="alert-message">
|
|
13
|
+
<div className="alert-title">{alertTitle}</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<style>{`
|
|
17
|
+
.alert-message {
|
|
18
|
+
margin-top: 1.5rem;
|
|
19
|
+
color: #000;
|
|
20
|
+
color: #151F72;
|
|
21
|
+
padding: 0.5rem 1.5rem;
|
|
22
|
+
border-radius: 4px;
|
|
23
|
+
text-align: start;
|
|
24
|
+
width: 100%;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.alert-title {
|
|
28
|
+
font-size: 16px;
|
|
29
|
+
font-weight: bold;
|
|
30
|
+
text-align: center;
|
|
31
|
+
}
|
|
32
|
+
`}</style>
|
|
33
|
+
</>
|
|
34
|
+
) : null;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { FunctionComponent, Ref } from 'preact';
|
|
2
|
+
import OvalProgress from '../OvalProgress';
|
|
3
|
+
|
|
4
|
+
interface CameraPreviewProps {
|
|
5
|
+
videoRef: Ref<HTMLVideoElement>;
|
|
6
|
+
canvasRef: Ref<HTMLCanvasElement>;
|
|
7
|
+
facingMode: 'user' | 'environment';
|
|
8
|
+
multipleFaces: boolean;
|
|
9
|
+
progress: number;
|
|
10
|
+
interval: number;
|
|
11
|
+
themeColor: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const CameraPreview: FunctionComponent<CameraPreviewProps> = ({
|
|
15
|
+
videoRef,
|
|
16
|
+
canvasRef,
|
|
17
|
+
facingMode,
|
|
18
|
+
multipleFaces,
|
|
19
|
+
progress,
|
|
20
|
+
interval,
|
|
21
|
+
themeColor,
|
|
22
|
+
}) => (
|
|
23
|
+
<>
|
|
24
|
+
<div className="camera-preview-container">
|
|
25
|
+
<div
|
|
26
|
+
className="video-wrapper"
|
|
27
|
+
style={{
|
|
28
|
+
clipPath: multipleFaces ? 'none' : 'url(#selfie-clip-path)',
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<div className="video-container">
|
|
32
|
+
<video
|
|
33
|
+
ref={videoRef}
|
|
34
|
+
autoPlay
|
|
35
|
+
playsInline
|
|
36
|
+
muted
|
|
37
|
+
className={`camera-video ${facingMode === 'user' ? 'mirror' : ''}`}
|
|
38
|
+
/>
|
|
39
|
+
<canvas
|
|
40
|
+
ref={canvasRef}
|
|
41
|
+
className={`camera-canvas ${facingMode === 'user' ? 'mirror' : ''}`}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
{!multipleFaces && (
|
|
46
|
+
<OvalProgress
|
|
47
|
+
progress={progress}
|
|
48
|
+
duration={interval}
|
|
49
|
+
themeColor={themeColor}
|
|
50
|
+
/>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<style>{`
|
|
55
|
+
.camera-preview-container {
|
|
56
|
+
position: relative;
|
|
57
|
+
width: 100%;
|
|
58
|
+
max-width: 300px;
|
|
59
|
+
margin: 0 auto;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.video-wrapper {
|
|
63
|
+
width: 100%;
|
|
64
|
+
max-width: 300px;
|
|
65
|
+
margin: 0 auto;
|
|
66
|
+
position: relative;
|
|
67
|
+
aspect-ratio: 3 / 4;
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.video-container {
|
|
72
|
+
position: absolute;
|
|
73
|
+
top: 0;
|
|
74
|
+
left: 0;
|
|
75
|
+
width: 100%;
|
|
76
|
+
height: 100%;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.camera-video,
|
|
80
|
+
.camera-canvas {
|
|
81
|
+
position: absolute;
|
|
82
|
+
top: 50%;
|
|
83
|
+
left: 50%;
|
|
84
|
+
transform: translate(-50%, -50%);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.camera-video.mirror,
|
|
88
|
+
.camera-canvas.mirror {
|
|
89
|
+
transform: translate(-50%, -50%) scaleX(-1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.camera-canvas {
|
|
93
|
+
pointer-events: none;
|
|
94
|
+
}
|
|
95
|
+
`}</style>
|
|
96
|
+
</>
|
|
97
|
+
);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
interface CaptureControlsProps {
|
|
4
|
+
isCapturing: boolean;
|
|
5
|
+
hasFinishedCapture: boolean;
|
|
6
|
+
isReadyToCapture: boolean;
|
|
7
|
+
allowAgentMode: boolean;
|
|
8
|
+
agentSupported: boolean;
|
|
9
|
+
showAgentModeForTests: boolean;
|
|
10
|
+
facingMode: 'user' | 'environment';
|
|
11
|
+
themeColor?: string;
|
|
12
|
+
onStartCapture: () => void;
|
|
13
|
+
onSwitchCamera: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const CaptureControls: FunctionComponent<CaptureControlsProps> = ({
|
|
17
|
+
isCapturing,
|
|
18
|
+
hasFinishedCapture,
|
|
19
|
+
isReadyToCapture,
|
|
20
|
+
allowAgentMode,
|
|
21
|
+
agentSupported,
|
|
22
|
+
showAgentModeForTests,
|
|
23
|
+
facingMode,
|
|
24
|
+
themeColor = '#001096',
|
|
25
|
+
onStartCapture,
|
|
26
|
+
onSwitchCamera,
|
|
27
|
+
}) => (
|
|
28
|
+
<>
|
|
29
|
+
<div className="controls">
|
|
30
|
+
<button
|
|
31
|
+
class="btn-primary"
|
|
32
|
+
onClick={onStartCapture}
|
|
33
|
+
disabled={isCapturing || hasFinishedCapture || !isReadyToCapture}
|
|
34
|
+
>
|
|
35
|
+
Start Capture
|
|
36
|
+
</button>
|
|
37
|
+
|
|
38
|
+
{allowAgentMode && (agentSupported || showAgentModeForTests) && (
|
|
39
|
+
<button
|
|
40
|
+
onClick={onSwitchCamera}
|
|
41
|
+
className="agent-mode-btn"
|
|
42
|
+
disabled={isCapturing || hasFinishedCapture}
|
|
43
|
+
>
|
|
44
|
+
{facingMode === 'user' ? 'Agent Mode Off' : 'Agent Mode On'}
|
|
45
|
+
</button>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<style>{`
|
|
50
|
+
.controls {
|
|
51
|
+
margin: 1.5rem 0;
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: 1rem;
|
|
55
|
+
justify-content: center;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.agent-mode-btn {
|
|
59
|
+
padding: 8px 16px;
|
|
60
|
+
font-size: 14px;
|
|
61
|
+
background: ${themeColor};
|
|
62
|
+
color: white;
|
|
63
|
+
border: none;
|
|
64
|
+
border-radius: 4px;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.agent-mode-btn:disabled {
|
|
69
|
+
background: #ccc;
|
|
70
|
+
cursor: not-allowed;
|
|
71
|
+
}
|
|
72
|
+
`}</style>
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type of image submitted in the job request
|
|
3
|
+
* @readonly
|
|
4
|
+
* @enum {number}
|
|
5
|
+
*/
|
|
6
|
+
export const ImageType = {
|
|
7
|
+
/** ID_CARD_BACK_IMAGE_BASE64 Base64 encoded back of ID card image (.jpg or .png) */
|
|
8
|
+
ID_CARD_BACK_IMAGE_BASE64: 7,
|
|
9
|
+
/** ID_CARD_BACK_IMAGE_FILE Back of ID card image in .png or .jpg file format */
|
|
10
|
+
ID_CARD_BACK_IMAGE_FILE: 5,
|
|
11
|
+
/** ID_CARD_IMAGE_BASE64 Base64 encoded ID card image (.png or .jpg) */
|
|
12
|
+
ID_CARD_IMAGE_BASE64: 3,
|
|
13
|
+
/** ID_CARD_IMAGE_FILE ID card image in .png or .jpg file format */
|
|
14
|
+
ID_CARD_IMAGE_FILE: 1,
|
|
15
|
+
/** LIVENESS_IMAGE_BASE64 Base64 encoded liveness image (.jpg or .png) */
|
|
16
|
+
LIVENESS_IMAGE_BASE64: 6,
|
|
17
|
+
/** LIVENESS_IMAGE_FILE Liveness image in .png or .jpg file format */
|
|
18
|
+
LIVENESS_IMAGE_FILE: 4,
|
|
19
|
+
/** SELFIE_IMAGE_BASE64 Base64 encoded selfie image (.png or .jpg) */
|
|
20
|
+
SELFIE_IMAGE_BASE64: 2,
|
|
21
|
+
/** SELFIE_IMAGE_FILE Selfie image in .png or .jpg file format */
|
|
22
|
+
SELFIE_IMAGE_FILE: 0,
|
|
23
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useRef, useState } from 'preact/hooks';
|
|
2
|
+
|
|
3
|
+
export const useCamera = () => {
|
|
4
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
5
|
+
const streamRef = useRef<MediaStream | null>(null);
|
|
6
|
+
const [facingMode, setFacingMode] = useState<'user' | 'environment'>('user');
|
|
7
|
+
const [agentSupported, setAgentSupported] = useState(false);
|
|
8
|
+
|
|
9
|
+
const startCamera = async () => {
|
|
10
|
+
try {
|
|
11
|
+
if (streamRef.current) {
|
|
12
|
+
streamRef.current.getTracks().forEach((track) => track.stop());
|
|
13
|
+
streamRef.current = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (videoRef.current) {
|
|
17
|
+
videoRef.current.srcObject = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
21
|
+
video: { facingMode },
|
|
22
|
+
});
|
|
23
|
+
streamRef.current = stream;
|
|
24
|
+
|
|
25
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
26
|
+
const videoDevice = devices.find(
|
|
27
|
+
(device) =>
|
|
28
|
+
device.kind === 'videoinput' &&
|
|
29
|
+
stream.getVideoTracks()[0].getSettings().deviceId === device.deviceId,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const smartCameraWeb = document.querySelector('smart-camera-web');
|
|
33
|
+
smartCameraWeb?.dispatchEvent(
|
|
34
|
+
new CustomEvent('metadata.camera-name', {
|
|
35
|
+
detail: { cameraName: videoDevice?.label },
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (videoRef.current) {
|
|
40
|
+
videoRef.current.srcObject = stream;
|
|
41
|
+
await videoRef.current.play();
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Failed to start camera:', error);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const switchCamera = async () => {
|
|
49
|
+
const newFacingMode = facingMode === 'user' ? 'environment' : 'user';
|
|
50
|
+
setFacingMode(newFacingMode);
|
|
51
|
+
|
|
52
|
+
if (streamRef.current) {
|
|
53
|
+
streamRef.current.getTracks().forEach((track) => track.stop());
|
|
54
|
+
streamRef.current = null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await startCamera();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const checkAgentSupport = async () => {
|
|
61
|
+
try {
|
|
62
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
63
|
+
const videoDevices = devices.filter(
|
|
64
|
+
(device) => device.kind === 'videoinput',
|
|
65
|
+
);
|
|
66
|
+
setAgentSupported(videoDevices.length > 1);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
setAgentSupported(false);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const stopCamera = () => {
|
|
73
|
+
if (streamRef.current) {
|
|
74
|
+
streamRef.current.getTracks().forEach((track) => track.stop());
|
|
75
|
+
streamRef.current = null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (videoRef.current) {
|
|
79
|
+
videoRef.current.srcObject = null;
|
|
80
|
+
videoRef.current.load();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
videoRef,
|
|
86
|
+
streamRef,
|
|
87
|
+
facingMode,
|
|
88
|
+
agentSupported,
|
|
89
|
+
startCamera,
|
|
90
|
+
switchCamera,
|
|
91
|
+
checkAgentSupport,
|
|
92
|
+
stopCamera,
|
|
93
|
+
};
|
|
94
|
+
};
|