import { Base64Url, Utf8 } from '@digitalpersona/core'; import { Handler, MultiCastEventSource } from '../../private'; import { Command, Request, Channel } from '../websdk'; import { Event, CommunicationFailed, CommunicationEventSource } from '../../common'; import { DeviceConnected, DeviceDisconnected, DeviceEventSource } from '../events'; import { ErrorOccurred, SamplesAcquired, QualityReported, AcquisitionStarted, AcquisitionStopped, } from './events'; import { FingerprintsEventSource } from './eventSource'; import { Method, NotificationType, Notification, EnumerateDevicesResponse, Completed, Error, Quality } from './messages'; import { DeviceInfo } from './device'; import { SampleFormat } from './sample'; /** * A fingerprint reader API. * An instance of this class allows to subscribe to finerprint reader events and read fingerprint data. * The fingerprint reader API uses DigitalPersona WebSDK to communicate with fingerprint reader drivers and hardware. */ export class FingerprintReader extends MultiCastEventSource // implements FingerprintsEventSource, DeviceEventSource, CommunicationEventSource { /** A WebSdk channel. */ private readonly channel: Channel; /** * Constructs a new fingerprint reader API object. * @param options - options for the `WebSdk` channel. */ constructor( private readonly options?: WebSdk.WebChannelOptions, ) { super(); this.channel = new Channel("fingerprints", this.options); this.channel.onCommunicationError = this.onConnectionFailed.bind(this); this.channel.onNotification = this.processNotification.bind(this); } /** An event handler for the {@link DeviceConnected} event. * @remarks This is a unicast subscription, i.e. only one handler can be registered at once. * For multicast subscription use {@link FingerprintReader.on} and {@link FingerprintReader.off}. */ public onDeviceConnected: Handler; /** An event handler for the {@link DeviceDisconnected} event. * @remarks This is a unicast subscription, i.e. only one handler can be registered at once. * For multicast subscription use {@link FingerprintReader.on} and {@link FingerprintReader.off}. */ public onDeviceDisconnected: Handler; /** An event handler for the {@link SamplesAcquired} event. * @remarks This is a unicast subscription, i.e. only one handler can be registered at once. * For multicast subscription use {@link FingerprintReader.on} and {@link FingerprintReader.off}. */ public onSamplesAcquired: Handler; /** An event handler for the {@link QualityReported} event. * @remarks This is a unicast subscription, i.e. only one handler can be registered at once. * For multicast subscription use {@link FingerprintReader.on} and {@link FingerprintReader.off}. */ public onQualityReported: Handler; /** An event handler for the {@link ErrorOccurred} event. * @remarks This is a unicast subscription, i.e. only one handler can be registered at once. * For multicast subscription use {@link FingerprintReader.on} and {@link FingerprintReader.off}. */ public onErrorOccurred: Handler; /** An event handler for the {@link AcquisitionStarted} event. * @remarks This is a unicast subscription, i.e. only one handler can be registered at once. * For multicast subscription use {@link FingerprintReader.on} and {@link FingerprintReader.off}. */ public onAcquisitionStarted: Handler; /** An event handler for the {@link AcquisitionStopped} event. * @remarks This is a unicast subscription, i.e. only one handler can be registered at once. * For multicast subscription use {@link FingerprintReader.on} and {@link FingerprintReader.off}. */ public onAcquisitionStopped: Handler; /** An event handler for the {@link CommunicationFailed} event. * @remarks This is a unicast subscription, i.e. only one handler can be registered at once. * For multicast subscription use {@link FingerprintReader.on} and {@link FingerprintReader.off}. */ public onCommunicationFailed: Handler; /** * Adds an event handler for the event. * This is a multicast subscription, i.e. many handlers can be registered at once. * * @param event - a name of the event to subscribe, e.g. "SampleAcquired" * @param handler - an event handler. * @returns an event handler reference. * Store the reference and pass it to the {@link FingerprintReader.off} to unsubscribe from the event. * * @example * ``` * class FingerprintComponent * { * private reader: FingerprintReader; * * private onDeviceConnected = (event: DeviceConnected) => { ... }; * private onDeviceDisconnected = (event: DeviceDisconnected) => { ... }; * private onSamplesAcquired = (event: SampleAquired) => { ... }; * ... * * public async $onInit() { * this.reader = new FingerprintReader(); * this.reader.on("DeviceConnected", onDeviceConnected); * this.reader.on("DeviceDisconnected", onDeviceDisconnected); * this.reader.on("SamplesAcquired", onSamplesAcquired); * ... * await this.fingerprintReader.startAcquisition(SampleFormat.Intermediate); * } * public async $onDestroy() { * await this.fingerprintReader.stopAcquisition(); * this.reader.off("DeviceConnected", onDeviceConnected); * this.reader.off("DeviceDisconnected", onDeviceDisconnected); * this.reader.off("SamplesAcquired", onSamplesAcquired); * ... * // alternatively, call this.reader.off() to unsubscribe from all events at once. * delete this.reader; * } * } * ``` */ public on(event: string, handler: Handler): Handler { return this._on(event, handler); } /** Deletes an event handler for the event. * @param event - a name of the event to subscribe. * @param handler - an event handler added with the {@link FingerprintReader.on} method. */ public off(event?: string, handler?: Handler): this { return this._off(event, handler); } /** Lists all connected fingerprint readers. * @returns a promise to return a list of fingerprint reader names. */ public enumerateDevices(): Promise { return this.channel.send(new Request(new Command( Method.EnumerateDevices, ))) .then(response => { if (!response) return []; const deviceList: EnumerateDevicesResponse = JSON.parse(Utf8.fromBase64Url(response.Data || "{}")); return JSON.parse(deviceList.DeviceIDs || "[]"); }); } /** Reads a fingerprint reader device information. * @param deviceUid - a fingerprint reader ID. * @returns a promise to return a device information. * The promise can be fulfilled but return `null` if the reader provides no information. * The promise will be rejected if a reader is not found or in case of a reading error. */ public getDeviceInfo(deviceUid: string): Promise { return this.channel.send(new Request(new Command( Method.GetDeviceInfo, Base64Url.fromJSON({ DeviceID: deviceUid }), ))) .then(response => { const deviceInfo: DeviceInfo = JSON.parse(Utf8.fromBase64Url(response.Data || "null")); return deviceInfo; }); } /** Activate a fingerprint acquisition mode. * This call will produce a {@link AcquisitionStarted} event if activation was successful. * After that the reader will wait for a finger placed on the reader. * When a finger is placed, a {@link QualityReported} event will report a scan quality, * and a {@link SamplesAcquired} event will return a scanned sample in case of a successful scan. */ public startAcquisition(sampleFormat: SampleFormat, deviceUid?: string): Promise { return this.channel.send(new Request(new Command( Method.StartAcquisition, Base64Url.fromJSON({ DeviceID: deviceUid ? deviceUid : "00000000-0000-0000-0000-000000000000", SampleType: sampleFormat, }), ))) .then(); } /** Deactivates a fingerprint acquisition mode. * This call will produce a {@link AcquisitionStopped} event if deactivation was successful. * After that the reader will stop waiting for a finger. */ public stopAcquisition(deviceUid?: string): Promise { return this.channel.send(new Request(new Command( Method.StopAcquisition, Base64Url.fromJSON({ DeviceID: deviceUid ? deviceUid : "00000000-0000-0000-0000-000000000000", }), ))) .then(); } /** Converts WebSdk connectivity error to a fingerprint API event. */ private onConnectionFailed(): void { this.emit(new CommunicationFailed()); } /** Converts WebSdk notification to fingerprint API events. */ private processNotification(notification: Notification): void { switch (notification.Event) { case NotificationType.Completed: const completed: Completed = JSON.parse(Utf8.fromBase64Url(notification.Data || "")); return this.emit(new SamplesAcquired(notification.Device, completed.SampleFormat, completed.Samples)); case NotificationType.Error: const error: Error = JSON.parse(Utf8.fromBase64Url(notification.Data || "")); return this.emit(new ErrorOccurred(notification.Device, error.uError)); case NotificationType.Disconnected: return this.emit(new DeviceDisconnected(notification.Device)); case NotificationType.Connected: return this.emit(new DeviceConnected(notification.Device)); case NotificationType.Quality: const quality: Quality = JSON.parse(Utf8.fromBase64Url(notification.Data || "")); return this.emit(new QualityReported(notification.Device, quality.Quality)); case NotificationType.Stopped : return this.emit(new AcquisitionStopped(notification.Device)); case NotificationType.Started: return this.emit(new AcquisitionStarted(notification.Device)); default: console.log(`Unknown notification: ${notification.Event}`); } } }