usb 1.9.2 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +4 -0
- package/.eslintrc.json +87 -0
- package/.gitattributes +1 -0
- package/.vscode/launch.json +15 -0
- package/.vscode/tasks.json +23 -0
- package/CHANGELOG.md +13 -0
- package/README.md +400 -175
- package/dist/index.d.ts +17 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/dist/usb/bindings.d.ts +253 -0
- package/dist/usb/bindings.js +10 -0
- package/dist/usb/bindings.js.map +1 -0
- package/dist/usb/capability.d.ts +14 -0
- package/dist/usb/capability.js +18 -0
- package/dist/usb/capability.js.map +1 -0
- package/dist/usb/descriptors.d.ts +129 -0
- package/dist/usb/descriptors.js +3 -0
- package/dist/usb/descriptors.js.map +1 -0
- package/dist/usb/device.d.ts +94 -0
- package/dist/usb/device.js +300 -0
- package/dist/usb/device.js.map +1 -0
- package/dist/usb/endpoint.d.ts +91 -0
- package/dist/usb/endpoint.js +236 -0
- package/dist/usb/endpoint.js.map +1 -0
- package/dist/usb/index.d.ts +25 -0
- package/dist/usb/index.js +116 -0
- package/dist/usb/index.js.map +1 -0
- package/dist/usb/interface.d.ts +78 -0
- package/dist/usb/interface.js +137 -0
- package/dist/usb/interface.js.map +1 -0
- package/dist/webusb/index.d.ts +56 -0
- package/dist/webusb/index.js +412 -0
- package/dist/webusb/index.js.map +1 -0
- package/dist/webusb/mutex.d.ts +22 -0
- package/dist/webusb/mutex.js +89 -0
- package/dist/webusb/mutex.js.map +1 -0
- package/dist/webusb/webusb-device.d.ts +49 -0
- package/dist/webusb/webusb-device.js +892 -0
- package/dist/webusb/webusb-device.js.map +1 -0
- package/package.json +28 -15
- package/prebuilds/win32-ia32/node.napi.node +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/src/device.cc +2 -2
- package/test/usb.coffee +13 -7
- package/test/webusb.coffee +189 -0
- package/tsc/index.ts +72 -0
- package/tsc/usb/bindings.ts +304 -0
- package/tsc/usb/capability.ts +22 -0
- package/tsc/usb/descriptors.ts +180 -0
- package/tsc/usb/device.ts +325 -0
- package/tsc/usb/endpoint.ts +228 -0
- package/tsc/usb/index.ts +111 -0
- package/tsc/usb/interface.ts +172 -0
- package/tsc/webusb/index.ts +363 -0
- package/tsc/webusb/mutex.ts +38 -0
- package/tsc/webusb/webusb-device.ts +541 -0
- package/tsconfig.json +17 -0
- package/typedoc.json +9 -0
- package/usb.js +0 -573
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { LibUSBException, LIBUSB_ENDPOINT_IN, Device } from './bindings';
|
|
2
|
+
import { InterfaceDescriptor } from './descriptors';
|
|
3
|
+
import { Endpoint, InEndpoint, OutEndpoint } from './endpoint';
|
|
4
|
+
|
|
5
|
+
export class Interface {
|
|
6
|
+
/** Integer interface number. */
|
|
7
|
+
public interfaceNumber!: number;
|
|
8
|
+
|
|
9
|
+
/** Integer alternate setting number. */
|
|
10
|
+
public altSetting = 0;
|
|
11
|
+
|
|
12
|
+
/** Object with fields from the interface descriptor -- see libusb documentation or USB spec. */
|
|
13
|
+
public descriptor!: InterfaceDescriptor;
|
|
14
|
+
|
|
15
|
+
/** List of endpoints on this interface: InEndpoint and OutEndpoint objects. */
|
|
16
|
+
public endpoints!: Endpoint[];
|
|
17
|
+
|
|
18
|
+
constructor(protected device: Device, protected id: number) {
|
|
19
|
+
this.refresh();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
protected refresh(): void {
|
|
23
|
+
if (!this.device.configDescriptor) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.descriptor = this.device.configDescriptor.interfaces[this.id][this.altSetting];
|
|
28
|
+
this.interfaceNumber = this.descriptor.bInterfaceNumber;
|
|
29
|
+
this.endpoints = [];
|
|
30
|
+
const len = this.descriptor.endpoints.length;
|
|
31
|
+
for (let i = 0; i < len; i++) {
|
|
32
|
+
const desc = this.descriptor.endpoints[i];
|
|
33
|
+
const c = (desc.bEndpointAddress & LIBUSB_ENDPOINT_IN) ? InEndpoint : OutEndpoint;
|
|
34
|
+
this.endpoints[i] = new c(this.device, desc);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Claims the interface. This method must be called before using any endpoints of this interface.
|
|
40
|
+
*
|
|
41
|
+
* The device must be open to use this method.
|
|
42
|
+
*/
|
|
43
|
+
public claim(): void {
|
|
44
|
+
this.device.__claimInterface(this.id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Releases the interface and resets the alternate setting. Calls callback when complete.
|
|
49
|
+
*
|
|
50
|
+
* It is an error to release an interface with pending transfers.
|
|
51
|
+
*
|
|
52
|
+
* The device must be open to use this method.
|
|
53
|
+
* @param callback
|
|
54
|
+
*/
|
|
55
|
+
public release(callback?: (error?: LibUSBException) => void): void;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Releases the interface and resets the alternate setting. Calls callback when complete.
|
|
59
|
+
*
|
|
60
|
+
* It is an error to release an interface with pending transfers. If the optional closeEndpoints
|
|
61
|
+
* parameter is true, any active endpoint streams are stopped (see `Endpoint.stopStream`),
|
|
62
|
+
* and the interface is released after the stream transfers are cancelled. Transfers submitted
|
|
63
|
+
* individually with `Endpoint.transfer` are not affected by this parameter.
|
|
64
|
+
*
|
|
65
|
+
* The device must be open to use this method.
|
|
66
|
+
* @param closeEndpoints
|
|
67
|
+
* @param callback
|
|
68
|
+
*/
|
|
69
|
+
public release(closeEndpoints?: boolean, callback?: (error?: LibUSBException) => void): void;
|
|
70
|
+
public release(closeEndpointsOrCallback?: boolean | ((error?: LibUSBException) => void), callback?: (error: LibUSBException | undefined) => void): void {
|
|
71
|
+
|
|
72
|
+
let closeEndpoints = false;
|
|
73
|
+
if (typeof closeEndpointsOrCallback === 'boolean') {
|
|
74
|
+
closeEndpoints = closeEndpointsOrCallback;
|
|
75
|
+
} else {
|
|
76
|
+
callback = closeEndpointsOrCallback;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const next = () => {
|
|
80
|
+
this.device.__releaseInterface(this.id, error => {
|
|
81
|
+
if (!error) {
|
|
82
|
+
this.altSetting = 0;
|
|
83
|
+
this.refresh();
|
|
84
|
+
}
|
|
85
|
+
if (callback) {
|
|
86
|
+
callback.call(this, error);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
if (!closeEndpoints || this.endpoints.length === 0) {
|
|
92
|
+
next();
|
|
93
|
+
} else {
|
|
94
|
+
let n = this.endpoints.length;
|
|
95
|
+
this.endpoints.forEach(ep => {
|
|
96
|
+
if (ep.direction === 'in' && (ep as InEndpoint).pollActive) {
|
|
97
|
+
ep.once('end', () => {
|
|
98
|
+
if (--n === 0) {
|
|
99
|
+
next();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
(ep as InEndpoint).stopPoll();
|
|
103
|
+
} else {
|
|
104
|
+
if (--n === 0) {
|
|
105
|
+
next();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Returns `false` if a kernel driver is not active; `true` if active.
|
|
114
|
+
*
|
|
115
|
+
* The device must be open to use this method.
|
|
116
|
+
*/
|
|
117
|
+
public isKernelDriverActive(): boolean {
|
|
118
|
+
return this.device.__isKernelDriverActive(this.id);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Detaches the kernel driver from the interface.
|
|
123
|
+
*
|
|
124
|
+
* The device must be open to use this method.
|
|
125
|
+
*/
|
|
126
|
+
public detachKernelDriver(): void {
|
|
127
|
+
return this.device.__detachKernelDriver(this.id);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Re-attaches the kernel driver for the interface.
|
|
132
|
+
*
|
|
133
|
+
* The device must be open to use this method.
|
|
134
|
+
*/
|
|
135
|
+
public attachKernelDriver(): void {
|
|
136
|
+
return this.device.__attachKernelDriver(this.id);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Sets the alternate setting. It updates the `interface.endpoints` array to reflect the endpoints found in the alternate setting.
|
|
141
|
+
*
|
|
142
|
+
* The device must be open to use this method.
|
|
143
|
+
* @param altSetting
|
|
144
|
+
* @param callback
|
|
145
|
+
*/
|
|
146
|
+
public setAltSetting(altSetting: number, callback?: (error: LibUSBException | undefined) => void): void {
|
|
147
|
+
this.device.__setInterface(this.id, altSetting, error => {
|
|
148
|
+
if (!error) {
|
|
149
|
+
this.altSetting = altSetting;
|
|
150
|
+
this.refresh();
|
|
151
|
+
}
|
|
152
|
+
if (callback) {
|
|
153
|
+
callback.call(this, error);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Return the InEndpoint or OutEndpoint with the specified address.
|
|
160
|
+
*
|
|
161
|
+
* The device must be open to use this method.
|
|
162
|
+
* @param addr
|
|
163
|
+
*/
|
|
164
|
+
public endpoint(addr: number): Endpoint {
|
|
165
|
+
const endpoint = this.endpoints.find(item => item.address === addr);
|
|
166
|
+
if (!endpoint) {
|
|
167
|
+
throw new Error(`Endpoint not found for address: ${addr}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return endpoint;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import * as usb from '../usb';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { WebUSBDevice } from './webusb-device';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* USB Options
|
|
7
|
+
*/
|
|
8
|
+
export interface USBOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Optional `device found` callback function to allow the user to select a device
|
|
11
|
+
*/
|
|
12
|
+
devicesFound?: (devices: USBDevice[]) => Promise<USBDevice | void>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional array of preconfigured allowed devices
|
|
16
|
+
*/
|
|
17
|
+
allowedDevices?: USBDeviceFilter[];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Optional flag to automatically allow all devices
|
|
21
|
+
*/
|
|
22
|
+
allowAllDevices?: boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Optional timeout (in milliseconds) to use for the device control transfers
|
|
26
|
+
*/
|
|
27
|
+
deviceTimeout?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class WebUSB implements USB {
|
|
31
|
+
|
|
32
|
+
protected emitter = new EventEmitter();
|
|
33
|
+
protected knownDevices: Map<string, USBDevice> = new Map();
|
|
34
|
+
protected allowedDevices: USBDeviceFilter[];
|
|
35
|
+
|
|
36
|
+
constructor(private options: USBOptions = {}) {
|
|
37
|
+
this.allowedDevices = options.allowedDevices || [];
|
|
38
|
+
|
|
39
|
+
const deviceConnectCallback = async (device: usb.Device) => {
|
|
40
|
+
const webDevice = await WebUSBDevice.createInstance(device);
|
|
41
|
+
|
|
42
|
+
// When connected, emit an event if it is an allowed device
|
|
43
|
+
if (webDevice && this.isAllowedDevice(webDevice)) {
|
|
44
|
+
const deviceId = this.getDeviceId(device);
|
|
45
|
+
if (deviceId) {
|
|
46
|
+
this.knownDevices.set(deviceId, webDevice);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const event = {
|
|
50
|
+
type: 'connect',
|
|
51
|
+
device: webDevice
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
this.emitter.emit('connect', event);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const deviceDisconnectCallback = async (device: usb.Device) => {
|
|
59
|
+
const deviceId = this.getDeviceId(device);
|
|
60
|
+
|
|
61
|
+
// When disconnected, emit an event if the device was a known allowed device
|
|
62
|
+
if (deviceId !== undefined && this.knownDevices.has(deviceId)) {
|
|
63
|
+
const webDevice = this.knownDevices.get(deviceId);
|
|
64
|
+
|
|
65
|
+
if (webDevice && this.isAllowedDevice(webDevice)) {
|
|
66
|
+
const event = {
|
|
67
|
+
type: 'disconnect',
|
|
68
|
+
device: webDevice
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
this.emitter.emit('disconnect', event);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
this.emitter.on('newListener', event => {
|
|
77
|
+
const listenerCount = this.emitter.listenerCount(event);
|
|
78
|
+
|
|
79
|
+
if (listenerCount !== 0) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (event === 'connect') {
|
|
84
|
+
usb.addListener('attach', deviceConnectCallback);
|
|
85
|
+
} else if (event === 'disconnect') {
|
|
86
|
+
usb.addListener('detach', deviceDisconnectCallback);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
this.emitter.on('removeListener', event => {
|
|
91
|
+
const listenerCount = this.emitter.listenerCount(event);
|
|
92
|
+
|
|
93
|
+
if (listenerCount !== 0) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (event === 'connect') {
|
|
98
|
+
usb.removeListener('attach', deviceConnectCallback);
|
|
99
|
+
} else if (event === 'disconnect') {
|
|
100
|
+
usb.removeListener('detach', deviceDisconnectCallback);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private _onconnect: ((ev: USBConnectionEvent) => void) | undefined;
|
|
106
|
+
public set onconnect(fn: (ev: USBConnectionEvent) => void) {
|
|
107
|
+
if (this._onconnect) {
|
|
108
|
+
this.removeEventListener('connect', this._onconnect);
|
|
109
|
+
this._onconnect = undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (fn) {
|
|
113
|
+
this._onconnect = fn;
|
|
114
|
+
this.addEventListener('connect', this._onconnect);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private _ondisconnect: ((ev: USBConnectionEvent) => void) | undefined;
|
|
119
|
+
public set ondisconnect(fn: (ev: USBConnectionEvent) => void) {
|
|
120
|
+
if (this._ondisconnect) {
|
|
121
|
+
this.removeEventListener('disconnect', this._ondisconnect);
|
|
122
|
+
this._ondisconnect = undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (fn) {
|
|
126
|
+
this._ondisconnect = fn;
|
|
127
|
+
this.addEventListener('disconnect', this._ondisconnect);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public addEventListener(type: 'connect' | 'disconnect', listener: (this: this, ev: USBConnectionEvent) => void): void;
|
|
132
|
+
public addEventListener(type: 'connect' | 'disconnect', listener: EventListener): void;
|
|
133
|
+
public addEventListener(type: string, listener: (ev: USBConnectionEvent) => void): void {
|
|
134
|
+
this.emitter.addListener(type, listener);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public removeEventListener(type: 'connect' | 'disconnect', callback: (this: this, ev: USBConnectionEvent) => void): void;
|
|
138
|
+
public removeEventListener(type: 'connect' | 'disconnect', callback: EventListener): void;
|
|
139
|
+
public removeEventListener(type: string, callback: (this: this, ev: USBConnectionEvent) => void): void {
|
|
140
|
+
this.emitter.removeListener(type, callback);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public dispatchEvent(_event: Event): boolean {
|
|
144
|
+
// Don't dispatch from here
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Requests a single Web USB device
|
|
150
|
+
* @param options The options to use when scanning
|
|
151
|
+
* @returns Promise containing the selected device
|
|
152
|
+
*/
|
|
153
|
+
public async requestDevice(options?: USBDeviceRequestOptions): Promise<USBDevice> {
|
|
154
|
+
// Must have options
|
|
155
|
+
if (!options) {
|
|
156
|
+
throw new TypeError('requestDevice error: 1 argument required, but only 0 present');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Options must be an object
|
|
160
|
+
if (options.constructor !== {}.constructor) {
|
|
161
|
+
throw new TypeError('requestDevice error: parameter 1 (options) is not an object');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Must have a filter
|
|
165
|
+
if (!options.filters) {
|
|
166
|
+
throw new TypeError('requestDevice error: required member filters is undefined');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Filter must be an array
|
|
170
|
+
if (options.filters.constructor !== [].constructor) {
|
|
171
|
+
throw new TypeError('requestDevice error: the provided value cannot be converted to a sequence');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check filters
|
|
175
|
+
options.filters.forEach(filter => {
|
|
176
|
+
// Protocol & Subclass
|
|
177
|
+
if (filter.protocolCode && !filter.subclassCode) {
|
|
178
|
+
throw new TypeError('requestDevice error: subclass code is required');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Subclass & Class
|
|
182
|
+
if (filter.subclassCode && !filter.classCode) {
|
|
183
|
+
throw new TypeError('requestDevice error: class code is required');
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
let devices = await this.loadDevices(options.filters);
|
|
188
|
+
devices = devices.filter(device => this.filterDevice(options, device));
|
|
189
|
+
|
|
190
|
+
if (devices.length === 0) {
|
|
191
|
+
throw new Error('requestDevice error: no devices found');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// If no devicesFound function, select the first device found
|
|
196
|
+
const device = this.options.devicesFound ? await this.options.devicesFound(devices) : devices[0];
|
|
197
|
+
|
|
198
|
+
if (!device) {
|
|
199
|
+
throw new Error('selected device not found');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!this.isAllowedDevice(device)) {
|
|
203
|
+
this.allowedDevices.push({
|
|
204
|
+
vendorId: device.vendorId,
|
|
205
|
+
productId: device.productId,
|
|
206
|
+
serialNumber: device.serialNumber
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return device;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
throw new Error(`requestDevice error: ${error}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Gets all allowed Web USB devices which are connected
|
|
218
|
+
* @returns Promise containing an array of devices
|
|
219
|
+
*/
|
|
220
|
+
public async getDevices(): Promise<USBDevice[]> {
|
|
221
|
+
let preFilters: USBDeviceFilter[] | undefined;
|
|
222
|
+
|
|
223
|
+
if (!this.options.allowAllDevices) {
|
|
224
|
+
// Create pre-filters
|
|
225
|
+
preFilters = this.allowedDevices.map(device => ({
|
|
226
|
+
vendorId: device.vendorId || undefined,
|
|
227
|
+
productId: device.productId || undefined,
|
|
228
|
+
serialNumber: device.serialNumber || undefined
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Refresh devices and filter for allowed ones
|
|
233
|
+
const devices = await this.loadDevices(preFilters);
|
|
234
|
+
return devices.filter(device => this.isAllowedDevice(device));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private async loadDevices(preFilters?: USBDeviceFilter[]): Promise<USBDevice[]> {
|
|
238
|
+
let devices = usb.getDeviceList();
|
|
239
|
+
|
|
240
|
+
// Pre-filter devices
|
|
241
|
+
devices = this.preFilterDevices(devices, preFilters);
|
|
242
|
+
|
|
243
|
+
const webDevices: USBDevice[] = [];
|
|
244
|
+
|
|
245
|
+
for (const device of devices) {
|
|
246
|
+
if (this.options.deviceTimeout) {
|
|
247
|
+
device.timeout = this.options.deviceTimeout;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const webDevice = await WebUSBDevice.createInstance(device);
|
|
251
|
+
if (webDevice) {
|
|
252
|
+
webDevices.push(webDevice);
|
|
253
|
+
|
|
254
|
+
const deviceId = this.getDeviceId(device);
|
|
255
|
+
if (deviceId) {
|
|
256
|
+
this.knownDevices.set(deviceId, webDevice);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return webDevices;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private preFilterDevices(devices: usb.Device[], preFilters?: USBDeviceFilter[]): usb.Device[] {
|
|
265
|
+
if (!preFilters || !preFilters.length) {
|
|
266
|
+
return devices;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Just pre-filter on vid/pid
|
|
270
|
+
return devices.filter(device => preFilters.some(filter => {
|
|
271
|
+
// Vendor
|
|
272
|
+
if (filter.vendorId && filter.vendorId !== device.deviceDescriptor.idVendor) return false;
|
|
273
|
+
|
|
274
|
+
// Product
|
|
275
|
+
if (filter.productId && filter.productId !== device.deviceDescriptor.idProduct) return false;
|
|
276
|
+
|
|
277
|
+
// Ignore serial number for node-usb as it requires device connection
|
|
278
|
+
return true;
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private filterDevice(options: USBDeviceRequestOptions, device: USBDevice): boolean {
|
|
283
|
+
if (!options.filters || !options.filters.length) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return options.filters.some(filter => {
|
|
288
|
+
// Vendor
|
|
289
|
+
if (filter.vendorId && filter.vendorId !== device.vendorId) return false;
|
|
290
|
+
|
|
291
|
+
// Product
|
|
292
|
+
if (filter.productId && filter.productId !== device.productId) return false;
|
|
293
|
+
|
|
294
|
+
// Class
|
|
295
|
+
if (filter.classCode) {
|
|
296
|
+
|
|
297
|
+
if (!device.configuration) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Interface Descriptors
|
|
302
|
+
const match = device.configuration.interfaces.some(iface => {
|
|
303
|
+
// Class
|
|
304
|
+
if (filter.classCode && filter.classCode !== iface.alternate.interfaceClass) return false;
|
|
305
|
+
|
|
306
|
+
// Subclass
|
|
307
|
+
if (filter.subclassCode && filter.subclassCode !== iface.alternate.interfaceSubclass) return false;
|
|
308
|
+
|
|
309
|
+
// Protocol
|
|
310
|
+
if (filter.protocolCode && filter.protocolCode !== iface.alternate.interfaceProtocol) return false;
|
|
311
|
+
|
|
312
|
+
return true;
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (match) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Class
|
|
321
|
+
if (filter.classCode && filter.classCode !== device.deviceClass) return false;
|
|
322
|
+
|
|
323
|
+
// Subclass
|
|
324
|
+
if (filter.subclassCode && filter.subclassCode !== device.deviceSubclass) return false;
|
|
325
|
+
|
|
326
|
+
// Protocol
|
|
327
|
+
if (filter.protocolCode && filter.protocolCode !== device.deviceProtocol) return false;
|
|
328
|
+
|
|
329
|
+
// Serial
|
|
330
|
+
if (filter.serialNumber && filter.serialNumber !== device.serialNumber) return false;
|
|
331
|
+
|
|
332
|
+
return true;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private getDeviceId(device: usb.Device): string | undefined {
|
|
337
|
+
if (device.busNumber === undefined || device.deviceAddress === undefined) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return `${device.busNumber}.${device.deviceAddress}`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private isAllowedDevice(device: USBDeviceFilter): boolean {
|
|
345
|
+
if (this.options.allowAllDevices) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const isSameDevice = (device1: USBDeviceFilter, device2: USBDeviceFilter): boolean => {
|
|
350
|
+
return (device1.productId === device2.productId
|
|
351
|
+
&& device1.vendorId === device2.vendorId
|
|
352
|
+
&& device1.serialNumber === device2.serialNumber);
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
for (const i in this.allowedDevices) {
|
|
356
|
+
if (isSameDevice(device, this.allowedDevices[i])) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mutex implementation that can be used to lock access between concurrent async functions
|
|
3
|
+
*/
|
|
4
|
+
export class Mutex {
|
|
5
|
+
private locked: boolean;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a new Mutex
|
|
9
|
+
*/
|
|
10
|
+
constructor() {
|
|
11
|
+
this.locked = false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Yield the current execution context, effectively moving it to the back of the promise queue
|
|
16
|
+
*/
|
|
17
|
+
private async sleep(): Promise<void> {
|
|
18
|
+
return new Promise(resolve => setTimeout(resolve, 1));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Wait until the Mutex is available and claim it
|
|
23
|
+
*/
|
|
24
|
+
public async lock(): Promise<void> {
|
|
25
|
+
while (this.locked) {
|
|
26
|
+
await this.sleep();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.locked = true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Unlock the Mutex
|
|
34
|
+
*/
|
|
35
|
+
public unlock(): void {
|
|
36
|
+
this.locked = false;
|
|
37
|
+
}
|
|
38
|
+
}
|