UNPKG

usb

Version:
295 lines 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebUSB = exports.getWebUsb = void 0; const usb = require("../usb"); const events_1 = require("events"); const webusb_device_1 = require("./webusb-device"); /** * Convenience method to get the WebUSB interface available */ const getWebUsb = () => { if (navigator && navigator.usb) { return navigator.usb; } return new WebUSB(); }; exports.getWebUsb = getWebUsb; class NamedError extends Error { constructor(message, name) { super(message); this.name = name; } } class WebUSB { constructor(options = {}) { this.options = options; this.emitter = new events_1.EventEmitter(); this.knownDevices = new Map(); this.authorisedDevices = new Set(); const deviceConnectCallback = async (device) => { const webDevice = await this.getWebDevice(device); // When connected, emit an event if it is an allowed device if (webDevice && this.isAuthorisedDevice(webDevice)) { const event = { type: 'connect', device: webDevice }; this.emitter.emit('connect', event); } }; const deviceDisconnectCallback = async (device) => { // When disconnected, emit an event if the device was a known allowed device if (this.knownDevices.has(device)) { const webDevice = this.knownDevices.get(device); if (webDevice && this.isAuthorisedDevice(webDevice)) { const event = { type: 'disconnect', device: webDevice }; this.emitter.emit('disconnect', event); } } }; this.emitter.on('newListener', event => { const listenerCount = this.emitter.listenerCount(event); if (listenerCount !== 0) { return; } if (event === 'connect') { usb.addListener('attach', deviceConnectCallback); } else if (event === 'disconnect') { usb.addListener('detach', deviceDisconnectCallback); } }); this.emitter.on('removeListener', event => { const listenerCount = this.emitter.listenerCount(event); if (listenerCount !== 0) { return; } if (event === 'connect') { usb.removeListener('attach', deviceConnectCallback); } else if (event === 'disconnect') { usb.removeListener('detach', deviceDisconnectCallback); } }); } set onconnect(fn) { if (this._onconnect) { this.removeEventListener('connect', this._onconnect); this._onconnect = undefined; } if (fn) { this._onconnect = fn; this.addEventListener('connect', this._onconnect); } } set ondisconnect(fn) { if (this._ondisconnect) { this.removeEventListener('disconnect', this._ondisconnect); this._ondisconnect = undefined; } if (fn) { this._ondisconnect = fn; this.addEventListener('disconnect', this._ondisconnect); } } addEventListener(type, listener) { this.emitter.addListener(type, listener); } removeEventListener(type, callback) { this.emitter.removeListener(type, callback); } dispatchEvent(_event) { // Don't dispatch from here return false; } /** * Requests a single Web USB device * @param options The options to use when scanning * @returns Promise containing the selected device */ async requestDevice(options) { // Must have options if (!options) { throw new TypeError('requestDevice error: 1 argument required, but only 0 present'); } // Options must be an object if (options.constructor !== {}.constructor) { throw new TypeError('requestDevice error: parameter 1 (options) is not an object'); } // Must have a filter if (!options.filters) { throw new TypeError('requestDevice error: required member filters is undefined'); } // Filter must be an array if (options.filters.constructor !== [].constructor) { throw new TypeError('requestDevice error: the provided value cannot be converted to a sequence'); } // Check filters options.filters.forEach(filter => { // Protocol & Subclass if (filter.protocolCode && !filter.subclassCode) { throw new TypeError('requestDevice error: subclass code is required'); } // Subclass & Class if (filter.subclassCode && !filter.classCode) { throw new TypeError('requestDevice error: class code is required'); } }); let devices = await this.loadDevices(options.filters); devices = devices.filter(device => this.filterDevice(device, options.filters)); if (devices.length === 0) { throw new NamedError('Failed to execute \'requestDevice\' on \'USB\': No device selected.', 'NotFoundError'); } try { // If no devicesFound function, select the first device found const device = this.options.devicesFound ? await this.options.devicesFound(devices) : devices[0]; if (!device) { throw new NamedError('Failed to execute \'requestDevice\' on \'USB\': No device selected.', 'NotFoundError'); } this.authorisedDevices.add({ vendorId: device.vendorId, productId: device.productId, classCode: device.deviceClass, subclassCode: device.deviceSubclass, protocolCode: device.deviceProtocol, serialNumber: device.serialNumber }); return device; } catch (error) { throw new NamedError('Failed to execute \'requestDevice\' on \'USB\': No device selected.', 'NotFoundError'); } } /** * Gets all allowed Web USB devices which are connected * @returns Promise containing an array of devices */ async getDevices() { const preFilters = this.options.allowAllDevices ? undefined : this.options.allowedDevices; // Refresh devices and filter for allowed ones const devices = await this.loadDevices(preFilters); return devices.filter(device => this.isAuthorisedDevice(device)); } async loadDevices(preFilters) { let devices = usb.getDeviceList(); // Pre-filter devices devices = this.quickFilter(devices, preFilters); const refreshedKnownDevices = new Map(); for (const device of devices) { const webDevice = await this.getWebDevice(device); if (webDevice) { refreshedKnownDevices.set(device, webDevice); } } // Refresh knownDevices to remove old devices from the map this.knownDevices = refreshedKnownDevices; return [...this.knownDevices.values()]; } // Get a WebUSBDevice corresponding to underlying device. // Returns undefined the device was not found and could not be created. async getWebDevice(device) { if (!this.knownDevices.has(device)) { if (this.options.deviceTimeout) { device.timeout = this.options.deviceTimeout; } try { const webDevice = await webusb_device_1.WebUSBDevice.createInstance(device, this.options.autoDetachKernelDriver); this.knownDevices.set(device, webDevice); } catch { // Ignore creation issues as this may be a system device } } return this.knownDevices.get(device); } // Undertake quick filter on devices before creating WebUSB devices if possible quickFilter(devices, preFilters) { if (!preFilters || !preFilters.length) { return devices; } // Just pre-filter on vid/pid return devices.filter(device => preFilters.some(filter => { // Vendor if (filter.vendorId && filter.vendorId !== device.deviceDescriptor.idVendor) return false; // Product if (filter.productId && filter.productId !== device.deviceDescriptor.idProduct) return false; // Ignore Class, Subclass and Protocol as these need to check interfaces, too // Ignore serial number for node-usb as it requires device connection return true; })); } // Filter WebUSB devices filterDevice(device, filters) { if (!filters || !filters.length) { return true; } return filters.some(filter => { // Vendor if (filter.vendorId && filter.vendorId !== device.vendorId) return false; // Product if (filter.productId && filter.productId !== device.productId) return false; // Class if (filter.classCode) { if (!device.configuration) { return false; } // Interface Descriptors const match = device.configuration.interfaces.some(iface => { // Class if (filter.classCode && filter.classCode !== iface.alternate.interfaceClass) return false; // Subclass if (filter.subclassCode && filter.subclassCode !== iface.alternate.interfaceSubclass) return false; // Protocol if (filter.protocolCode && filter.protocolCode !== iface.alternate.interfaceProtocol) return false; return true; }); if (match) { return true; } } // Class if (filter.classCode && filter.classCode !== device.deviceClass) return false; // Subclass if (filter.subclassCode && filter.subclassCode !== device.deviceSubclass) return false; // Protocol if (filter.protocolCode && filter.protocolCode !== device.deviceProtocol) return false; // Serial if (filter.serialNumber && filter.serialNumber !== device.serialNumber) return false; return true; }); } // Check whether a device is authorised isAuthorisedDevice(device) { // All devices are authorised if (this.options.allowAllDevices) { return true; } // Check any allowed device filters if (this.options.allowedDevices && this.filterDevice(device, this.options.allowedDevices)) { return true; } // Check authorised devices return [...this.authorisedDevices.values()].some(authorised => authorised.vendorId === device.vendorId && authorised.productId === device.productId && authorised.classCode === device.deviceClass && authorised.subclassCode === device.deviceSubclass && authorised.protocolCode === device.deviceProtocol && authorised.serialNumber === device.serialNumber); } } exports.WebUSB = WebUSB; //# sourceMappingURL=index.js.map