@digitalpersona/devices
Version:
DigitalPersona Security Devices support library
244 lines (223 loc) • 10.2 kB
text/typescript
import { Handler, MultiCastEventSource } from '../../private';
import { Command, Request, Channel } from '../websdk';
import { Event, CommunicationFailed, CommunicationEventSource } from '../../common';
import { DeviceConnected, DeviceDisconnected, DeviceEventSource } from '../events';
import { CardInserted, CardRemoved } from './events';
import { CardsEventSource as CardsEventSource } from './eventSource';
import { Method, NotificationType, Notification, CardNotification, ReaderList, CardList } from "./messages";
import { Card } from './cards';
import { Utf8, Base64Url, Base64, Utf16 } from '@digitalpersona/core';
/**
* A card reader API class.
* An instance of this class allows to subscribe to card reader events and read card data.
* The card reader API uses DigitalPersona WebSDK to communicate with card reader drivers and hardware.
*/
export class CardsReader
extends MultiCastEventSource
// implements CommunicationEventSource, DeviceEventSource, CardsEventSource
{
/** A WebSdk channel. */
private readonly channel: Channel;
/**
* Constructs a new card reader API object.
* @param options - options for the `WebSdk` channel.
*/
constructor(options?: WebSdk.WebChannelOptions) {
super();
this.channel = new Channel("smartcards", options);
this.channel.onCommunicationError = this.onConnectionFailed.bind(this);
this.channel.onNotification = this.processNotification.bind(this);
}
/** An event handler for the {@link DeviceConnected} event.
* This is a unicast subscription, i.e. only one handler can be registered at once.
*/
public onDeviceConnected: Handler<DeviceConnected>;
/** An event handler for the {@link DeviceDisconnected} event.
* This is a unicast subscription, i.e. only one handler can be registered at once.
*/
public onDeviceDisconnected: Handler<DeviceDisconnected>;
/** An event handler for the {@link CardInserted} event.
* This is a unicast subscription, i.e. only one handler can be registered at once.
*/
public onCardInserted: Handler<CardInserted>;
/** An event handler for the {@link CardRemoved} event.
* This is a unicast subscription, i.e. only one handler can be registered at once.
*/
public onCardRemoved: Handler<CardRemoved>;
/** An event handler for the {@link CommunicationFailed} event.
* This is a unicast subscription, i.e. only one handler can be registered at once.
*/
public onCommunicationFailed: Handler<CommunicationFailed>;
/**
* 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. "CardInserted"
* @param handler - an event handler.
* @returns an event handler reference.
* Store the reference and pass it to the {@link CardsReader.off} to unsubscribe from the event.
*
* @example
* ```
* class CardComponent
* {
* private reader: CardsReader;
*
* private onCardInserted = (event: CardInserted) => { ... }
* private onCardRemoved = (event: CardRemoved) => { ... }
* ...
*
* public async $onInit() {
* this.reader = new CardsReader();
* this.reader.on("CardInserted", this.onCardInserted);
* this.reader.on("CardRemoved", this.onCardRemoved);
* ...
* await this.cardReader.subscribe()
* }
* public async $onDestroy() {
* await this.cardReader.unsubscribe();
* this.reader.off("CardInserted", this.onCardInserted);
* this.reader.off("CardRemoved", this.onCardRemoved);
* ...
* // alternatively, call this.reader.off() to unsubscribe from all events at once.
* delete this.reader;
* }
* }
* ```
*/
public on<E extends Event>(event: string, handler: Handler<E>): Handler<E> { 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 CardsReader.on} method.
* @example See example in {@link CardsReader.on}
*/
public off<E extends Event>(event?: string, handler?: Handler<E>): this { return this._off(event, handler); }
/** Lists all connected card readers.
* @returns a promise to return a list of card reader names.
*/
public enumerateReaders(): Promise<string[]> {
return this.channel.send(new Request(new Command(
Method.EnumerateReaders,
)))
.then(response => {
const list: ReaderList = JSON.parse(Utf8.fromBase64Url(response.Data || "{}"));
return JSON.parse(list.Readers || "[]");
});
}
/** Lists all inserted cards.
* @returns a promise to return a list of card information for connected cards.
*/
public enumerateCards(): Promise<Card[]> {
return this.channel.send(new Request(new Command(
Method.EnumerateCards,
)))
.then(response => {
const list: CardList = JSON.parse(Utf8.fromBase64Url(response.Data || "{}"));
const cards: string[] = JSON.parse(list.Cards || "[]");
return cards.map(s => JSON.parse(Utf16.fromBase64Url(s)));
});
}
/** Reads card data from a specific card.
* @param reader - a name of a card reader where the card was presented.
* @returns a promise to return a card information.
* The promise can be fulfilled but return `null` if the card has no information.
* The promise will be rejected if a card is not found or in case of a reading error.
*/
public getCardInfo(reader: string): Promise<Card|null> {
return this.channel.send(new Request(new Command(
Method.GetCardInfo,
Base64Url.fromJSON({ Reader: reader }),
)))
.then(response => {
const cardInfo: Card = JSON.parse(Utf8.fromBase64Url(response.Data || "null"));
return cardInfo;
});
}
/** Reads a card unique identifier.
* @param reader - a name of a card reader where the card was presented.
* @returns a promise to return a card identifier.
*/
public getCardUid(reader: string): Promise<string> {
return this.channel.send(new Request(new Command(
Method.GetCardUID,
Base64Url.fromJSON({ Reader: reader }),
)))
.then(response => {
const data = Base64.fromBase64Url(response.Data || "");
return data;
});
}
/** Reads card authentication data.
* @param reader - a name of a card reader where the card was presented.
* @param pin - an PIN code (for cards requiring a PIN).
* @returns a promise to return card authentication data.
* The card data is an opaque encoded string which should be sent to the server as is.
*/
public getCardAuthData(reader: string, pin?: string): Promise<string> {
return this.channel.send(new Request(new Command(
Method.GetDPCardAuthData,
Base64Url.fromJSON({ Reader: reader, PIN: pin || "" }),
)))
.then(response => {
const data = JSON.parse(Utf8.fromBase64Url(response.Data || ""));
return data;
});
}
/** Reads card enrollment data.
* @param reader - a name of a card reader where the card was presented.
* @param pin - an PIN code (for cards requiring a PIN).
* @returns a promise to return a card enrollment data.
* The card data is an opaque encoded string which should be sent to the server as is.
*/
public getCardEnrollData(reader: string, pin?: string): Promise<string> {
return this.channel.send(new Request(new Command(
Method.GetDPCardEnrollData,
Base64Url.fromJSON({ Reader: reader, PIN: pin || "" }),
)))
.then(response => {
const data = JSON.parse(Utf8.fromBase64Url(response.Data || ""));
return data;
});
}
/** Starts listening for card reader events.
* @param reader - an optional name of a card reader to listen.
* If no name is provided, the API will start listening all card readers.
*/
public subscribe(reader?: string): Promise<void> {
return this.channel.send(new Request(new Command(
Method.Subscribe,
reader ? Base64Url.fromJSON({ Reader: reader }) : "",
)))
.then();
}
/** Stop listening for card reader events.
* @param reader - an optional name of a card reader to stop listening.
* If no name is provided, the API will stop listening all card readers.
*/
public unsubscribe(reader?: string): Promise<void> {
return this.channel.send(new Request(new Command(
Method.Unsubscribe,
reader ? Base64Url.fromJSON({ Reader: reader }) : "",
)))
.then();
}
/** Converts WebSdk connectivity error to a card API event. */
private onConnectionFailed(): void {
this.emit(new CommunicationFailed());
}
/** Converts WebSdk notification to card API events. */
private processNotification(notification: Notification): void {
switch (notification.Event) {
case NotificationType.ReaderConnected:
return this.emit(new DeviceConnected(notification.Reader));
case NotificationType.ReaderDisconnected:
return this.emit(new DeviceDisconnected(notification.Reader));
case NotificationType.CardInserted:
return this.emit(new CardInserted(notification.Reader, (notification as CardNotification).Card));
case NotificationType.CardRemoved:
return this.emit(new CardRemoved(notification.Reader, (notification as CardNotification).Card));
default:
console.log(`Unknown notification: ${notification.Event}`);
}
}
}