onfido-sdk-ui
Version:
JavaScript SDK view layer for Onfido identity verification
192 lines (170 loc) • 5.23 kB
JavaScript
import { h, Component } from 'preact'
import { appendToTracking } from '../../Tracker'
import Selfie from '../Photo/Selfie'
import FaceVideo from '../FaceVideo'
import Uploader from '../Uploader'
import PageTitle from '../PageTitle'
import withCrossDeviceWhenNoCamera from './withCrossDeviceWhenNoCamera'
import GenericError from '../GenericError'
import FallbackButton from '../Button/FallbackButton'
import CustomFileInput from '../CustomFileInput'
import {
isDesktop,
addDeviceRelatedProperties,
getUnsupportedMobileBrowserError,
} from '~utils'
import { compose } from '~utils/func'
import { randomId } from '~utils/string'
import { validateFile } from '~utils/file'
import { getInactiveError } from '~utils/inactiveError'
import { localised } from '../../locales'
import withTheme from '../Theme'
import theme from '../Theme/style.scss'
import style from './style.scss'
const defaultPayload = {
method: 'face',
variant: 'standard',
side: null,
}
const WrappedError = withTheme(GenericError)
class Face extends Component {
static defaultProps = {
useUploader: false,
requestedVariant: 'standard',
uploadFallback: true,
useMultipleSelfieCapture: true,
photoCaptureFallback: true,
snapshotInterval: 500,
}
handleCapture = (payload) => {
const { actions, nextStep, mobileFlow } = this.props
const id = randomId()
const faceCaptureData = {
...defaultPayload,
...payload,
sdkMetadata: addDeviceRelatedProperties(payload.sdkMetadata, mobileFlow),
id,
}
actions.createCapture(faceCaptureData)
nextStep()
}
handleVideoCapture = (payload) =>
this.handleCapture({ ...payload, variant: 'video' })
handleUpload = (blob, imageResizeInfo) =>
this.handleCapture({
blob,
sdkMetadata: { captureMethod: 'html5', imageResizeInfo },
})
handleError = (error) => {
this.props.triggerOnError(error)
this.props.actions.deleteCapture({ method: 'face' })
}
handleFallbackClick = (callback) => {
this.props.changeFlowTo('crossDeviceSteps')
callback()
}
handleFileSelected = (file) =>
validateFile(file, this.handleUpload, this.handleError)
renderUploadFallback = (text) => (
<CustomFileInput
className={theme.errorFallbackButton}
onChange={this.handleFileSelected}
accept="image/*"
capture="user"
>
{text}
</CustomFileInput>
)
renderCrossDeviceFallback = (text, callback) => (
<FallbackButton
text={text}
onClick={() => this.handleFallbackClick(callback)}
/>
)
isUploadFallbackDisabled = () => !isDesktop && !this.props.uploadFallback
render() {
const {
hasCamera,
requestedVariant,
translate,
useMultipleSelfieCapture,
snapshotInterval,
uploadFallback,
photoCaptureFallback,
} = this.props
const title = translate('selfie_capture.title')
const props = {
onError: this.handleError,
...this.props,
}
const cameraProps = {
renderTitle: <PageTitle title={title} smaller />,
cameraClassName: style.faceContainer,
renderFallback: isDesktop
? this.renderCrossDeviceFallback
: this.renderUploadFallback,
inactiveError: getInactiveError(this.isUploadFallbackDisabled()),
isUploadFallbackDisabled: this.isUploadFallbackDisabled(),
...props,
}
// `hasCamera` is `true`/`false`, or `null` if the logic is still loading
// its value.
// We don't want to render while it's loading, otherwise we'll flicker
// when we finally do get its value
if (hasCamera === null) return
const isVideoCompatible = window.MediaRecorder != null
if (hasCamera && (isVideoCompatible || photoCaptureFallback)) {
const ariaLabelForSelfieCameraView = translate(
'selfie_capture.frame_accessibility'
)
if (requestedVariant === 'video') {
return (
<FaceVideo
{...cameraProps}
onVideoCapture={this.handleVideoCapture}
/>
)
}
if (!this.props.useUploader && photoCaptureFallback) {
return (
<Selfie
{...cameraProps}
onCapture={this.handleCapture}
useMultipleSelfieCapture={useMultipleSelfieCapture}
snapshotInterval={snapshotInterval}
ariaLabel={ariaLabelForSelfieCameraView}
/>
)
}
}
if (
!isVideoCompatible &&
!photoCaptureFallback &&
requestedVariant === 'video'
) {
return (
<WrappedError
disableNavigation={true}
error={{ name: getUnsupportedMobileBrowserError() }}
/>
)
}
if ((this.props.useUploader || hasCamera === false) && uploadFallback) {
return (
<Uploader
{...props}
uploadType="face"
onUpload={this.handleUpload}
title={translate('photo_upload.title_selfie') || title}
instructions={translate('photo_upload.body_selfie')}
/>
)
}
return <GenericError error={{ name: 'INTERRUPTED_FLOW_ERROR' }} />
}
}
export default compose(
appendToTracking,
localised,
withCrossDeviceWhenNoCamera
)(Face)