How WebUSB Connects USB Devices in JavaScript

The WebUSB API enables web applications to securely and directly communicate with Universal Serial Bus (USB) hardware peripherals using client-side JavaScript. Traditionally, hardware communication required platform-specific native drivers or standalone desktop software. WebUSB removes this barrier by providing a standardized JavaScript interface inside modern web browsers, allowing web pages to discover, configure, and exchange data with connected USB hardware after explicit user authorization.

The Security and Permission Model

Direct hardware access carries inherent security risks, so the WebUSB API enforces strict browser security controls before any data can be transferred:

  1. Secure Contexts (HTTPS): WebUSB is restricted to secure origins (https:// or localhost) to prevent man-in-the-middle attacks.
  2. User Gesture Requirement: A device prompt can only be triggered by an explicit user action, such as clicking a button or pressing a key.
  3. Explicit User Consent: The browser displays a native permissions dialog listing available devices matching predefined filters. The web page cannot access any device until the user explicitly selects it and clicks “Connect.”
  4. Protected Device Classes: WebUSB explicitly blocks access to standard human interface devices (like primary keyboards and mice), mass storage drives, and smart cards to prevent unauthorized input hijacking or system compromise.

Discovering and Requesting a Device

Communication begins by invoking navigator.usb.requestDevice(). Developers supply filtering criteria, such as the Vendor ID (vendorId) and Product ID (productId), to narrow down the list of compatible hardware shown in the browser prompt:

const filters = [{ vendorId: 0x1234, productId: 0x5678 }];

try {
  const device = await navigator.usb.requestDevice({ filters });
  console.log("Device selected:", device.productName);
} catch (error) {
  console.error("No device selected or access denied:", error);
}

Once granted, the browser retains permission, and previously paired devices can be re-accessed in subsequent sessions via navigator.usb.getDevices().

Opening and Configuring the Device Session

Obtaining a USBDevice object does not immediately allow data transmission. The session must be opened, configured, and bound to specific USB interfaces:

  1. Open Session: Call await device.open() to initiate the physical session with the operating system’s USB stack.
  2. Select Configuration: Call await device.selectConfiguration(1) to activate the desired power and operational state defined in the USB device descriptors.
  3. Claim Interface: Call await device.claimInterface(0) to gain exclusive control over the interface that houses the target communication endpoints. Claiming an interface prevents other applications or operating system drivers from interacting with that specific component of the device.
await device.open();
await device.selectConfiguration(1);
await device.claimInterface(0);

Exchanging Data via Endpoints

USB hardware communicates through directional channels called endpoints. WebUSB supports all standard USB transfer types:

To send raw binary data (via ArrayBuffer or TypedArray) to an OUT endpoint:

const data = new Uint8Array([0x01, 0x02, 0x03]);
await device.transferOut(1, data); // Sends data to endpoint number 1

To receive incoming binary data from an IN endpoint:

const result = await device.transferIn(1, 64); // Listens on endpoint 1 for up to 64 bytes
const receivedData = new Uint8Array(result.data.buffer);

Closing the Connection

When data exchange is complete, the application releases its exclusive lock and terminates the session cleanly using releaseInterface() and close():

await device.releaseInterface(0);
await device.close();

By standardizing these discovery, authentication, configuration, and transfer steps into Promise-based JavaScript methods, the WebUSB API provides web applications with deterministic, low-level hardware control directly inside the browser.