@microblink/blinkid-in-browser-sdk
Version:
A simple ID scanning library for WebAssembly-enabled browsers.
442 lines (441 loc) • 17.6 kB
JavaScript
/**
* Copyright (c) Microblink Ltd. All rights reserved.
*/
import * as BlinkIDSDK from "../../../es/blinkid-sdk";
import { AvailableRecognizers, AvailableRecognizerOptions, CameraExperience, Code, EventFatalError, EventReady, RecognitionStatus } from './data-structures';
export class SdkService {
constructor() {
this.cancelInitiatedFromOutside = false;
this.showOverlay = false;
this.eventEmitter$ = document.createElement('a');
}
initialize(licenseKey, sdkSettings) {
const loadSettings = new BlinkIDSDK.WasmSDKLoadSettings(licenseKey);
loadSettings.allowHelloMessage = sdkSettings.allowHelloMessage;
loadSettings.engineLocation = sdkSettings.engineLocation;
return new Promise((resolve) => {
BlinkIDSDK.loadWasmModule(loadSettings)
.then((sdk) => {
this.sdk = sdk;
this.showOverlay = sdk.showOverlay;
resolve(new EventReady(this.sdk));
})
.catch(error => {
resolve(new EventFatalError(Code.SdkLoadFailed, 'Failed to load SDK!', 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!`
};
}
if (recognizer === 'BlinkIdCombinedRecognizer' && recognizers.length > 1) {
return {
status: false,
message: 'Recognizer "BlinkIdCombinedRecognizer" cannot be used in combination with other recognizers!'
};
}
}
return {
status: true
};
}
checkRecognizerOptions(recognizers, recognizerOptions) {
if (!recognizerOptions || !recognizerOptions.length) {
return {
status: true
};
}
for (const recognizerOption of recognizerOptions) {
let optionExistInProvidedRecognizers = false;
for (const recognizer of recognizers) {
const availableOptions = AvailableRecognizerOptions[recognizer];
if (availableOptions.indexOf(recognizerOption) > -1) {
optionExistInProvidedRecognizers = true;
break;
}
}
if (!optionExistInProvidedRecognizers) {
return {
status: false,
message: `Recognizer option "${recognizerOption}" is not supported by available recognizers!`
};
}
}
return {
status: true
};
}
getDesiredCameraExperience(recognizers, _recognizerOptions = []) {
if (recognizers.indexOf('BlinkIdCombinedRecognizer') > -1) {
return CameraExperience.CardCombined;
}
if (recognizers.indexOf('BlinkIdRecognizer') > -1) {
return CameraExperience.CardSingleSide;
}
return CameraExperience.Barcode;
}
async scanFromCamera(configuration, eventCallback) {
eventCallback({ status: RecognitionStatus.Preparing });
this.cancelInitiatedFromOutside = false;
const recognizers = await this.createRecognizers(configuration.recognizers, configuration.recognizerOptions, configuration.anonymization, configuration.successFrame);
const recognizerRunner = await this.createRecognizerRunner(recognizers, eventCallback);
try {
this.videoRecognizer = await BlinkIDSDK.VideoRecognizer.createVideoRecognizerFromCameraStream(configuration.cameraFeed, recognizerRunner, configuration.cameraId);
await this.videoRecognizer.setVideoRecognitionMode(BlinkIDSDK.VideoRecognitionMode.Recognition);
this.eventEmitter$.addEventListener('terminate', async () => {
if (this.videoRecognizer && typeof this.videoRecognizer.cancelRecognition === 'function') {
this.videoRecognizer.cancelRecognition();
}
if (recognizerRunner) {
try {
await recognizerRunner.delete();
}
catch (error) {
// Psst, this error should not happen.
}
}
for (const recognizer of recognizers) {
if (!recognizer) {
continue;
}
if (recognizer.recognizer &&
recognizer.recognizer.objectHandle > -1 &&
typeof recognizer.recognizer.delete === 'function') {
recognizer.recognizer.delete();
}
if (recognizer.successFrame &&
recognizer.successFrame.objectHandle > -1
&& typeof recognizer.successFrame.delete === 'function') {
recognizer.successFrame.delete();
}
}
window.setTimeout(() => {
if (this.videoRecognizer) {
this.videoRecognizer.releaseVideoFeed();
}
}, 1);
});
this.videoRecognizer.startRecognition(async (recognitionState) => {
this.videoRecognizer.pauseRecognition();
eventCallback({ status: RecognitionStatus.Processing });
if (recognitionState !== BlinkIDSDK.RecognizerResultState.Empty) {
for (const recognizer of recognizers) {
const results = await recognizer.recognizer.getResult();
this.recognizerName = recognizer.recognizer.recognizerName;
if (!results || results.state === BlinkIDSDK.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 !== BlinkIDSDK.RecognizerResultState.Empty) {
recognitionResults.successFrame = successFrameResults;
}
}
eventCallback({
status: RecognitionStatus.ScanSuccessful,
data: {
result: recognitionResults,
initiatedByUser: this.cancelInitiatedFromOutside,
imageCapture: this.recognizerName === 'BlinkIdImageCaptureRecognizer'
}
});
break;
}
}
}
else {
eventCallback({
status: RecognitionStatus.EmptyResultState,
data: {
initiatedByUser: this.cancelInitiatedFromOutside,
recognizerName: ''
}
});
}
if (this.recognizerName !== 'BlinkIdImageCaptureRecognizer') {
window.setTimeout(() => void this.cancelRecognition(), 400);
}
});
}
catch (error) {
if (error && error.name === 'VideoRecognizerError') {
const reason = error.reason;
switch (reason) {
case BlinkIDSDK.NotSupportedReason.MediaDevicesNotSupported:
eventCallback({ status: RecognitionStatus.NoSupportForMediaDevices });
break;
case BlinkIDSDK.NotSupportedReason.CameraNotFound:
eventCallback({ status: RecognitionStatus.CameraNotFound });
break;
case BlinkIDSDK.NotSupportedReason.CameraNotAllowed:
eventCallback({ status: RecognitionStatus.CameraNotAllowed });
break;
case BlinkIDSDK.NotSupportedReason.CameraInUse:
eventCallback({ status: RecognitionStatus.CameraInUse });
break;
default:
eventCallback({ status: RecognitionStatus.UnableToAccessCamera });
}
console.warn('VideoRecognizerError', error.name, '[' + reason + ']:', error.message);
void this.cancelRecognition();
}
else {
eventCallback({ status: RecognitionStatus.UnknownError });
}
}
}
async flipCamera() {
await this.videoRecognizer.flipCamera();
}
isCameraFlipped() {
if (!this.videoRecognizer) {
return false;
}
return this.videoRecognizer.cameraFlipped;
}
isScanFromImageAvailable(recognizers, _recognizerOptions = []) {
return recognizers.indexOf('BlinkIdCombinedRecognizer') === -1;
}
async scanFromImage(configuration, eventCallback) {
eventCallback({ status: RecognitionStatus.Preparing });
const recognizers = await this.createRecognizers(configuration.recognizers, configuration.recognizerOptions, configuration.anonymization);
const recognizerRunner = await this.createRecognizerRunner(recognizers, eventCallback);
// Get image file
const imageRegex = RegExp(/^image\//);
const file = (() => {
for (let i = 0; i < configuration.fileList.length; ++i) {
if (imageRegex.exec(configuration.fileList[i].type)) {
return configuration.fileList[i];
}
}
return null;
})();
if (!file) {
eventCallback({ status: RecognitionStatus.NoImageFileFound });
return;
}
const imageElement = new Image();
imageElement.src = URL.createObjectURL(file);
await imageElement.decode();
const imageFrame = BlinkIDSDK.captureFrame(imageElement);
this.eventEmitter$.addEventListener('terminate', async () => {
if (recognizerRunner) {
try {
await recognizerRunner.delete();
}
catch (error) {
// Psst, this error should not happen.
}
}
for (const recognizer of recognizers) {
if (!recognizer) {
continue;
}
if (recognizer.recognizer &&
recognizer.recognizer.objectHandle > -1 &&
typeof recognizer.recognizer.delete === 'function') {
await recognizer.recognizer.delete();
}
}
this.eventEmitter$.dispatchEvent(new Event('terminate:done'));
});
// Get results
eventCallback({ status: RecognitionStatus.Processing });
const processResult = await recognizerRunner.processImage(imageFrame);
if (processResult !== BlinkIDSDK.RecognizerResultState.Empty) {
for (const recognizer of recognizers) {
const results = await recognizer.recognizer.getResult();
if (!results || results.state === BlinkIDSDK.RecognizerResultState.Empty) {
eventCallback({
status: RecognitionStatus.EmptyResultState,
data: {
initiatedByUser: this.cancelInitiatedFromOutside,
recognizerName: recognizer.name
}
});
}
else {
const recognitionResults = {
recognizer: results,
imageCapture: recognizer.name === 'BlinkIdImageCaptureRecognizer',
recognizerName: recognizer.name
};
eventCallback({
status: RecognitionStatus.ScanSuccessful,
data: recognitionResults
});
break;
}
}
}
else {
// If necessary, scan the image once again with different settings
if (configuration.thoroughScan &&
(configuration.recognizers.indexOf('BlinkIdRecognizer') > -1 ||
configuration.recognizers.indexOf('BlinkIdCombinedRecognizer') > -1)) {
configuration.thoroughScan = false;
if (!Array.isArray(configuration.recognizerOptions) ||
configuration.recognizerOptions.indexOf('scanCroppedDocumentImage') === -1) {
if (!Array.isArray(configuration.recognizerOptions)) {
configuration.recognizerOptions = [];
}
configuration.recognizerOptions.push('scanCroppedDocumentImage');
}
else {
const position = configuration.recognizerOptions.indexOf('scanCroppedDocumentImage');
configuration.recognizerOptions.splice(position, 1);
}
const eventHandler = (recognitionEvent) => eventCallback(recognitionEvent);
const handleTerminateDone = () => {
this.eventEmitter$.removeEventListener('terminate:done', handleTerminateDone);
this.scanFromImage(configuration, eventHandler);
};
this.cancelRecognition();
this.eventEmitter$.addEventListener('terminate:done', handleTerminateDone);
return;
}
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);
}
//////////////////////////////////////////////////////////////////////////////
//
// PRIVATE METHODS
isRecognizerAvailable(recognizer) {
return !!AvailableRecognizers[recognizer];
}
async createRecognizers(recognizers, recognizerOptions, anonymization, successFrame = false) {
const pureRecognizers = [];
for (const recognizer of recognizers) {
const instance = await BlinkIDSDK[AvailableRecognizers[recognizer]](this.sdk);
pureRecognizers.push(instance);
}
if (recognizerOptions && recognizerOptions.length) {
for (const recognizer of pureRecognizers) {
let settingsUpdated = false;
const settings = await recognizer.currentSettings();
for (const setting of recognizerOptions) {
if (setting in settings) {
settings[setting] = true;
settingsUpdated = true;
}
}
if (settingsUpdated) {
await recognizer.updateSettings(settings);
}
}
}
if (typeof anonymization !== 'undefined') {
for (const recognizer of pureRecognizers) {
const settings = await recognizer.currentSettings();
settings.anonymizationMode = anonymization;
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 BlinkIDSDK.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 BlinkIDSDK.DetectionStatus.Fail:
eventCallback({ status: RecognitionStatus.DetectionStatusSuccess });
break;
case BlinkIDSDK.DetectionStatus.Success:
eventCallback({ status: RecognitionStatus.DetectionStatusSuccess });
break;
case BlinkIDSDK.DetectionStatus.CameraTooHigh:
eventCallback({ status: RecognitionStatus.DetectionStatusCameraTooHigh });
break;
case BlinkIDSDK.DetectionStatus.FallbackSuccess:
eventCallback({ status: RecognitionStatus.DetectionStatusFallbackSuccess });
break;
case BlinkIDSDK.DetectionStatus.Partial:
eventCallback({ status: RecognitionStatus.DetectionStatusPartial });
break;
case BlinkIDSDK.DetectionStatus.CameraAtAngle:
eventCallback({ status: RecognitionStatus.DetectionStatusCameraAtAngle });
break;
case BlinkIDSDK.DetectionStatus.CameraTooNear:
eventCallback({ status: RecognitionStatus.DetectionStatusCameraTooNear });
break;
case BlinkIDSDK.DetectionStatus.DocumentTooCloseToEdge:
eventCallback({ status: RecognitionStatus.DetectionStatusDocumentTooCloseToEdge });
break;
default:
// Send nothing
}
}
};
const blinkIdGeneric = recognizers.find(el => el.recognizer.recognizerName === 'BlinkIdRecognizer');
const blinkIdCombined = recognizers.find(el => el.recognizer.recognizerName === 'BlinkIdCombinedRecognizer');
if (blinkIdGeneric || blinkIdCombined) {
for (const el of recognizers) {
if (el.recognizer.recognizerName === 'BlinkIdRecognizer' ||
el.recognizer.recognizerName === 'BlinkIdCombinedRecognizer') {
const settings = await el.recognizer.currentSettings();
settings.classifierCallback = (supported) => {
eventCallback({ status: RecognitionStatus.DocumentClassified, data: supported });
};
await el.recognizer.updateSettings(settings);
}
}
}
if (blinkIdCombined) {
metadataCallbacks.onFirstSideResult = () => eventCallback({ status: RecognitionStatus.OnFirstSideResult });
}
const recognizerRunner = await BlinkIDSDK.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'));
}
}