usb
Version:
Library to access USB devices
367 lines • 14 kB
JavaScript
"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