UNPKG

@microblink/photopay-in-browser-sdk

Version:

A simple payment barcode scanning library for WebAssembly-enabled browsers.

378 lines (377 loc) 15.2 kB
/** * Copyright (c) Microblink Ltd. All rights reserved. */ import * as PhotoPaySDK from '@microblink/photopay-in-browser-sdk'; import { AvailableRecognizers, CameraExperience, EventReady, ImageRecognitionType, RecognitionStatus, SDKError } from './data-structures'; import * as ErrorTypes from './error-structures'; const _IS_IMAGE_CAPTURE = false; export async function getCameraDevices() { const devices = await PhotoPaySDK.getCameraDevices(); const allDevices = devices.frontCameras.concat(devices.backCameras); const finalEntries = allDevices.map((el) => { return { prettyName: el.label, details: el }; }); return finalEntries; } export class SdkService { constructor() { this.cancelInitiatedFromOutside = false; this.showOverlay = false; this.eventEmitter$ = document.createElement('a'); } delete() { var _a; (_a = this.sdk) === null || _a === void 0 ? void 0 : _a.delete(); } initialize(licenseKey, sdkSettings) { const loadSettings = new PhotoPaySDK.WasmSDKLoadSettings(licenseKey); loadSettings.allowHelloMessage = sdkSettings.allowHelloMessage; loadSettings.engineLocation = sdkSettings.engineLocation; if (sdkSettings.wasmType) { loadSettings.wasmType = sdkSettings.wasmType; } return new Promise((resolve) => { PhotoPaySDK.loadWasmModule(loadSettings) .then((sdk) => { this.sdk = sdk; this.showOverlay = sdk.showOverlay; resolve(new EventReady(this.sdk)); }) .catch(error => { resolve(new SDKError(ErrorTypes.componentErrors.sdkLoadFailed, error)); }); }); } checkRecognizers(recognizers) { if (!recognizers || !recognizers.length) { return { status: false, message: 'There are no provided recognizers!' }; } for (const recognizer of recognizers) { if (!this.isRecognizerAvailable(recognizer)) { return { status: false, message: `Recognizer "${recognizer}" doesn't exist!` }; } } return { status: true }; } getDesiredCameraExperience(_recognizers = [], _recognizerOptions = {}) { return CameraExperience.Barcode; } async scanFromCamera(configuration, eventCallback) { var _a, _b; eventCallback({ status: RecognitionStatus.Preparing }); this.cancelInitiatedFromOutside = false; // Prepare terminate mechanism before recognizer and runner instances are created this.eventEmitter$.addEventListener('terminate', async () => { var _a, _b, _c, _d, _e, _f, _g, _h; (_b = (_a = this.videoRecognizer) === null || _a === void 0 ? void 0 : _a.cancelRecognition) === null || _b === void 0 ? void 0 : _b.call(_a); window.setTimeout(() => { var _a, _b; return (_b = (_a = this.videoRecognizer) === null || _a === void 0 ? void 0 : _a.releaseVideoFeed) === null || _b === void 0 ? void 0 : _b.call(_a); }, 1); if (recognizerRunner) { try { await recognizerRunner.delete(); } catch (error) { // Psst, this error should not happen. } } for (const recognizer of recognizers) { if (!recognizer) { continue; } if (((_c = recognizer.recognizer) === null || _c === void 0 ? void 0 : _c.objectHandle) > -1) { (_e = (_d = recognizer.recognizer).delete) === null || _e === void 0 ? void 0 : _e.call(_d); } if (((_f = recognizer.successFrame) === null || _f === void 0 ? void 0 : _f.objectHandle) > -1) { (_h = (_g = recognizer.successFrame).delete) === null || _h === void 0 ? void 0 : _h.call(_g); } } }); // Prepare recognizers and runner const recognizers = await this.createRecognizers(configuration.recognizers, configuration.recognizerOptions, configuration.successFrame); const recognizerRunner = await this.createRecognizerRunner(recognizers, eventCallback); try { this.videoRecognizer = await PhotoPaySDK.VideoRecognizer.createVideoRecognizerFromCameraStream(configuration.cameraFeed, recognizerRunner, configuration.cameraId); eventCallback({ status: RecognitionStatus.Ready }); await this.videoRecognizer.setVideoRecognitionMode(PhotoPaySDK.VideoRecognitionMode.Recognition); this.videoRecognizer.startRecognition(async (recognitionState) => { this.videoRecognizer.pauseRecognition(); eventCallback({ status: RecognitionStatus.Processing }); if (recognitionState !== PhotoPaySDK.RecognizerResultState.Empty) { for (const recognizer of recognizers) { const results = await recognizer.recognizer.getResult(); this.recognizerName = recognizer.recognizer.recognizerName; if (!results || results.state === PhotoPaySDK.RecognizerResultState.Empty) { eventCallback({ status: RecognitionStatus.EmptyResultState, data: { initiatedByUser: this.cancelInitiatedFromOutside, recognizerName: this.recognizerName } }); } else { const recognitionResults = { recognizer: results, recognizerName: this.recognizerName }; if (recognizer.successFrame) { const successFrameResults = await recognizer.successFrame.getResult(); if (successFrameResults && successFrameResults.state !== PhotoPaySDK.RecognizerResultState.Empty) { recognitionResults.successFrame = successFrameResults; } } recognitionResults.imageCapture = _IS_IMAGE_CAPTURE; const scanData = { result: recognitionResults, initiatedByUser: this.cancelInitiatedFromOutside, imageCapture: _IS_IMAGE_CAPTURE }; eventCallback({ status: RecognitionStatus.ScanSuccessful, data: scanData }); break; } } } else { eventCallback({ status: RecognitionStatus.EmptyResultState, data: { initiatedByUser: this.cancelInitiatedFromOutside, recognizerName: '' } }); } window.setTimeout(() => void this.cancelRecognition(), 400); }, configuration.recognitionTimeout); } catch (error) { if (error && ((_a = error.details) === null || _a === void 0 ? void 0 : _a.reason)) { const reason = (_b = error.details) === null || _b === void 0 ? void 0 : _b.reason; switch (reason) { case PhotoPaySDK.NotSupportedReason.MediaDevicesNotSupported: eventCallback({ status: RecognitionStatus.NoSupportForMediaDevices }); break; case PhotoPaySDK.NotSupportedReason.CameraNotFound: eventCallback({ status: RecognitionStatus.CameraNotFound }); break; case PhotoPaySDK.NotSupportedReason.CameraNotAllowed: eventCallback({ status: RecognitionStatus.CameraNotAllowed }); break; case PhotoPaySDK.NotSupportedReason.CameraInUse: eventCallback({ status: RecognitionStatus.CameraInUse }); break; default: eventCallback({ status: RecognitionStatus.UnableToAccessCamera }); } console.warn('VideoRecognizerError', error.name, '[' + reason + ']:', error.message); } else { eventCallback({ status: RecognitionStatus.UnknownError }); } void this.cancelRecognition(); } } async flipCamera() { await this.videoRecognizer.flipCamera(); } isCameraFlipped() { if (!this.videoRecognizer) { return false; } return this.videoRecognizer.isCameraFlipped(); } isScanFromImageAvailable(_recognizers = [], _recognizerOptions = {}) { return true; } getScanFromImageType(_recognizers = [], _recognizerOptions = {}) { return ImageRecognitionType.Single; } async scanFromImage(configuration, eventCallback) { eventCallback({ status: RecognitionStatus.Preparing }); const recognizers = await this.createRecognizers(configuration.recognizers, configuration.recognizerOptions); const recognizerRunner = await this.createRecognizerRunner(recognizers, eventCallback); const handleTerminate = async () => { var _a, _b, _c; this.eventEmitter$.removeEventListener('terminate', handleTerminate); if (recognizerRunner) { try { await recognizerRunner.delete(); } catch (error) { // Psst, this error should not happen. } } for (const recognizer of recognizers) { if (!recognizer) { continue; } if (((_a = recognizer.recognizer) === null || _a === void 0 ? void 0 : _a.objectHandle) > -1) { (_c = (_b = recognizer.recognizer).delete) === null || _c === void 0 ? void 0 : _c.call(_b); } } this.eventEmitter$.dispatchEvent(new Event('terminate:done')); }; this.eventEmitter$.addEventListener('terminate', handleTerminate); // Get image file if (!configuration.file || !RegExp(/^image\//).exec(configuration.file.type)) { eventCallback({ status: RecognitionStatus.NoImageFileFound }); window.setTimeout(() => void this.cancelRecognition(), 500); return; } const file = configuration.file; const imageElement = new Image(); imageElement.src = URL.createObjectURL(file); await imageElement.decode(); const imageFrame = PhotoPaySDK.captureFrame(imageElement); // Get results eventCallback({ status: RecognitionStatus.Processing }); const processResult = await recognizerRunner.processImage(imageFrame); if (processResult !== PhotoPaySDK.RecognizerResultState.Empty) { for (const recognizer of recognizers) { const results = await recognizer.recognizer.getResult(); if (!results || results.state === PhotoPaySDK.RecognizerResultState.Empty) { eventCallback({ status: RecognitionStatus.EmptyResultState, data: { initiatedByUser: this.cancelInitiatedFromOutside, recognizerName: recognizer.name } }); } else { const recognitionResults = { recognizer: results, imageCapture: _IS_IMAGE_CAPTURE, recognizerName: recognizer.name }; eventCallback({ status: RecognitionStatus.ScanSuccessful, data: recognitionResults }); break; } } } else { eventCallback({ status: RecognitionStatus.EmptyResultState, data: { initiatedByUser: this.cancelInitiatedFromOutside, recognizerName: '' } }); } window.setTimeout(() => void this.cancelRecognition(), 500); } async stopRecognition() { void await this.cancelRecognition(true); } async resumeRecognition() { this.videoRecognizer.resumeRecognition(true); } changeCameraDevice(camera) { return new Promise((resolve) => { this.videoRecognizer.changeCameraDevice(camera) .then(() => resolve(true)) .catch(() => resolve(false)); }); } ////////////////////////////////////////////////////////////////////////////// // // PRIVATE METHODS isRecognizerAvailable(recognizer) { return !!AvailableRecognizers[recognizer]; } async createRecognizers(recognizers, recognizerOptions, successFrame = false) { const pureRecognizers = []; for (const recognizer of recognizers) { const instance = await PhotoPaySDK[AvailableRecognizers[recognizer]](this.sdk); pureRecognizers.push(instance); } if (recognizerOptions && Object.keys(recognizerOptions).length > 0) { for (const recognizer of pureRecognizers) { const settings = await recognizer.currentSettings(); let updated = false; if (!recognizerOptions[recognizer.recognizerName] || Object.keys(recognizerOptions[recognizer.recognizerName]).length < 1) { continue; } for (const [key, value] of Object.entries(recognizerOptions[recognizer.recognizerName])) { if (key in settings) { settings[key] = value; updated = true; } } if (updated) { await recognizer.updateSettings(settings); } } } const recognizerInstances = []; for (let i = 0; i < pureRecognizers.length; ++i) { const recognizer = pureRecognizers[i]; const instance = { name: recognizers[i], recognizer }; if (successFrame) { const successFrameGrabber = await PhotoPaySDK.createSuccessFrameGrabberRecognizer(this.sdk, recognizer); instance.successFrame = successFrameGrabber; } recognizerInstances.push(instance); } return recognizerInstances; } async createRecognizerRunner(recognizers, eventCallback) { const metadataCallbacks = { onDetectionFailed: () => eventCallback({ status: RecognitionStatus.DetectionFailed }), onQuadDetection: (quad) => { eventCallback({ status: RecognitionStatus.DetectionStatusChange, data: quad }); const detectionStatus = quad.detectionStatus; switch (detectionStatus) { case PhotoPaySDK.DetectionStatus.Fail: eventCallback({ status: RecognitionStatus.DetectionStatusSuccess }); break; case PhotoPaySDK.DetectionStatus.Success: eventCallback({ status: RecognitionStatus.DetectionStatusSuccess }); break; case PhotoPaySDK.DetectionStatus.CameraTooHigh: eventCallback({ status: RecognitionStatus.DetectionStatusCameraTooHigh }); break; case PhotoPaySDK.DetectionStatus.FallbackSuccess: eventCallback({ status: RecognitionStatus.DetectionStatusFallbackSuccess }); break; case PhotoPaySDK.DetectionStatus.Partial: eventCallback({ status: RecognitionStatus.DetectionStatusPartial }); break; case PhotoPaySDK.DetectionStatus.CameraAtAngle: eventCallback({ status: RecognitionStatus.DetectionStatusCameraAtAngle }); break; case PhotoPaySDK.DetectionStatus.CameraTooNear: eventCallback({ status: RecognitionStatus.DetectionStatusCameraTooNear }); break; case PhotoPaySDK.DetectionStatus.DocumentTooCloseToEdge: eventCallback({ status: RecognitionStatus.DetectionStatusDocumentTooCloseToEdge }); break; default: // Send nothing } } }; const recognizerRunner = await PhotoPaySDK.createRecognizerRunner(this.sdk, recognizers.map((el) => el.successFrame || el.recognizer), false, metadataCallbacks); return recognizerRunner; } async cancelRecognition(initiatedFromOutside = false) { this.cancelInitiatedFromOutside = initiatedFromOutside; this.eventEmitter$.dispatchEvent(new Event('terminate')); } }