UNPKG

usb

Version:
367 lines 14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebUSB = exports.webusb = exports.usb = void 0; const index_js_1 = require("../index.js"); /** * Hidden */ const DEFAULT_TIMEOUT = 1000; /** * Hidden */ const toUint8Array = (data) => { if (data instanceof ArrayBuffer) { return new Uint8Array(data); } // ArrayBufferView return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); }; /** * Hidden */ index_js_1.UsbDevice.prototype.controlTransferIn = async function (setup, length, timeout = DEFAULT_TIMEOUT) { const res = await this.nativeControlTransferIn(setup, timeout, length); return { data: res ? new DataView(res.buffer) : undefined, status: res ? 'ok' : 'stall', }; }; /** * Hidden */ index_js_1.UsbDevice.prototype.controlTransferOut = async function (setup, data, timeout = DEFAULT_TIMEOUT) { const res = await this.nativeControlTransferOut(setup, timeout, toUint8Array(data)); return { bytesWritten: res, status: res >= 0 ? 'ok' : 'stall', }; }; /** * Hidden */ index_js_1.UsbDevice.prototype.transferIn = async function (endpointNumber, length, timeout = DEFAULT_TIMEOUT) { const res = await this.nativeTransferIn(endpointNumber, timeout, length); return { data: res ? new DataView(res.buffer) : undefined, status: res ? 'ok' : 'stall', }; }; /** * Hidden */ index_js_1.UsbDevice.prototype.transferOut = async function (endpointNumber, data, timeout = DEFAULT_TIMEOUT) { const res = await this.nativeTransferOut(endpointNumber, timeout, toUint8Array(data)); return { bytesWritten: res, status: res >= 0 ? 'ok' : 'stall', }; }; /** * Hidden */ index_js_1.UsbDevice.prototype.isochronousTransferIn = async function (endpointNumber, packetLengths, timeout = DEFAULT_TIMEOUT) { const res = await this.nativeIsochronousTransferIn(endpointNumber, packetLengths, timeout); return res; }; /** * Hidden */ index_js_1.UsbDevice.prototype.isochronousTransferOut = async function (endpointNumber, data, packetLengths, timeout = DEFAULT_TIMEOUT) { const res = await this.nativeIsochronousTransferOut(endpointNumber, toUint8Array(data), packetLengths, timeout); return res; }; class NamedError extends Error { constructor(message, name) { super(message); this.name = name; } } class ConnectionEvent extends Event { constructor(type, eventInitDict) { super(type, eventInitDict); this.eventInitDict = eventInitDict; } get device() { return this.eventInitDict.device; } } /** * WebUSB class * * ### Events * * | Name | Event | Description | * | ---- | ----- | ----------- | * | `connect` | {@link USBConnectionEvent} | Device connected | * | `disconnect` | {@link USBConnectionEvent} | Device disconnected | */ class WebUSB extends EventTarget { constructor(options = {}) { super(); this.options = options; this.nativeEmitter = new index_js_1.Emitter(); this.authorisedDevices = new Set(); this.listenerCount = 0; this.eventListeners = new Map(); this.knownDevices = new Map(); this.deviceConnectCallback = async (device) => { this.knownDevices.set(device.handle, device); // When connected, emit an event if it is an allowed device if (device && this.isAuthorisedDevice(device)) { this.dispatchEvent(new ConnectionEvent('connect', { device })); } }; this.deviceDisconnectCallback = async (handle) => { // When disconnected, emit an event if the device was a known allowed device if (this.knownDevices.has(handle)) { const device = this.knownDevices.get(handle); if (device && this.isAuthorisedDevice(device)) { this.dispatchEvent(new ConnectionEvent('disconnect', { device })); } this.knownDevices.delete(handle); } }; } addEventListener(type, listener) { if (type !== 'connect' && type !== 'disconnect') { return; } const existingListeners = this.eventListeners.get(type) || new Set(); const hadListeners = this.listenerCount > 0; super.addEventListener(type, listener); if (!existingListeners.has(listener)) { existingListeners.add(listener); this.eventListeners.set(type, existingListeners); this.listenerCount++; } if (!hadListeners && this.listenerCount > 0) { this.nativeEmitter.addAttach(this.deviceConnectCallback); this.nativeEmitter.addDetach(this.deviceDisconnectCallback); (0, index_js_1.nativeGetDevices)().then(devices => devices.forEach(device => this.knownDevices.set(device.handle, device))); } } removeEventListener(type, callback) { if (type !== 'connect' && type !== 'disconnect') { return; } const existingListeners = this.eventListeners.get(type); const removed = existingListeners ? existingListeners.delete(callback) : false; super.removeEventListener(type, callback); if (removed) { this.listenerCount--; if (existingListeners && existingListeners.size === 0) { this.eventListeners.delete(type); } } if (removed && this.listenerCount === 0) { this.nativeEmitter.removeAttach(); this.nativeEmitter.removeDetach(); this.knownDevices.clear(); } } 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); } } /** * 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'); } // 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 || undefined }); return device; } /** * 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)); } /** * Convenience method to get the first device with the specified VID and PID, or `undefined` if no such device is present. * @param vid * @param pid */ async findDeviceByIds(vid, pid) { const device = await (0, index_js_1.nativeFindDeviceByIds)(vid, pid); return device || undefined; } /** * Convenience method to get the device with the specified serial number, or `undefined` if no such device is present. * @param serialNumber */ async findDeviceBySerial(serialNumber) { const device = await (0, index_js_1.nativeFindDeviceBySerial)(serialNumber); return device || undefined; } async loadDevices(preFilters) { let devices = await (0, index_js_1.nativeGetDevices)(); // Pre-filter devices devices = this.quickFilter(devices, preFilters); return devices; } // 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.vendorId) return false; // Product if (filter.productId && filter.productId !== device.productId) 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; /** * Default USB object (allows all devices by default) */ const usb = new WebUSB({ allowAllDevices: true }); exports.usb = usb; /** * Default WebUSB object (mimics navigator.usb) */ const webusb = typeof navigator !== 'undefined' && navigator.usb ? navigator.usb : new WebUSB(); exports.webusb = webusb; //# sourceMappingURL=index.js.map