or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

constants-errors.mddata-transfers.mddescriptors.mddevice-communication.mddevice-management.mdevent-handling.mdindex.mdinterfaces-endpoints.mdwebusb-api.md

webusb-api.mddocs/

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

```