01 Jun
18
Ruby developer

I am working as a Ruby on Rails engineer at Visuality. Lately, one of our projects required connecting to a USB device. Naturally there are solutions to handle this requirement; however, all these solutions are complicated, deprecated, or unsecured. To tackle this problem we found an easy, secure, and new way to do it. Today, I will introduce a new technology which is becoming popular recently. From my point of view, it will give a new impulse to the market and can decrease usage of native apps that require USB device connection.

It is quite difficult to use USB devices on the browser. Fortunately, there are some solutions in order to connect USB devices to browsers but most of them are not usable or deprecated. The first solution coming to mind is Google Chrome’s USB extension library, but that’s going to be deprecated because Google stopped supporting Chrome Apps. Another solution can be writing a native plugin for that, but that’s incredibly insecure and may bring cross-platform compatibility problems. That's why the WebUSB API has been created: to provide a way for websites to connect to users' USB devices.

Introduction to WebUSB

The WebUSB API provides a way to safely expose USB device services to the web using JavaScript. With this API, hardware manufacturers will be able to build cross-platform JavaScript SDKs for their devices. Most important thing is that it makes USB safer and easier to use by bringing it to the Web without the need to install any drivers on the user side.

WebUSB API can play a crucial role in the connection between web and USB devices. However, there can be a few worries about security. Compared to the previous alternatives, WebUSB offers more reliable system:

  • WebUSB API capabilities require HTTPS; It means you'll need to get a TLS certificate and set it up on your server.
  • Getting request to connected USB devices, WebUSB API must be called via a user gesture like a touch or mouse click.
  • To avoid keylogging, some devices (such as USB keyboard or mouse) are not accessible to WebUSB.

On the other hand, as it is known, there is a possibility that some issues may be found in new technologies. For instance, I encountered a problem that WebUSB API is removed by self in Chrome 64. And to bring it back I had to re-install Chrome. I had contacted Chrome support and they told me that in the new version it will be fixed. Then - like they said - it is fixed in Chrome 65 and until now, I have never seen the same issue again.

Let's Code

Before starting, it is better to have the latest version of Chrome - you can check it here. The WebUSB API relies on promises, you can get more information from this tutorial

To get a list of USB devices which are connected to the system by calling getDevices() method:

let devices = await navigator.usb.getDevices();
devices.forEach(device => {
    // You can list or choose your device in this block 
});

If this is the first time the user has visited the page then it won’t have permission to access any devices. Therefore, in order to use USB device, you need to request a permission from the user by calling requestDevice() method (do not forget, this function is user gesture required such as button or touch). Moreover, you can filter devices for users by calling filters. At this moment, you need to provide vendorId and optionally productId identifiers. Thanks to that, we can show filtered devices to the user (You can find this information from the internal page of Chrome which is chrome://device-log):

let button = document.getElementById('request-device');
button.addEventListener('click', async () => {
  let device;
  let usbDeviceProperties = { name: "Bixolon", vendorId: 5380, productId: 31 };
  try {
    device = await navigator.usb.requestDevice({ filters: usbDeviceProperties });
  } catch (error) {
    alert('Error: ' + error.message);
  }
});

After selecting device it needs to be configured. Select the first configuration (it only has one but the operating system may not have already done this during enumeration) and also interface needs to be claimed in order to transfer data:

device.open()
      .then(() => device.selectConfiguration(1))
      .then(() => device.claimInterface(0));

Therefore, the last part is sending or receiving data:

await device.transferOut(1, data)

or

await device.controlTransferOut({
        requestType: 'vendor',
        recipient: 'interface',
        request: 0x01,  // vendor-specific request: enable channels
        value: 0x0013,  // 0b00010011 (channels 1, 2 and 5)
        index: 0x0001   // Interface 1 is the recipient
});

In order to receive data:

let receivedData = await data.transferIn(1, 6);// Waiting for 6 bytes of data from endpoint #1.

The WebUSB API provides all endpoint types of USB devices:

  • Control transfers are typically used for command and status operations by calling controlTransferIn(setup, length) and controlTransferOut(setup, data)

  • Bulk transfers can be used for large bursty data. Such examples could include a print-job sent to a printer or an image generated from a scanner by calling transferIn(endpointNumber, length) and transferOut(endpointNumber, data)

  • Isochronous transfers occur continuously and periodically. They typically contain time sensitive information, such as an audio or video stream by calling isochronousTransferIn(endpointNumber, packetLengths) and isochronousTransferOut(endpointNumber, data, packetLengths)

  • Interrupt transfers are typically non-periodic, small device "initiated" communication requiring bounded latency by calling transferIn(endpointNumber, length) and transferOut(endpointNumber, data)

NOTE: It should not be forgotten that user may connect or disconnect the device from their system, therefore, the script should also register these events to keep the interface up-to-date:

navigator.usb.addEventListener('connect', event => {
  // event.device will bring the connected device
});

navigator.usb.addEventListener('disconnect', event => {
  // event.device will bring the disconnected device
}); 

Areas of Usage of WebUSB

The first example can be for educational purposes. Students use and learn from the different microcontroller (e.g., Arduino, Photon-although Photon provides programming via Wi-Fi) development kits. But each of them uses different applications in order to write or upload the code. Therefore, these native extensions/applications add a barrier to entry into this area. Moreover, the web applications can be loaded quickly on any computer without questions of platform compatibility or administrative credentials (educational applications should to be registered).

Another example can be 3D printers. Imagine that a site provides a possibility to design your 3D objects and you can print directly from their page. It would bring new ideas into this ecosystem.

The last example can be thermal printers - which I have been working on. We use thermal printers everywhere like restaurants, shops or kiosks. Instead of using lots of different applications to print the receipt/ticket, it would be nice to have just one application which can allow modifying styling too.

Summary

In recent years, time of implementation and prototyping possibility have become crucial to the market. In order to cope with these requirements, the web applications are trying to have more powerful APIs such as WebUSB, WebBluetooth, and similar APIs. This kind of APIs enable to increase usage of web applications while developing products and it brings some advantages such as platform independence, portability and comprehensibility.

This kind of improvements is making everything much easier for developers and users. Therefore, browser APIs are, finally, making the need for many types of native apps disappear.

It’s already in production and battle-tested with thousands of different visitors, devices, locations every week. Join the journey!

Resources

WebUSB API Spec: http://wicg.github.io/webusb/

Two years ago we introduced Docker to our company. One of our projects was complex in terms of deployment architecture and scalability. We were looking for a solution which allows us to simplify the process of deployment and scaling client's instances in the future. Docker turned out as a very good tool to isolate apps into containers with all needed dependencies and treat them as block boxes. Read this article to find out more!

We're planning to open a new office in Poznań. If you want to know more about the reasons and the way we want to start this new adventure, read this article.