0
# WebUSB Compatibility
1
2
WebUSB compatibility provides a browser-compatible API that mimics the navigator.usb interface for cross-platform USB device access. This enables web applications and Node.js applications to use the same API patterns for USB communication.
3
4
## Capabilities
5
6
### WebUSB Interface
7
8
Main WebUSB interface compatible with browser navigator.usb.
9
10
```typescript { .api }
11
/**
12
* WebUSB Class constructor for creating custom WebUSB instances
13
*/
14
class WebUSB implements USB {
15
/**
16
* Create a new WebUSB instance with custom options
17
* @param options - USB configuration options
18
*/
19
constructor(options?: USBOptions);
20
}
21
22
/**
23
* USB Options for configuring WebUSB behavior
24
*/
25
interface USBOptions {
26
/** Optional device found callback for user device selection */
27
devicesFound?: (devices: USBDevice[]) => Promise<USBDevice | void>;
28
29
/** Optional array of preconfigured allowed devices */
30
allowedDevices?: USBDeviceFilter[];
31
32
/** Optional flag to automatically allow all devices */
33
allowAllDevices?: boolean;
34
35
/** Optional timeout in milliseconds for device control transfers */
36
deviceTimeout?: number;
37
38
/** Optional flag to enable/disable automatic kernel driver detaching */
39
autoDetachKernelDriver?: boolean;
40
}
41
42
/**
43
* WebUSB interface compatible with navigator.usb
44
*/
45
interface USB {
46
/**
47
* Request access to USB device with filters
48
* @param options - Device request options with filters
49
* @returns Promise resolving to selected USBDevice
50
*/
51
requestDevice(options?: USBDeviceRequestOptions): Promise<USBDevice>;
52
53
/**
54
* Get previously authorized devices
55
* @returns Promise resolving to array of authorized USBDevice objects
56
*/
57
getDevices(): Promise<USBDevice[]>;
58
59
/**
60
* Connect event handler
61
*/
62
onconnect: ((ev: USBConnectionEvent) => void) | undefined;
63
64
/**
65
* Disconnect event handler
66
*/
67
ondisconnect: ((ev: USBConnectionEvent) => void) | undefined;
68
69
/**
70
* Add event listener for connect/disconnect events
71
* @param type - Event type ('connect' or 'disconnect')
72
* @param listener - Event listener function
73
*/
74
addEventListener(type: 'connect' | 'disconnect', listener: (ev: USBConnectionEvent) => void): void;
75
76
/**
77
* Remove event listener
78
* @param type - Event type ('connect' or 'disconnect')
79
* @param callback - Event listener function to remove
80
*/
81
removeEventListener(type: 'connect' | 'disconnect', callback: (ev: USBConnectionEvent) => void): void;
82
}
83
84
/**
85
* Get WebUSB interface (browser navigator.usb or Node.js implementation)
86
* Returns navigator.usb if available in browser, otherwise returns Node.js WebUSB instance
87
* @returns USB interface
88
*/
89
function getWebUsb(): USB;
90
91
/**
92
* WebUSBDevice static factory method
93
*/
94
class WebUSBDevice {
95
/**
96
* Create WebUSBDevice instance from legacy Device
97
* @param device - Legacy USB Device object
98
* @param autoDetachKernelDriver - Whether to auto-detach kernel drivers
99
* @returns Promise resolving to WebUSBDevice instance
100
*/
101
static createInstance(device: Device, autoDetachKernelDriver?: boolean): Promise<WebUSBDevice>;
102
}
103
```
104
105
**Usage Examples:**
106
107
```typescript
108
import { webusb, getWebUsb, WebUSB } from 'usb';
109
110
// Use the default WebUSB instance
111
console.log('Using default WebUSB instance');
112
113
// Create custom WebUSB instance with options
114
const customWebUSB = new WebUSB({
115
allowAllDevices: true,
116
deviceTimeout: 5000,
117
autoDetachKernelDriver: true,
118
devicesFound: async (devices) => {
119
// Custom device selection logic
120
console.log(`Found ${devices.length} devices`);
121
return devices.find(device => device.vendorId === 0x1234);
122
}
123
});
124
125
// Use custom instance
126
const device = await customWebUSB.requestDevice({
127
filters: [{ vendorId: 0x1234 }]
128
});
129
130
// Or get WebUSB interface (browser or Node.js)
131
const usb = getWebUsb();
132
console.log('Got WebUSB interface');
133
134
// Request device access with filters
135
async function requestUSBDevice() {
136
try {
137
const device = await webusb.requestDevice({
138
filters: [
139
{
140
vendorId: 0x1234,
141
productId: 0x5678
142
},
143
{
144
vendorId: 0xABCD,
145
classCode: 3 // HID class
146
}
147
]
148
});
149
150
console.log('Device selected:', device.productName);
151
console.log(`VID:PID = ${device.vendorId.toString(16)}:${device.productId.toString(16)}`);
152
153
return device;
154
} catch (error) {
155
console.error('Failed to request device:', error.message);
156
return null;
157
}
158
}
159
160
// Get previously authorized devices
161
async function getAuthorizedDevices() {
162
try {
163
const devices = await webusb.getDevices();
164
console.log(`Found ${devices.length} authorized devices`);
165
166
devices.forEach((device, index) => {
167
console.log(`Device ${index}:`);
168
console.log(` Name: ${device.productName || 'Unknown'}`);
169
console.log(` Manufacturer: ${device.manufacturerName || 'Unknown'}`);
170
console.log(` VID:PID = ${device.vendorId.toString(16)}:${device.productId.toString(16)}`);
171
});
172
173
return devices;
174
} catch (error) {
175
console.error('Failed to get devices:', error.message);
176
return [];
177
}
178
}
179
```
180
181
### WebUSB Device Interface
182
183
WebUSB device interface providing browser-compatible device access.
184
185
```typescript { .api }
186
/**
187
* WebUSB Device interface
188
*/
189
interface USBDevice {
190
/** USB version major number */
191
readonly usbVersionMajor: number;
192
193
/** USB version minor number */
194
readonly usbVersionMinor: number;
195
196
/** USB version subminor number */
197
readonly usbVersionSubminor: number;
198
199
/** Device class code */
200
readonly deviceClass: number;
201
202
/** Device subclass code */
203
readonly deviceSubclass: number;
204
205
/** Device protocol code */
206
readonly deviceProtocol: number;
207
208
/** Vendor ID */
209
readonly vendorId: number;
210
211
/** Product ID */
212
readonly productId: number;
213
214
/** Device version major number */
215
readonly deviceVersionMajor: number;
216
217
/** Device version minor number */
218
readonly deviceVersionMinor: number;
219
220
/** Device version subminor number */
221
readonly deviceVersionSubminor: number;
222
223
/** Manufacturer name string */
224
readonly manufacturerName?: string;
225
226
/** Product name string */
227
readonly productName?: string;
228
229
/** Serial number string */
230
readonly serialNumber?: string;
231
232
/** Array of device configurations */
233
readonly configurations: USBConfiguration[];
234
235
/** Current active configuration */
236
readonly configuration?: USBConfiguration;
237
238
/** Whether device is currently open */
239
readonly opened: boolean;
240
}
241
```
242
243
**Usage Examples:**
244
245
```typescript
246
import { webusb } from 'usb';
247
248
async function exploreUSBDevice() {
249
const device = await webusb.requestDevice({
250
filters: [{ vendorId: 0x1234 }]
251
});
252
253
if (device) {
254
console.log('Device Information:');
255
console.log(` USB Version: ${device.usbVersionMajor}.${device.usbVersionMinor}.${device.usbVersionSubminor}`);
256
console.log(` Device Class: ${device.deviceClass}`);
257
console.log(` Device Subclass: ${device.deviceSubclass}`);
258
console.log(` Device Protocol: ${device.deviceProtocol}`);
259
console.log(` Vendor ID: 0x${device.vendorId.toString(16).padStart(4, '0')}`);
260
console.log(` Product ID: 0x${device.productId.toString(16).padStart(4, '0')}`);
261
console.log(` Device Version: ${device.deviceVersionMajor}.${device.deviceVersionMinor}.${device.deviceVersionSubminor}`);
262
263
if (device.manufacturerName) console.log(` Manufacturer: ${device.manufacturerName}`);
264
if (device.productName) console.log(` Product: ${device.productName}`);
265
if (device.serialNumber) console.log(` Serial: ${device.serialNumber}`);
266
267
console.log(` Configurations: ${device.configurations.length}`);
268
console.log(` Currently open: ${device.opened}`);
269
270
// Explore configurations
271
device.configurations.forEach((config, index) => {
272
console.log(` Configuration ${index}:`);
273
console.log(` Value: ${config.configurationValue}`);
274
console.log(` Name: ${config.configurationName || 'Unnamed'}`);
275
console.log(` Interfaces: ${config.interfaces.length}`);
276
});
277
}
278
}
279
```
280
281
### Device Operations
282
283
Open, close, and configure WebUSB devices.
284
285
```typescript { .api }
286
/**
287
* Device operation methods
288
*/
289
interface USBDevice {
290
/**
291
* Open device for communication
292
* @returns Promise that resolves when device is opened
293
*/
294
open(): Promise<void>;
295
296
/**
297
* Close device
298
* @returns Promise that resolves when device is closed
299
*/
300
close(): Promise<void>;
301
302
/**
303
* Select device configuration
304
* @param configurationValue - Configuration value to select
305
* @returns Promise that resolves when configuration is set
306
*/
307
selectConfiguration(configurationValue: number): Promise<void>;
308
309
/**
310
* Reset the device
311
* @returns Promise that resolves when device is reset
312
*/
313
reset(): Promise<void>;
314
}
315
```
316
317
**Usage Examples:**
318
319
```typescript
320
import { webusb } from 'usb';
321
322
async function basicDeviceOperations() {
323
const device = await webusb.requestDevice({
324
filters: [{ vendorId: 0x1234, productId: 0x5678 }]
325
});
326
327
if (!device) {
328
console.log('No device selected');
329
return;
330
}
331
332
try {
333
// Open device
334
console.log('Opening device...');
335
await device.open();
336
console.log(`Device opened: ${device.opened}`);
337
338
// Select configuration if needed (usually configuration 1)
339
if (device.configurations.length > 1) {
340
console.log('Selecting configuration 1...');
341
await device.selectConfiguration(1);
342
console.log('Configuration selected');
343
}
344
345
// Device is now ready for interface and endpoint operations
346
console.log('Device ready for communication');
347
348
// Perform operations...
349
350
} catch (error) {
351
console.error('Device operation failed:', error.message);
352
} finally {
353
// Always close device when done
354
if (device.opened) {
355
console.log('Closing device...');
356
await device.close();
357
console.log('Device closed');
358
}
359
}
360
}
361
362
// Device reset example
363
async function resetDevice() {
364
const device = await webusb.requestDevice({
365
filters: [{ vendorId: 0x1234 }]
366
});
367
368
if (device) {
369
try {
370
await device.open();
371
372
console.log('Resetting device...');
373
await device.reset();
374
console.log('Device reset completed');
375
376
// Note: After reset, device may re-enumerate
377
// You may need to close and re-request the device
378
379
} catch (error) {
380
console.error('Reset failed:', error.message);
381
} finally {
382
if (device.opened) {
383
await device.close();
384
}
385
}
386
}
387
}
388
```
389
390
### Interface Management
391
392
Claim and manage device interfaces in WebUSB style.
393
394
```typescript { .api }
395
/**
396
* Interface management methods
397
*/
398
interface USBDevice {
399
/**
400
* Claim interface for exclusive access
401
* @param interfaceNumber - Interface number to claim
402
* @returns Promise that resolves when interface is claimed
403
*/
404
claimInterface(interfaceNumber: number): Promise<void>;
405
406
/**
407
* Release previously claimed interface
408
* @param interfaceNumber - Interface number to release
409
* @returns Promise that resolves when interface is released
410
*/
411
releaseInterface(interfaceNumber: number): Promise<void>;
412
413
/**
414
* Select alternate interface setting
415
* @param interfaceNumber - Interface number
416
* @param alternateSetting - Alternate setting number
417
* @returns Promise that resolves when alternate setting is selected
418
*/
419
selectAlternateInterface(interfaceNumber: number, alternateSetting: number): Promise<void>;
420
}
421
```
422
423
**Usage Examples:**
424
425
```typescript
426
import { webusb } from 'usb';
427
428
async function manageInterfaces() {
429
const device = await webusb.requestDevice({
430
filters: [{ vendorId: 0x1234 }]
431
});
432
433
if (device) {
434
try {
435
await device.open();
436
437
// Claim interface 0
438
console.log('Claiming interface 0...');
439
await device.claimInterface(0);
440
console.log('Interface 0 claimed');
441
442
// Check if interface has alternate settings
443
const config = device.configuration;
444
if (config) {
445
const interface0 = config.interfaces.find(iface => iface.interfaceNumber === 0);
446
if (interface0 && interface0.alternates.length > 1) {
447
console.log(`Interface has ${interface0.alternates.length} alternate settings`);
448
449
// Select alternate setting 1
450
console.log('Selecting alternate setting 1...');
451
await device.selectAlternateInterface(0, 1);
452
console.log('Alternate setting 1 selected');
453
}
454
}
455
456
// Use interface for communication...
457
console.log('Interface ready for use');
458
459
// Release interface when done
460
console.log('Releasing interface 0...');
461
await device.releaseInterface(0);
462
console.log('Interface 0 released');
463
464
} catch (error) {
465
console.error('Interface management failed:', error.message);
466
} finally {
467
await device.close();
468
}
469
}
470
}
471
472
// Multiple interface management
473
async function manageMultipleInterfaces() {
474
const device = await webusb.requestDevice({
475
filters: [{ classCode: 9 }] // Hub class devices often have multiple interfaces
476
});
477
478
if (device) {
479
try {
480
await device.open();
481
482
const config = device.configuration;
483
if (config) {
484
console.log(`Device has ${config.interfaces.length} interfaces`);
485
486
// Claim all available interfaces
487
const claimedInterfaces: number[] = [];
488
489
for (const iface of config.interfaces) {
490
try {
491
console.log(`Claiming interface ${iface.interfaceNumber}...`);
492
await device.claimInterface(iface.interfaceNumber);
493
claimedInterfaces.push(iface.interfaceNumber);
494
console.log(`Interface ${iface.interfaceNumber} claimed`);
495
} catch (error) {
496
console.warn(`Failed to claim interface ${iface.interfaceNumber}:`, error.message);
497
}
498
}
499
500
console.log(`Successfully claimed ${claimedInterfaces.length} interfaces`);
501
502
// Use interfaces...
503
504
// Release all claimed interfaces
505
for (const interfaceNum of claimedInterfaces) {
506
try {
507
await device.releaseInterface(interfaceNum);
508
console.log(`Interface ${interfaceNum} released`);
509
} catch (error) {
510
console.warn(`Failed to release interface ${interfaceNum}:`, error.message);
511
}
512
}
513
}
514
515
} catch (error) {
516
console.error('Multiple interface management failed:', error.message);
517
} finally {
518
await device.close();
519
}
520
}
521
}
522
```
523
524
### Data Transfers
525
526
Perform control and bulk/interrupt transfers using WebUSB API.
527
528
```typescript { .api }
529
/**
530
* Data transfer methods
531
*/
532
interface USBDevice {
533
/**
534
* Control transfer IN (device to host)
535
* @param setup - Transfer setup parameters
536
* @param length - Number of bytes to read
537
* @returns Promise resolving to transfer result
538
*/
539
controlTransferIn(setup: USBControlTransferParameters, length: number): Promise<USBInTransferResult>;
540
541
/**
542
* Control transfer OUT (host to device)
543
* @param setup - Transfer setup parameters
544
* @param data - Data to send (optional)
545
* @returns Promise resolving to transfer result
546
*/
547
controlTransferOut(setup: USBControlTransferParameters, data?: ArrayBuffer): Promise<USBOutTransferResult>;
548
549
/**
550
* Bulk/Interrupt transfer IN
551
* @param endpointNumber - Endpoint number (without direction bit)
552
* @param length - Number of bytes to read
553
* @returns Promise resolving to transfer result
554
*/
555
transferIn(endpointNumber: number, length: number): Promise<USBInTransferResult>;
556
557
/**
558
* Bulk/Interrupt transfer OUT
559
* @param endpointNumber - Endpoint number (without direction bit)
560
* @param data - Data to send
561
* @returns Promise resolving to transfer result
562
*/
563
transferOut(endpointNumber: number, data: ArrayBuffer): Promise<USBOutTransferResult>;
564
565
/**
566
* Clear halt condition on endpoint
567
* @param direction - Endpoint direction ('in' or 'out')
568
* @param endpointNumber - Endpoint number
569
* @returns Promise that resolves when halt is cleared
570
*/
571
clearHalt(direction: USBDirection, endpointNumber: number): Promise<void>;
572
}
573
574
/**
575
* Control transfer setup parameters
576
*/
577
interface USBControlTransferParameters {
578
requestType: 'standard' | 'class' | 'vendor';
579
recipient: 'device' | 'interface' | 'endpoint' | 'other';
580
request: number;
581
value: number;
582
index: number;
583
}
584
585
/**
586
* Transfer results
587
*/
588
interface USBInTransferResult {
589
data?: DataView;
590
status: 'ok' | 'stall' | 'babble';
591
}
592
593
interface USBOutTransferResult {
594
bytesWritten: number;
595
status: 'ok' | 'stall';
596
}
597
```
598
599
**Usage Examples:**
600
601
```typescript
602
import { webusb } from 'usb';
603
604
async function performDataTransfers() {
605
const device = await webusb.requestDevice({
606
filters: [{ vendorId: 0x1234, productId: 0x5678 }]
607
});
608
609
if (device) {
610
try {
611
await device.open();
612
await device.claimInterface(0);
613
614
// Control transfer IN - Get device descriptor
615
console.log('Performing control transfer IN...');
616
const controlResult = await device.controlTransferIn({
617
requestType: 'standard',
618
recipient: 'device',
619
request: 0x06, // GET_DESCRIPTOR
620
value: 0x0100, // Device descriptor
621
index: 0x0000
622
}, 18); // Device descriptor is 18 bytes
623
624
if (controlResult.status === 'ok' && controlResult.data) {
625
console.log(`Received ${controlResult.data.byteLength} bytes`);
626
const view = controlResult.data;
627
console.log(`Device descriptor bLength: ${view.getUint8(0)}`);
628
console.log(`Device descriptor bDescriptorType: ${view.getUint8(1)}`);
629
}
630
631
// Control transfer OUT - Vendor-specific command
632
console.log('Performing control transfer OUT...');
633
const commandData = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
634
const outResult = await device.controlTransferOut({
635
requestType: 'vendor',
636
recipient: 'device',
637
request: 0x01,
638
value: 0x1234,
639
index: 0x0000
640
}, commandData.buffer);
641
642
if (outResult.status === 'ok') {
643
console.log(`Sent ${outResult.bytesWritten} bytes`);
644
}
645
646
// Bulk transfer IN
647
console.log('Performing bulk transfer IN...');
648
const bulkInResult = await device.transferIn(1, 64); // Endpoint 1, 64 bytes
649
650
if (bulkInResult.status === 'ok' && bulkInResult.data) {
651
console.log(`Bulk IN received ${bulkInResult.data.byteLength} bytes`);
652
653
// Convert DataView to hex string for display
654
const bytes = new Uint8Array(bulkInResult.data.buffer);
655
const hexString = Array.from(bytes)
656
.map(b => b.toString(16).padStart(2, '0'))
657
.join(' ');
658
console.log(`Data: ${hexString}`);
659
}
660
661
// Bulk transfer OUT
662
console.log('Performing bulk transfer OUT...');
663
const bulkData = new Uint8Array([0xFF, 0xAA, 0x55, 0x00]);
664
const bulkOutResult = await device.transferOut(2, bulkData.buffer); // Endpoint 2
665
666
if (bulkOutResult.status === 'ok') {
667
console.log(`Bulk OUT sent ${bulkOutResult.bytesWritten} bytes`);
668
}
669
670
await device.releaseInterface(0);
671
672
} catch (error) {
673
console.error('Transfer failed:', error.message);
674
} finally {
675
await device.close();
676
}
677
}
678
}
679
680
// Handle transfer errors and retries
681
async function robustTransfer() {
682
const device = await webusb.requestDevice({
683
filters: [{ vendorId: 0x1234 }]
684
});
685
686
if (device) {
687
try {
688
await device.open();
689
await device.claimInterface(0);
690
691
// Robust bulk transfer with error handling
692
const performBulkTransferWithRetry = async (endpointNumber: number, length: number, maxRetries: number = 3) => {
693
for (let attempt = 1; attempt <= maxRetries; attempt++) {
694
try {
695
console.log(`Transfer attempt ${attempt}/${maxRetries}`);
696
697
const result = await device.transferIn(endpointNumber, length);
698
699
if (result.status === 'ok') {
700
console.log('Transfer successful');
701
return result.data;
702
} else if (result.status === 'stall') {
703
console.log('Endpoint stalled, clearing halt...');
704
await device.clearHalt('in', endpointNumber);
705
706
if (attempt === maxRetries) {
707
throw new Error('Transfer stalled after clearing halt');
708
}
709
} else {
710
throw new Error(`Transfer failed with status: ${result.status}`);
711
}
712
713
} catch (error) {
714
console.error(`Attempt ${attempt} failed:`, error.message);
715
716
if (attempt === maxRetries) {
717
throw error;
718
}
719
720
// Wait before retry
721
await new Promise(resolve => setTimeout(resolve, 100 * attempt));
722
}
723
}
724
};
725
726
const data = await performBulkTransferWithRetry(1, 64);
727
if (data) {
728
console.log(`Successfully received ${data.byteLength} bytes`);
729
}
730
731
await device.releaseInterface(0);
732
733
} catch (error) {
734
console.error('Robust transfer failed:', error.message);
735
} finally {
736
await device.close();
737
}
738
}
739
}
740
```
741
742
### Event Handling
743
744
Handle WebUSB connect and disconnect events.
745
746
```typescript { .api }
747
/**
748
* WebUSB events
749
*/
750
interface USBConnectionEvent {
751
type: 'connect' | 'disconnect';
752
device: USBDevice;
753
}
754
```
755
756
**Usage Examples:**
757
758
```typescript
759
import { webusb } from 'usb';
760
761
// Set up WebUSB event handlers
762
function setupWebUSBEvents() {
763
// Using event handler properties
764
webusb.onconnect = (event) => {
765
console.log('WebUSB device connected:', event.device.productName);
766
handleDeviceConnect(event.device);
767
};
768
769
webusb.ondisconnect = (event) => {
770
console.log('WebUSB device disconnected:', event.device.productName);
771
handleDeviceDisconnect(event.device);
772
};
773
774
// Using addEventListener (alternative approach)
775
webusb.addEventListener('connect', (event) => {
776
console.log('Connect event listener:', event.device.productName);
777
});
778
779
webusb.addEventListener('disconnect', (event) => {
780
console.log('Disconnect event listener:', event.device.productName);
781
});
782
}
783
784
function handleDeviceConnect(device: USBDevice) {
785
console.log(`Device connected: ${device.productName || 'Unknown'}`);
786
console.log(` VID:PID = ${device.vendorId.toString(16)}:${device.productId.toString(16)}`);
787
788
// Automatically set up device if it's a known type
789
if (device.vendorId === 0x1234 && device.productId === 0x5678) {
790
setupKnownDevice(device);
791
}
792
}
793
794
function handleDeviceDisconnect(device: USBDevice) {
795
console.log(`Device disconnected: ${device.productName || 'Unknown'}`);
796
797
// Clean up any resources associated with this device
798
cleanupDeviceResources(device);
799
}
800
801
async function setupKnownDevice(device: USBDevice) {
802
try {
803
await device.open();
804
await device.claimInterface(0);
805
806
console.log('Known device set up and ready for communication');
807
808
// Device is ready for use
809
// Store reference for later use
810
global.connectedKnownDevice = device;
811
812
} catch (error) {
813
console.error('Failed to set up known device:', error.message);
814
}
815
}
816
817
function cleanupDeviceResources(device: USBDevice) {
818
if (global.connectedKnownDevice === device) {
819
console.log('Cleaning up known device resources');
820
global.connectedKnownDevice = null;
821
822
// The device is already disconnected, so we don't need to close it
823
// Just clean up any timers, polling, or other resources
824
}
825
}
826
827
// Initialize event handling
828
setupWebUSBEvents();
829
console.log('WebUSB event handlers set up');
830
```