Cross-platform Node.js library for USB device communication with both legacy and WebUSB-compatible APIs.
—
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.
Main WebUSB interface compatible with browser navigator.usb.
/**
* WebUSB Class constructor for creating custom WebUSB instances
*/
class WebUSB implements USB {
/**
* Create a new WebUSB instance with custom options
* @param options - USB configuration options
*/
constructor(options?: USBOptions);
}
/**
* USB Options for configuring WebUSB behavior
*/
interface USBOptions {
/** Optional device found callback for user device selection */
devicesFound?: (devices: USBDevice[]) => Promise<USBDevice | void>;
/** Optional array of preconfigured allowed devices */
allowedDevices?: USBDeviceFilter[];
/** Optional flag to automatically allow all devices */
allowAllDevices?: boolean;
/** Optional timeout in milliseconds for device control transfers */
deviceTimeout?: number;
/** Optional flag to enable/disable automatic kernel driver detaching */
autoDetachKernelDriver?: boolean;
}
/**
* WebUSB interface compatible with navigator.usb
*/
interface USB {
/**
* Request access to USB device with filters
* @param options - Device request options with filters
* @returns Promise resolving to selected USBDevice
*/
requestDevice(options?: USBDeviceRequestOptions): Promise<USBDevice>;
/**
* Get previously authorized devices
* @returns Promise resolving to array of authorized USBDevice objects
*/
getDevices(): Promise<USBDevice[]>;
/**
* Connect event handler
*/
onconnect: ((ev: USBConnectionEvent) => void) | undefined;
/**
* Disconnect event handler
*/
ondisconnect: ((ev: USBConnectionEvent) => void) | undefined;
/**
* Add event listener for connect/disconnect events
* @param type - Event type ('connect' or 'disconnect')
* @param listener - Event listener function
*/
addEventListener(type: 'connect' | 'disconnect', listener: (ev: USBConnectionEvent) => void): void;
/**
* Remove event listener
* @param type - Event type ('connect' or 'disconnect')
* @param callback - Event listener function to remove
*/
removeEventListener(type: 'connect' | 'disconnect', callback: (ev: USBConnectionEvent) => void): void;
}
/**
* Get WebUSB interface (browser navigator.usb or Node.js implementation)
* Returns navigator.usb if available in browser, otherwise returns Node.js WebUSB instance
* @returns USB interface
*/
function getWebUsb(): USB;
/**
* WebUSBDevice static factory method
*/
class WebUSBDevice {
/**
* Create WebUSBDevice instance from legacy Device
* @param device - Legacy USB Device object
* @param autoDetachKernelDriver - Whether to auto-detach kernel drivers
* @returns Promise resolving to WebUSBDevice instance
*/
static createInstance(device: Device, autoDetachKernelDriver?: boolean): Promise<WebUSBDevice>;
}Usage Examples:
import { webusb, getWebUsb, WebUSB } from 'usb';
// Use the default WebUSB instance
console.log('Using default WebUSB instance');
// Create custom WebUSB instance with options
const customWebUSB = new WebUSB({
allowAllDevices: true,
deviceTimeout: 5000,
autoDetachKernelDriver: true,
devicesFound: async (devices) => {
// Custom device selection logic
console.log(`Found ${devices.length} devices`);
return devices.find(device => device.vendorId === 0x1234);
}
});
// Use custom instance
const device = await customWebUSB.requestDevice({
filters: [{ vendorId: 0x1234 }]
});
// Or get WebUSB interface (browser or Node.js)
const usb = getWebUsb();
console.log('Got WebUSB interface');
// Request device access with filters
async function requestUSBDevice() {
try {
const device = await webusb.requestDevice({
filters: [
{
vendorId: 0x1234,
productId: 0x5678
},
{
vendorId: 0xABCD,
classCode: 3 // HID class
}
]
});
console.log('Device selected:', device.productName);
console.log(`VID:PID = ${device.vendorId.toString(16)}:${device.productId.toString(16)}`);
return device;
} catch (error) {
console.error('Failed to request device:', error.message);
return null;
}
}
// Get previously authorized devices
async function getAuthorizedDevices() {
try {
const devices = await webusb.getDevices();
console.log(`Found ${devices.length} authorized devices`);
devices.forEach((device, index) => {
console.log(`Device ${index}:`);
console.log(` Name: ${device.productName || 'Unknown'}`);
console.log(` Manufacturer: ${device.manufacturerName || 'Unknown'}`);
console.log(` VID:PID = ${device.vendorId.toString(16)}:${device.productId.toString(16)}`);
});
return devices;
} catch (error) {
console.error('Failed to get devices:', error.message);
return [];
}
}WebUSB device interface providing browser-compatible device access.
/**
* WebUSB Device interface
*/
interface USBDevice {
/** USB version major number */
readonly usbVersionMajor: number;
/** USB version minor number */
readonly usbVersionMinor: number;
/** USB version subminor number */
readonly usbVersionSubminor: number;
/** Device class code */
readonly deviceClass: number;
/** Device subclass code */
readonly deviceSubclass: number;
/** Device protocol code */
readonly deviceProtocol: number;
/** Vendor ID */
readonly vendorId: number;
/** Product ID */
readonly productId: number;
/** Device version major number */
readonly deviceVersionMajor: number;
/** Device version minor number */
readonly deviceVersionMinor: number;
/** Device version subminor number */
readonly deviceVersionSubminor: number;
/** Manufacturer name string */
readonly manufacturerName?: string;
/** Product name string */
readonly productName?: string;
/** Serial number string */
readonly serialNumber?: string;
/** Array of device configurations */
readonly configurations: USBConfiguration[];
/** Current active configuration */
readonly configuration?: USBConfiguration;
/** Whether device is currently open */
readonly opened: boolean;
}Usage Examples:
import { webusb } from 'usb';
async function exploreUSBDevice() {
const device = await webusb.requestDevice({
filters: [{ vendorId: 0x1234 }]
});
if (device) {
console.log('Device Information:');
console.log(` USB Version: ${device.usbVersionMajor}.${device.usbVersionMinor}.${device.usbVersionSubminor}`);
console.log(` Device Class: ${device.deviceClass}`);
console.log(` Device Subclass: ${device.deviceSubclass}`);
console.log(` Device Protocol: ${device.deviceProtocol}`);
console.log(` Vendor ID: 0x${device.vendorId.toString(16).padStart(4, '0')}`);
console.log(` Product ID: 0x${device.productId.toString(16).padStart(4, '0')}`);
console.log(` Device Version: ${device.deviceVersionMajor}.${device.deviceVersionMinor}.${device.deviceVersionSubminor}`);
if (device.manufacturerName) console.log(` Manufacturer: ${device.manufacturerName}`);
if (device.productName) console.log(` Product: ${device.productName}`);
if (device.serialNumber) console.log(` Serial: ${device.serialNumber}`);
console.log(` Configurations: ${device.configurations.length}`);
console.log(` Currently open: ${device.opened}`);
// Explore configurations
device.configurations.forEach((config, index) => {
console.log(` Configuration ${index}:`);
console.log(` Value: ${config.configurationValue}`);
console.log(` Name: ${config.configurationName || 'Unnamed'}`);
console.log(` Interfaces: ${config.interfaces.length}`);
});
}
}Open, close, and configure WebUSB devices.
/**
* Device operation methods
*/
interface USBDevice {
/**
* Open device for communication
* @returns Promise that resolves when device is opened
*/
open(): Promise<void>;
/**
* Close device
* @returns Promise that resolves when device is closed
*/
close(): Promise<void>;
/**
* Select device configuration
* @param configurationValue - Configuration value to select
* @returns Promise that resolves when configuration is set
*/
selectConfiguration(configurationValue: number): Promise<void>;
/**
* Reset the device
* @returns Promise that resolves when device is reset
*/
reset(): Promise<void>;
}Usage Examples:
import { webusb } from 'usb';
async function basicDeviceOperations() {
const device = await webusb.requestDevice({
filters: [{ vendorId: 0x1234, productId: 0x5678 }]
});
if (!device) {
console.log('No device selected');
return;
}
try {
// Open device
console.log('Opening device...');
await device.open();
console.log(`Device opened: ${device.opened}`);
// Select configuration if needed (usually configuration 1)
if (device.configurations.length > 1) {
console.log('Selecting configuration 1...');
await device.selectConfiguration(1);
console.log('Configuration selected');
}
// Device is now ready for interface and endpoint operations
console.log('Device ready for communication');
// Perform operations...
} catch (error) {
console.error('Device operation failed:', error.message);
} finally {
// Always close device when done
if (device.opened) {
console.log('Closing device...');
await device.close();
console.log('Device closed');
}
}
}
// Device reset example
async function resetDevice() {
const device = await webusb.requestDevice({
filters: [{ vendorId: 0x1234 }]
});
if (device) {
try {
await device.open();
console.log('Resetting device...');
await device.reset();
console.log('Device reset completed');
// Note: After reset, device may re-enumerate
// You may need to close and re-request the device
} catch (error) {
console.error('Reset failed:', error.message);
} finally {
if (device.opened) {
await device.close();
}
}
}
}Claim and manage device interfaces in WebUSB style.
/**
* Interface management methods
*/
interface USBDevice {
/**
* Claim interface for exclusive access
* @param interfaceNumber - Interface number to claim
* @returns Promise that resolves when interface is claimed
*/
claimInterface(interfaceNumber: number): Promise<void>;
/**
* Release previously claimed interface
* @param interfaceNumber - Interface number to release
* @returns Promise that resolves when interface is released
*/
releaseInterface(interfaceNumber: number): Promise<void>;
/**
* Select alternate interface setting
* @param interfaceNumber - Interface number
* @param alternateSetting - Alternate setting number
* @returns Promise that resolves when alternate setting is selected
*/
selectAlternateInterface(interfaceNumber: number, alternateSetting: number): Promise<void>;
}Usage Examples:
import { webusb } from 'usb';
async function manageInterfaces() {
const device = await webusb.requestDevice({
filters: [{ vendorId: 0x1234 }]
});
if (device) {
try {
await device.open();
// Claim interface 0
console.log('Claiming interface 0...');
await device.claimInterface(0);
console.log('Interface 0 claimed');
// Check if interface has alternate settings
const config = device.configuration;
if (config) {
const interface0 = config.interfaces.find(iface => iface.interfaceNumber === 0);
if (interface0 && interface0.alternates.length > 1) {
console.log(`Interface has ${interface0.alternates.length} alternate settings`);
// Select alternate setting 1
console.log('Selecting alternate setting 1...');
await device.selectAlternateInterface(0, 1);
console.log('Alternate setting 1 selected');
}
}
// Use interface for communication...
console.log('Interface ready for use');
// Release interface when done
console.log('Releasing interface 0...');
await device.releaseInterface(0);
console.log('Interface 0 released');
} catch (error) {
console.error('Interface management failed:', error.message);
} finally {
await device.close();
}
}
}
// Multiple interface management
async function manageMultipleInterfaces() {
const device = await webusb.requestDevice({
filters: [{ classCode: 9 }] // Hub class devices often have multiple interfaces
});
if (device) {
try {
await device.open();
const config = device.configuration;
if (config) {
console.log(`Device has ${config.interfaces.length} interfaces`);
// Claim all available interfaces
const claimedInterfaces: number[] = [];
for (const iface of config.interfaces) {
try {
console.log(`Claiming interface ${iface.interfaceNumber}...`);
await device.claimInterface(iface.interfaceNumber);
claimedInterfaces.push(iface.interfaceNumber);
console.log(`Interface ${iface.interfaceNumber} claimed`);
} catch (error) {
console.warn(`Failed to claim interface ${iface.interfaceNumber}:`, error.message);
}
}
console.log(`Successfully claimed ${claimedInterfaces.length} interfaces`);
// Use interfaces...
// Release all claimed interfaces
for (const interfaceNum of claimedInterfaces) {
try {
await device.releaseInterface(interfaceNum);
console.log(`Interface ${interfaceNum} released`);
} catch (error) {
console.warn(`Failed to release interface ${interfaceNum}:`, error.message);
}
}
}
} catch (error) {
console.error('Multiple interface management failed:', error.message);
} finally {
await device.close();
}
}
}Perform control and bulk/interrupt transfers using WebUSB API.
/**
* Data transfer methods
*/
interface USBDevice {
/**
* Control transfer IN (device to host)
* @param setup - Transfer setup parameters
* @param length - Number of bytes to read
* @returns Promise resolving to transfer result
*/
controlTransferIn(setup: USBControlTransferParameters, length: number): Promise<USBInTransferResult>;
/**
* Control transfer OUT (host to device)
* @param setup - Transfer setup parameters
* @param data - Data to send (optional)
* @returns Promise resolving to transfer result
*/
controlTransferOut(setup: USBControlTransferParameters, data?: ArrayBuffer): Promise<USBOutTransferResult>;
/**
* Bulk/Interrupt transfer IN
* @param endpointNumber - Endpoint number (without direction bit)
* @param length - Number of bytes to read
* @returns Promise resolving to transfer result
*/
transferIn(endpointNumber: number, length: number): Promise<USBInTransferResult>;
/**
* Bulk/Interrupt transfer OUT
* @param endpointNumber - Endpoint number (without direction bit)
* @param data - Data to send
* @returns Promise resolving to transfer result
*/
transferOut(endpointNumber: number, data: ArrayBuffer): Promise<USBOutTransferResult>;
/**
* Clear halt condition on endpoint
* @param direction - Endpoint direction ('in' or 'out')
* @param endpointNumber - Endpoint number
* @returns Promise that resolves when halt is cleared
*/
clearHalt(direction: USBDirection, endpointNumber: number): Promise<void>;
}
/**
* Control transfer setup parameters
*/
interface USBControlTransferParameters {
requestType: 'standard' | 'class' | 'vendor';
recipient: 'device' | 'interface' | 'endpoint' | 'other';
request: number;
value: number;
index: number;
}
/**
* Transfer results
*/
interface USBInTransferResult {
data?: DataView;
status: 'ok' | 'stall' | 'babble';
}
interface USBOutTransferResult {
bytesWritten: number;
status: 'ok' | 'stall';
}Usage Examples:
import { webusb } from 'usb';
async function performDataTransfers() {
const device = await webusb.requestDevice({
filters: [{ vendorId: 0x1234, productId: 0x5678 }]
});
if (device) {
try {
await device.open();
await device.claimInterface(0);
// Control transfer IN - Get device descriptor
console.log('Performing control transfer IN...');
const controlResult = await device.controlTransferIn({
requestType: 'standard',
recipient: 'device',
request: 0x06, // GET_DESCRIPTOR
value: 0x0100, // Device descriptor
index: 0x0000
}, 18); // Device descriptor is 18 bytes
if (controlResult.status === 'ok' && controlResult.data) {
console.log(`Received ${controlResult.data.byteLength} bytes`);
const view = controlResult.data;
console.log(`Device descriptor bLength: ${view.getUint8(0)}`);
console.log(`Device descriptor bDescriptorType: ${view.getUint8(1)}`);
}
// Control transfer OUT - Vendor-specific command
console.log('Performing control transfer OUT...');
const commandData = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
const outResult = await device.controlTransferOut({
requestType: 'vendor',
recipient: 'device',
request: 0x01,
value: 0x1234,
index: 0x0000
}, commandData.buffer);
if (outResult.status === 'ok') {
console.log(`Sent ${outResult.bytesWritten} bytes`);
}
// Bulk transfer IN
console.log('Performing bulk transfer IN...');
const bulkInResult = await device.transferIn(1, 64); // Endpoint 1, 64 bytes
if (bulkInResult.status === 'ok' && bulkInResult.data) {
console.log(`Bulk IN received ${bulkInResult.data.byteLength} bytes`);
// Convert DataView to hex string for display
const bytes = new Uint8Array(bulkInResult.data.buffer);
const hexString = Array.from(bytes)
.map(b => b.toString(16).padStart(2, '0'))
.join(' ');
console.log(`Data: ${hexString}`);
}
// Bulk transfer OUT
console.log('Performing bulk transfer OUT...');
const bulkData = new Uint8Array([0xFF, 0xAA, 0x55, 0x00]);
const bulkOutResult = await device.transferOut(2, bulkData.buffer); // Endpoint 2
if (bulkOutResult.status === 'ok') {
console.log(`Bulk OUT sent ${bulkOutResult.bytesWritten} bytes`);
}
await device.releaseInterface(0);
} catch (error) {
console.error('Transfer failed:', error.message);
} finally {
await device.close();
}
}
}
// Handle transfer errors and retries
async function robustTransfer() {
const device = await webusb.requestDevice({
filters: [{ vendorId: 0x1234 }]
});
if (device) {
try {
await device.open();
await device.claimInterface(0);
// Robust bulk transfer with error handling
const performBulkTransferWithRetry = async (endpointNumber: number, length: number, maxRetries: number = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Transfer attempt ${attempt}/${maxRetries}`);
const result = await device.transferIn(endpointNumber, length);
if (result.status === 'ok') {
console.log('Transfer successful');
return result.data;
} else if (result.status === 'stall') {
console.log('Endpoint stalled, clearing halt...');
await device.clearHalt('in', endpointNumber);
if (attempt === maxRetries) {
throw new Error('Transfer stalled after clearing halt');
}
} else {
throw new Error(`Transfer failed with status: ${result.status}`);
}
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error.message);
if (attempt === maxRetries) {
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 100 * attempt));
}
}
};
const data = await performBulkTransferWithRetry(1, 64);
if (data) {
console.log(`Successfully received ${data.byteLength} bytes`);
}
await device.releaseInterface(0);
} catch (error) {
console.error('Robust transfer failed:', error.message);
} finally {
await device.close();
}
}
}Handle WebUSB connect and disconnect events.
/**
* WebUSB events
*/
interface USBConnectionEvent {
type: 'connect' | 'disconnect';
device: USBDevice;
}Usage Examples:
import { webusb } from 'usb';
// Set up WebUSB event handlers
function setupWebUSBEvents() {
// Using event handler properties
webusb.onconnect = (event) => {
console.log('WebUSB device connected:', event.device.productName);
handleDeviceConnect(event.device);
};
webusb.ondisconnect = (event) => {
console.log('WebUSB device disconnected:', event.device.productName);
handleDeviceDisconnect(event.device);
};
// Using addEventListener (alternative approach)
webusb.addEventListener('connect', (event) => {
console.log('Connect event listener:', event.device.productName);
});
webusb.addEventListener('disconnect', (event) => {
console.log('Disconnect event listener:', event.device.productName);
});
}
function handleDeviceConnect(device: USBDevice) {
console.log(`Device connected: ${device.productName || 'Unknown'}`);
console.log(` VID:PID = ${device.vendorId.toString(16)}:${device.productId.toString(16)}`);
// Automatically set up device if it's a known type
if (device.vendorId === 0x1234 && device.productId === 0x5678) {
setupKnownDevice(device);
}
}
function handleDeviceDisconnect(device: USBDevice) {
console.log(`Device disconnected: ${device.productName || 'Unknown'}`);
// Clean up any resources associated with this device
cleanupDeviceResources(device);
}
async function setupKnownDevice(device: USBDevice) {
try {
await device.open();
await device.claimInterface(0);
console.log('Known device set up and ready for communication');
// Device is ready for use
// Store reference for later use
global.connectedKnownDevice = device;
} catch (error) {
console.error('Failed to set up known device:', error.message);
}
}
function cleanupDeviceResources(device: USBDevice) {
if (global.connectedKnownDevice === device) {
console.log('Cleaning up known device resources');
global.connectedKnownDevice = null;
// The device is already disconnected, so we don't need to close it
// Just clean up any timers, polling, or other resources
}
}
// Initialize event handling
setupWebUSBEvents();
console.log('WebUSB event handlers set up');Install with Tessl CLI
npx tessl i tessl/npm-usb