import SignatureTablet from './SignatureTablet.js'
import { BASE_URL, checkResponse } from './Common.js'
/**
* ControlProperty
* @classdesc Form Control Properties
* @see {@link Verifone#renderForm} Render a form and update control properties
*/
export class ControlProperty {
id
name
value
/**
* Instantiate a Control Property
* @constructor
* @param {number} id - ID of the Control on the Form
* @param {string} name - Name of the property to update
* @param {number|string|boolean} value - property value
* @example
* // A property to make a control hidden
* const prop = new ControlProperty(1, "visible", false)
*
* // A property to change the text of a control
* const prop = new ControlProperty(1, "caption", "Cancel Operation")
*
* // A property to change the background color of a control
* const prop = new ControlProperty(1, "bg_color", "#EB9534")
*/
constructor (id, name, value) {
this.id = id
this.name = name
this.value = value
}
toJSON () {
return {
id: this.id,
name: this.name,
value: this.value
}
}
}
/**
* DeviceForm
* @classdesc Form to be rendered on a tablet device.
* @see {@link Verifone#renderForm} Render a form
*/
export class DeviceForm {
id
waitForEvents
timeout
/**
* Instantiate a Form to be Rendered
* @constructor
* @param {string} id - ID of the Form
* @param {bool} [waitForEvents=false] - If `true` will wait for event data to
* be received on the form.
* @param {number} [timeout=15] - How long, in seconds, to wait for events from
* the form.
* @example
* // Form with no input data
* const form = new DeviceForm("FP_DISPLAY")
*
* // Form with buttons, wait 15 seconds for event data
* const form = new DeviceForm("FP_GEN_DISPLAY", true, 25)
*/
constructor (id, waitForEvents = false, timeout = 15) {
this.id = id
this.waitForEvents = waitForEvents
this.timeout = timeout
}
toJSON () {
return {
id: this.id,
wait_for_events: this.waitForEvents,
timeout: this.timeout
}
}
}
/**
* Verifone
* @classdesc Captures a signature and renders forms on Verifone devices.
* @extends SignatureTablet
* @see {@link SignatureTablet}
*/
export class Verifone extends SignatureTablet {
/**
* Instantiate a Verifone Class
* @constructor
* @param {string} [baseURL] - Protocol, domain, and port for the service.
* @example
* const tablet = new Verifone()
*
*/
constructor (baseUrl = BASE_URL) {
super('capture_signature_verifone', baseUrl)
}
/**
* Not currently supported for this plugin.
* @throws Not implemented error
* @example
* // Not currently supported for this plugin
* @override
*/
async streamUrl () {
throw new Error('streamUrl() is not implemented for the Verifone Plugin')
}
/**
* Not currently supported for this plugin.
* @throws Not implemented error
* @example
* // Not currently supported for this plugin
* @override
*/
async getMostRecentFrame () {
throw new Error('getMostRecentFrame() is not implemented for the Verifone Plugin')
}
/**
* Not currently supported for this plugin.
* @throws Not implemented error
* @see {@link Verifone#cancel}
* @example
* // Not currently supported for this plugin
* @override
*/
async stopFeed () {
throw new Error('stopFeed() is not implemented for the Verifone Plugin')
}
/**
* Get detailed information about a device
*
* @description Returns detailed information about a device.
*
* @async
* @method
* @returns {object} Device info
* @example
* const info = await device.info()
*/
async info (deviceOpt) {
const device = await this.setupDevice(deviceOpt)
return device.info()
}
/**
* Cancel a pending device operation
*
* @description Will attempt to cancel any active long-running operation
*
* @param {Number?} [timeout] If provided, will attempt to
* wait at least this long (in seconds) for the operation to be cancelled
* before returning a response. By default this endpoint will request a
* cancellation and return immediately, however if you'd like to block, waiting
* for the device to be ready before attempting another operation you may supply
* this timeout.
*
* @param {string|object} [deviceOpt] - Cancel the operation for either a
* specific Device ID or a Device Object. The default is the first available
* device.
*
* @async
* @method
* @returns {object} Cancellation status
* @example
* const {status} = await device.cancel()
*/
async cancel (timeout = null, deviceOpt) {
const device = await this.setupDevice(deviceOpt)
return device.cancel(timeout)
}
/**
* Restart the device or reset state.
*
* @description Reset the Verifone's internal state, reboot the device, or
* Forms Processor application.
*
* @param {Number?} [depth=3] 0 = Reboot the device, 1 = Restart Forms
* Processor, 2 = Reset device state, 3 = Reset device state and drain serial
* port.
*
* @param {string|object} [deviceOpt] - Display the objects on either a
* specific Device ID or a Device Object. The default is the first available
* device.
*
* @async
* @method
* @returns {object} Reset status
* @example
* const {status} = await device.reset()
*/
async reset (depth = 3, deviceOpt) {
const device = await this.setupDevice(deviceOpt)
return device.reset(depth)
}
/**
* Render an array of form controls.
*
* @description If any objects are Button types, the method will wait for one
* to be clicked.
*
* @param {object[]} objects An array of objects to display.
*
* @param {object} [formOpt] - Form configuration options.
* @param {string} [formOpt.background='#ffffff'] - Background color of form (hex or RGB.)
* @param {number} [formOpt.timeout=45] - Form timeout, in seconds.
*
* @param {string|object} [deviceOpt] - Display the objects on either a
* specific Device ID or a Device Object. The default is the first available
* device.
*
* @param {object} [requestOpt] - Additional options for the request
*
* @param {AbortSignal} [requestOpt.signal=null] - An {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal|AbortSignal}
* obtained from an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortController|AbortController}
* allowing the request to be aborted. If the request is aborted or the HTTP
* connection is closed for any reason before a user has interacted with the
* display, the device state will be reset and the screen will be cleared.
* The {@link Verifone#cancel} method is the preferred method for cancelling
* a displayObjects request and should usually be used instead of an AbortSignal.
*
* @param {Number?} [requestOpt.readyTimeout=10] Attempt to wait at least this
* long (in seconds) for the device to be ready before sending the
* displayObjects request. This reduces the chance that the request will fail
* due to the serial port being locked after a previously cancelled or aborted
* request.
*
* @async
* @method
* @returns {string?} ID of the clicked object if any
* @example
* const result = await tablet.displayObjects([
* new Button("OK", 20, 200),
* new Button("Cancel", 80, 200)
* ], { background: 'rgb(10,100,10)'})
*
* console.log(result)
*/
async displayObjects (objects, formOpt = {}, deviceOpt, requestOpt = {}) {
const device = await this.setupDevice(deviceOpt)
const defaultRequestOpt = { signal: null, readyTimeout: 10 }
const mergedOpts = Object.assign({}, defaultRequestOpt, requestOpt)
if (typeof mergedOpts.readyTimeout === 'number') {
// Abort any active in-flight requests and wait at most readyTimeout
// seconds for the device to be ready
await this.cancel(mergedOpts.readyTimeout)
}
return device.displayObjects(objects, formOpt, mergedOpts.signal)
}
/**
* Render a form and get event data.
*
* @param {string|DeviceForm} form - ID of the form on the device or a DeviceForm
* object with addtional configuration details.
* @param {ControlProperty[]} [controlProps] - Array of {@link ControlProperty}
* objects.
* @param {string|object} [deviceOpt] - Display the objects on either a
* specific Device ID or a Device Object. The default is the first available
* device.
*
* @override
* @see {@link Verifone#renderForm} Render a form
*/
async renderForm (form, controlProps, deviceOpt) {
if (typeof form === 'string') {
form = new DeviceForm(form)
}
const device = await this.setupDevice(deviceOpt)
device.can('render_form')
const body = JSON.stringify({
form: form.id,
wait_for_events: form.waitForEvents,
timeout: form.timeout,
control_properties: controlProps
})
const options = {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body
}
const url = `${this.baseUrl}/plugin/${this.id}/device/${device.id}/form/${form.id}/render`
const response = await fetch(url, options)
await checkResponse(response)
return await response.json()
}
/**
* Read magstripe from a card reader
*
* @param {object} [readOpts] - Additional options for reading magstripe.
* @param {number} [readOpts.timeout=30] - Magstripe read timeout, in seconds.
*
* @param {string|object} [deviceOpt] - Read magstrip using a specific Device
* ID or a Device Object. The default is the first available device.
*
* @param {object} [requestOpt] - Additional options for the request
*
* @param {AbortSignal} [requestOpt.signal=null] - An {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal|AbortSignal}
* obtained from an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortController|AbortController}
* allowing the request to be aborted. If the request is aborted or the HTTP
* connection is closed for any reason before a user has interacted with the
* display, the device state will be reset and the screen will be cleared.
* The {@link Verifone#cancel} method is the preferred method for cancelling
* a readMagstripe request and should usually be used instead of an AbortSignal.
*
* @param {Number?} [requestOpt.readyTimeout=10] Attempt to wait at least this
* long (in seconds) for the device to be ready before sending the
* readMagstripe request. This reduces the chance that the request will fail
* due to the serial port being locked after a previously cancelled or aborted
* request.
*/
async readMagstripe (readOpts = { timeout: 30 }, deviceOpt, requestOpt = {}) {
const device = await this.setupDevice(deviceOpt)
const defaultRequestOpt = { signal: null, readyTimeout: 10 }
const mergedOpts = Object.assign({}, defaultRequestOpt, requestOpt)
const options = {
method: 'GET',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
}
}
device.can('read_magstripe')
if (typeof mergedOpts.readyTimeout === 'number') {
// Abort any active in-flight requests and wait at most readyTimeout
// seconds for the device to be ready
await this.cancel(mergedOpts.readyTimeout)
}
if (requestOpt.signal instanceof AbortSignal) {
options.signal = requestOpt.signal
}
const url = `${this.baseUrl}/plugin/${this.id}/device/${device.id}/magstripe?timeout=${readOpts.timeout}`
const response = await fetch(url, options)
await checkResponse(response)
return await response.json()
}
}
export default Verifone