UNPKG

onfido-sdk-ui

Version:

JavaScript SDK view layer for Onfido identity verification

459 lines (416 loc) 11.9 kB
import type { LocaleConfig, SupportedLanguages } from '~types/locales' import type { DocumentTypes, DocumentTypeConfig, StepConfig, StepTypes, } from '~types/steps' import type { ServerRegions, SdkOptions } from '~types/sdk' import type { UICustomizationOptions } from '~types/ui-customisation-options' import customUIConfig from './custom-ui-config.json' import testDarkCobrandLogo from './assets/onfido-logo.svg' import testLightCobrandLogo from './assets/onfido-logo-light.svg' type StringifiedBoolean = 'true' | 'false' type DecoupleResponseOptions = 'success' | 'error' | 'onfido' export type QueryParams = { countryCode?: StringifiedBoolean disableAnalytics?: StringifiedBoolean forceCrossDevice?: StringifiedBoolean hideOnfidoLogo?: StringifiedBoolean language?: 'customTranslations' | SupportedLanguages customWelcomeScreenCopy?: StringifiedBoolean link_id?: string liveness?: StringifiedBoolean multiDocWithInvalidPresetCountry?: StringifiedBoolean multiDocWithPresetCountry?: StringifiedBoolean multiDocWithBooleanValues?: StringifiedBoolean noCompleteStep?: StringifiedBoolean oneDoc?: DocumentTypes oneDocWithCountrySelection?: StringifiedBoolean oneDocWithPresetCountry?: StringifiedBoolean poa?: StringifiedBoolean region?: string shouldCloseOnOverlayClick?: StringifiedBoolean showCobrand?: StringifiedBoolean showLogoCobrand?: StringifiedBoolean showUserConsent?: StringifiedBoolean showAuth?: StringifiedBoolean smsNumber?: StringifiedBoolean snapshotInterval?: string uploadFallback?: StringifiedBoolean useHistory?: StringifiedBoolean useLiveDocumentCapture?: StringifiedBoolean useMemoryHistory?: StringifiedBoolean useModal?: StringifiedBoolean useMultipleSelfieCapture?: StringifiedBoolean useUploader?: StringifiedBoolean useWebcam?: StringifiedBoolean customisedUI?: StringifiedBoolean useCustomizedApiRequests?: StringifiedBoolean decoupleResponse?: DecoupleResponseOptions photoCaptureFallback?: StringifiedBoolean } export type CheckData = { applicantId?: string sdkFlowCompleted: boolean } export type UIConfigs = { darkBackground: boolean iframeWidth: string iframeHeight: string tearDown: boolean } const SAMPLE_LOCALE: LocaleConfig = { locale: 'en', phrases: { 'welcome.title': 'My custom title' }, mobilePhrases: { 'capture.driving_licence.back.instructions': 'Custom instructions', }, } export const queryParamToValueString = window.location.search .slice(1) .split('&') .reduce((acc: QueryParams, cur: string) => { const [key, value] = cur.split('=') return { ...acc, [key]: value } }, {}) const getPreselectedDocumentTypes = (): Partial< Record<DocumentTypes, DocumentTypeConfig> > => { const preselectedDocumentType = queryParamToValueString.oneDoc if (preselectedDocumentType) { return { [preselectedDocumentType]: true, } } if (queryParamToValueString.oneDocWithCountrySelection === 'true') { return { driving_licence: true, } } if (queryParamToValueString.oneDocWithPresetCountry === 'true') { return { driving_licence: { country: 'ESP', }, } } if (queryParamToValueString.multiDocWithPresetCountry === 'true') { return { driving_licence: { country: 'ESP', }, national_identity_card: { country: 'MYS', }, residence_permit: { country: null, }, } } if (queryParamToValueString.multiDocWithInvalidPresetCountry === 'true') { return { driving_licence: { country: 'ES', }, national_identity_card: { country: 'XYZ', }, } } if (queryParamToValueString.multiDocWithBooleanValues === 'true') { return { driving_licence: true, national_identity_card: true, } } return {} } export const getInitSdkOptions = (): SdkOptions => { const linkId = queryParamToValueString.link_id as string if (linkId) { return { mobileFlow: true, roomId: linkId.substring(2), } } const language = queryParamToValueString.language === 'customTranslations' ? SAMPLE_LOCALE : queryParamToValueString.language const steps: Array<StepConfig> = [] if (queryParamToValueString.customWelcomeScreenCopy === 'true') { steps.push({ type: 'welcome', options: { title: 'Open your new bank account', descriptions: [ 'To open a bank account, we will need to verify your identity.', 'It will only take a couple of minutes.', ], nextButton: 'Verify Identity', }, }) } else { steps.push({ type: 'welcome' }) } if (queryParamToValueString.showAuth === 'true') { steps.push({ type: 'auth', options: { retries: 10 } }) } if (queryParamToValueString.showUserConsent === 'true') { steps.push({ type: 'userConsent' }) } if (queryParamToValueString.poa === 'true') { steps.push({ type: 'poa' }) } steps.push({ type: 'document', options: { useLiveDocumentCapture: queryParamToValueString.useLiveDocumentCapture === 'true', uploadFallback: queryParamToValueString.uploadFallback !== 'false', useWebcam: queryParamToValueString.useWebcam === 'true', documentTypes: getPreselectedDocumentTypes(), showCountrySelection: queryParamToValueString.oneDocWithCountrySelection === 'true', forceCrossDevice: queryParamToValueString.forceCrossDevice === 'true', }, }) steps.push({ type: 'face', options: { requestedVariant: queryParamToValueString.liveness === 'true' ? 'video' : 'standard', useUploader: queryParamToValueString.useUploader === 'true', uploadFallback: queryParamToValueString.uploadFallback !== 'false', useMultipleSelfieCapture: queryParamToValueString.useMultipleSelfieCapture !== 'false', photoCaptureFallback: queryParamToValueString.photoCaptureFallback !== 'false', }, }) if (queryParamToValueString.noCompleteStep !== 'true') { steps.push({ type: 'complete' }) } const smsNumberCountryCode = queryParamToValueString.countryCode ? { smsNumberCountryCode: queryParamToValueString.countryCode } : {} const hideOnfidoLogo = queryParamToValueString.hideOnfidoLogo === 'true' const cobrand = queryParamToValueString.showCobrand === 'true' ? { text: 'Planet Express, Incorporated' } : undefined const logoCobrand = queryParamToValueString.showLogoCobrand === 'true' ? { lightLogoSrc: testLightCobrandLogo, darkLogoSrc: testDarkCobrandLogo } : undefined const useCustomizedApiRequests = queryParamToValueString.useCustomizedApiRequests === 'true' let decoupleCallbacks = {} if (queryParamToValueString.decoupleResponse === 'success') { const successResponse = Promise.resolve({ onfidoSuccessResponse: { id: '123-456-789', }, }) decoupleCallbacks = { onSubmitDocument: () => successResponse, onSubmitSelfie: () => successResponse, onSubmitVideo: () => successResponse, } } else if (queryParamToValueString.decoupleResponse === 'error') { const errorResponse = { status: 422, response: JSON.stringify({ error: { message: 'There was a validation error on this request', type: 'validation_error', fields: { detect_glare: ['glare found in image'] }, }, }), } decoupleCallbacks = { onSubmitDocument: () => Promise.reject(errorResponse), onSubmitSelfie: () => Promise.reject(errorResponse), onSubmitVideo: () => Promise.reject(errorResponse), } } else if (queryParamToValueString.decoupleResponse === 'onfido') { const response = Promise.resolve({ continueWithOnfidoSubmission: true }) decoupleCallbacks = { onSubmitDocument: () => response, onSubmitSelfie: () => response, onSubmitVideo: () => response, } } const customUI = queryParamToValueString.customisedUI === 'true' ? customUIConfig : undefined return { useModal: queryParamToValueString.useModal === 'true', shouldCloseOnOverlayClick: queryParamToValueString.shouldCloseOnOverlayClick !== 'true', language, disableAnalytics: queryParamToValueString.disableAnalytics === 'true', useMemoryHistory: queryParamToValueString.useMemoryHistory === 'true', steps, mobileFlow: false, userDetails: { smsNumber: queryParamToValueString.smsNumber, }, enterpriseFeatures: { hideOnfidoLogo, cobrand, logoCobrand, useCustomizedApiRequests, ...decoupleCallbacks, }, customUI: customUI as UICustomizationOptions, ...smsNumberCountryCode, } } export const commonSteps: Record<string, Array<StepTypes | StepConfig>> = { standard: [], liveness: [ 'welcome', 'document', { type: 'face', options: { requestedVariant: 'video' }, }, 'complete', ], poa: ['welcome', 'poa', 'complete'], 'no welcome': ['document', 'face', 'complete'], 'no complete': ['welcome', 'document', 'face'], 'upload fallback': [ 'welcome', { type: 'document', options: { useWebcam: false, }, }, { type: 'face', options: { uploadFallback: true, }, }, 'complete', ], 'document autocapture (BETA)': [ 'welcome', { type: 'document', options: { useWebcam: true, }, }, 'face', 'complete', ], 'document live capture (BETA)': [ 'welcome', { type: 'document', options: { useLiveDocumentCapture: true, }, }, 'face', 'complete', ], 'force cross device (docs)': [ 'welcome', { type: 'document', options: { forceCrossDevice: true, }, }, 'face', 'complete', ], 'no upload fallback': [ 'welcome', { type: 'document', options: { useWebcam: false, }, }, { type: 'face', options: { uploadFallback: false, }, }, 'complete', ], 'no snapshot': [ 'welcome', 'document', { type: 'face', options: { useMultipleSelfieCapture: false, }, }, 'complete', ], } export const commonLanguages: Record< string, SupportedLanguages | LocaleConfig > = { en: 'en', es: 'es', de: 'de', fr: 'fr', custom: { phrases: { 'welcome.title': 'My custom title' }, mobilePhrases: { 'capture.driving_licence.back.instructions': 'Custom instructions', }, }, } export const commonRegions: ServerRegions[] = ['EU', 'US', 'CA'] export const getTokenFactoryUrl = (region: ServerRegions): string => { if (region === 'US' && process.env.US_JWT_FACTORY) { return process.env.US_JWT_FACTORY } if (region === 'CA' && process.env.CA_JWT_FACTORY) { return process.env.CA_JWT_FACTORY } if (region === 'EU' && process.env.JWT_FACTORY) { return process.env.JWT_FACTORY } throw new Error('No JWT_FACTORY env provided') } export const getToken = ( hasPreview: boolean, url: string, eventEmitter: MessagePort | undefined, onSuccess: (message: string) => void ): void => { const request = new XMLHttpRequest() request.open('GET', url, true) request.setRequestHeader( 'Authorization', `BASIC ${process.env.SDK_TOKEN_FACTORY_SECRET}` ) request.onload = function () { if (request.status >= 200 && request.status < 400) { const data = JSON.parse(request.responseText) if (hasPreview && eventEmitter) { eventEmitter.postMessage({ type: 'UPDATE_CHECK_DATA', payload: { applicantId: data.applicant_id, }, }) } onSuccess(data.message) } } request.send() }