import { DetectionType, FeedbackState, FeedbackStateCallback, ImageFeedbackCallback, ITruliooCameraConfig, ITruliooCaptureResponse, ITruliooImageFeedback, ITruliooManualCaptureResponse, ITruliooVerifyResponse, TruliooVerifyFeedback, VerificationStatus, } from './types' import {TruliooCamera} from './TruliooCamera' import uuid from 'react-native-uuid' import {NativeEventEmitter, NativeModules} from 'react-native' import {HandledError} from './types/TruliooError' const {TruliooCaptureSdkModule} = NativeModules export class TruliooCapture implements ITruliooCapture { private eventEmitter = new NativeEventEmitter(TruliooCaptureSdkModule) private EVENT = { ON_IMAGE_FEEDBACK: 'onImageFeedbackResult', ON_FEEDBACK_STATE: 'onFeedbackStateResult', } private allEventNames: string[] = [] captureLatestFrame(cameraId: string): Promise { return new Promise((resolve, reject) => { TruliooCaptureSdkModule.captureLatestFrame(cameraId) .then((capture: string) => { const parseResponse = JSON.parse(capture) as ITruliooManualCaptureResponse const manualCaptureResponse = { ...parseResponse, verifyImage: async () => { return this._verifyWithImageId(parseResponse.imageId) }, acceptImage: async () => { return this._acceptWithImageId(parseResponse.imageId) }, } as ITruliooManualCaptureResponse resolve(manualCaptureResponse) }) .catch((error: unknown) => { reject(this._errorMapping(error)) }) }) } getCameraComponent(config?: ITruliooCameraConfig): TruliooCamera { const defaultConfig: ITruliooCameraConfig = { detectionType: DetectionType.DOCUMENT, } const mergedConfig = {...defaultConfig, ...config} const cameraId = uuid.v4().toString() const internalConfig = {...mergedConfig, cameraId} TruliooCaptureSdkModule.createCameraComponent(JSON.stringify(internalConfig)) return new TruliooCamera(cameraId, mergedConfig.detectionType) } initialize(shortCode: string, previewMode?: boolean): Promise { const enablePreview = previewMode ?? false return new Promise((resolve, reject) => { TruliooCaptureSdkModule.initialize(shortCode, enablePreview) .then((success: string) => { resolve(success) }) .catch((error: unknown) => { reject(this._errorMapping(error)) }) }) } onFeedbackState(feedback: FeedbackStateCallback): void { const eventName = `${this.EVENT.ON_FEEDBACK_STATE}` this._removeEventListeners(eventName) this._addEventListener(eventName, (feedbackState: string) => { feedback(this._mapFeedbackState(feedbackState)) }) TruliooCaptureSdkModule.onFeedbackState(eventName) } startFeedback(cameraId: string, imageFeedback?: ImageFeedbackCallback): Promise { if (imageFeedback !== undefined) { return this._startFeedbackWithImageFeedback(cameraId, imageFeedback) } else { return this._startFeedbackWithPromise(cameraId) } } startFeedbackWithVerify(cameraId: string): Promise { return new Promise((resolve, reject) => { TruliooCaptureSdkModule.startFeedbackPromise(cameraId) .then((response: string) => { const parseResponse = JSON.parse(response) as ITruliooCaptureResponse parseResponse.detectionType = this._mapDetectionType(parseResponse.detectionType.toString()) const captureResponse = { ...parseResponse, verifyImage: async () => { return this._verifyWithImageId(parseResponse.imageId) }, acceptImage: async () => { return this._acceptWithImageId(parseResponse.imageId) }, } as ITruliooCaptureResponse resolve(captureResponse) }) .catch((error: unknown) => { reject(this._errorMapping(error)) }) }) } stopFeedback(cameraId: string): Promise { return new Promise((resolve, reject) => { this._removeEventListeners() TruliooCaptureSdkModule.stopFeedback(cameraId) .then((success: boolean) => { resolve(success) }) .catch((error: unknown) => { reject(this._errorMapping(error)) }) }) } submitTransaction(): Promise { return new Promise((resolve, reject) => { TruliooCaptureSdkModule.submitTransaction() .then((success: boolean) => { resolve(success) }) .catch((error: unknown) => { reject(this._errorMapping(error)) }) }) } private _mapFeedbackState(str: string): FeedbackState { switch (str.toUpperCase()) { case 'NONE': return FeedbackState.NONE case 'CAPTURING': return FeedbackState.CAPTURING case 'BLUR': return FeedbackState.BLUR case 'SUCCESS': return FeedbackState.SUCCESS case 'TOO_FAR': return FeedbackState.TOO_FAR case 'TOO_CLOSE': return FeedbackState.TOO_CLOSE default: return FeedbackState.NONE } } private _mapDetectionType(str: string): DetectionType { switch (str.toUpperCase()) { case 'DOCUMENT': return DetectionType.DOCUMENT case 'PASSPORT': return DetectionType.PASSPORT case 'BIOMETRIC_SELFIE': return DetectionType.BIOMETRIC_SELFIE default: return DetectionType.NO_DETECTION } } private _mapVerificationStatus(str: string): VerificationStatus { switch (str.toUpperCase()) { case 'CAN_BE_PROCESSED': return VerificationStatus.CAN_BE_PROCESSED case 'EXPIRED_DOCUMENT': return VerificationStatus.EXPIRED_DOCUMENT case 'UNSUPPORTED_DOCUMENT': return VerificationStatus.UNSUPPORTED_DOCUMENT case 'CANNOT_BE_CLASSIFIED': return VerificationStatus.CANNOT_BE_CLASSIFIED case 'GLARE': return VerificationStatus.GLARE case 'BLUR': return VerificationStatus.BLUR case 'SKEWED': return VerificationStatus.SKEWED default: return VerificationStatus.ERROR } } private _startFeedbackWithImageFeedback(cameraId: string, imageFeedback: ImageFeedbackCallback): Promise { const eventName = `${this.EVENT.ON_IMAGE_FEEDBACK}-${cameraId}` this._removeEventListeners(eventName) this._addEventListener(eventName, (feedback: string) => { const feedbackResponse = JSON.parse(feedback) as ITruliooImageFeedback feedbackResponse.detectionType = this._mapDetectionType(feedbackResponse.detectionType.toString()) imageFeedback(feedbackResponse) }) TruliooCaptureSdkModule.startFeedbackCallback(cameraId, eventName) return Promise.resolve({} as ITruliooCaptureResponse) } private _startFeedbackWithPromise(cameraId: string): Promise { return new Promise((resolve, reject) => { TruliooCaptureSdkModule.startFeedbackPromise(cameraId) .then((response: string) => { const captureResponse = JSON.parse(response) as ITruliooCaptureResponse captureResponse.detectionType = this._mapDetectionType(captureResponse.detectionType.toString()) this._acceptWithImageId(captureResponse.imageId) resolve(captureResponse) }) .catch((error: unknown) => { reject(this._errorMapping(error)) }) }) } private _verifyWithImageId(imageId: string): Promise { return new Promise((resolve, reject) => { TruliooCaptureSdkModule.verifyImage(imageId) .then((response: string) => { const verifyResponse = JSON.parse(response) as ITruliooVerifyResponse verifyResponse.verificationStatus = this._mapVerificationStatus(verifyResponse.verificationStatus.toString()) resolve(verifyResponse) }) .catch((error: unknown) => { reject(this._errorMapping(error)) }) }) } private _acceptWithImageId(imageId: string): Promise { return new Promise((resolve, reject) => { TruliooCaptureSdkModule.acceptImage(imageId) .then((success: boolean) => { resolve(success) }) .catch((error: unknown) => { reject(this._errorMapping(error)) }) }) } private _removeEventListeners = (eventName?: string): void => { if (eventName) { this.eventEmitter.removeAllListeners(eventName) this.allEventNames = this.allEventNames.filter((name) => name !== eventName) } else { this.allEventNames.forEach((name) => { this.eventEmitter.removeAllListeners(name) }) this.allEventNames = [] } } private _addEventListener(eventName: string, callback: (event: any) => void) { this.eventEmitter.addListener(eventName, callback) if (!this.allEventNames.includes(eventName)) { this.allEventNames.push(eventName) } } private _errorMapping(error: unknown): unknown { if (typeof error === 'object' && error !== null && 'code' in error) { const errorWithCode = error as {code: string} switch (errorWithCode.code) { case '1337': return new HandledError.FeedbackStopped() default: return error } } return error } } export interface ITruliooCapture { /** * Authorizes and configures the SDK for a capture transaction * * @param shortCode Created from the Trulioo API endpoint: `/customer/handoff` * @param previewMode Will use preview environment if set to true with default value of false. * * @returns The current Transaction ID on success otherwise rejected with the reason */ initialize: (shortCode: string, previewMode?: boolean) => Promise /** * This component uses the configuration that is passed in to determine the shape of the capture and detection area. * When rendered it will provide the camera feed to the screen but will have no capture feedback until {@link startFeedback} is called * * @returns A React Native Component that Renders a Camera View to the Screen and can provide feedback on the documents in frame */ getCameraComponent(config?: ITruliooCameraConfig): TruliooCamera /** * startFeedback will begin taking the frames from a rendered camera, and providing the capture feedback on them. An acceptable image can be determined based on the {@link ITruliooImageFeedback} then the resulting {@link ITruliooImageFeedback.imageId} can be provided to {@link captureLatestFrame} * * @param cameraId The id of the returned camera component from {@link getCameraComponent} * @param imageFeedback * If this is not included, resolve on the first image that satisfies the {@link ITruliooImageFeedback.hasAcceptableQuality}. * If this is included, the feedback will need to be both stopped with {@link stopFeedback} and an image accepted with {@link captureLatestFrame} * * @returns If `imageFeedback` is not provided then the promise will resolve with the first image that meets the criteria for verification * * @throws {FeedbackStopped} If {@link stopFeedback} or {@link captureLatestFrame} function is called during the feedback process. */ startFeedback: (cameraId: string, imageFeedback?: ImageFeedbackCallback) => Promise /** * startFeedbackWithVerify will begin taking the frames from a rendered camera, and providing the capture feedback on them. An acceptable image can be determined based on the {@link ITruliooImageFeedback} then the resulting response {@link ITruliooCaptureResponse.verifyImage} * function can be called for post capture feedback or to {@link ITruliooCaptureResponse.acceptImage} for submit verification. * * @param cameraId The id of the returned camera component from {@link getCameraComponent} that has started feedback * * @returns The ITruliooCaptureResponse of the first image that meets the criteria for verification. * * @throws {FeedbackStopped} If {@link stopFeedback} or {@link captureLatestFrame} function is called during the feedback process. */ startFeedbackWithVerify: (cameraId: string) => Promise /** * * To stop any ongoing auto capture feedback from {@link startFeedbackWithVerify} or {@link startFeedback}. Note that this will result in both * {@link startFeedbackWithVerify} or {@link startFeedback} to reject with the specific {@link FeedbackStopped} error. * * @param cameraId The id of the returned camera component from {@link getCameraComponent} that has started feedback * * @returns true if the camera was stopped, false if it was not yet started */ stopFeedback: (cameraId: string) => Promise /** * This can be used to help expose the state of capture as feedback to the UI. The callback will only be executed when the state changes internal to the SDK */ onFeedbackState: (feedback: FeedbackStateCallback) => void /** * To manually capture the latest image from a running camera. When captureLatestFrame is called, any ongoing auto capture process from {@link startFeedbackWithVerify} * or {@link startFeedback} will be stopped. * * @param cameraId The id of the camera component from {@link getCameraComponent} that has been rendered and running * * @returns The ITruliooManualCaptureResponse of the captured image. */ captureLatestFrame: (cameraId: string) => Promise /** * Once all needed documents for a transaction have been accepted, this must be called to begin the verification process on the provided information * * @returns true with a success otherwise rejected with the reason */ submitTransaction: () => Promise }