14
.
11
.
2023
1
.
06
.
2018
Ruby on Rails
Frontend
Backend
Tutorial

WebUSB - Bridge between USB devices and web browsers

Burak Aybar
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 and check also my other articles related to WebUSB!

Resources

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

Burak Aybar
Ruby Developer

Check my Twitter

Check my Linkedin

Did you like it? 

Sign up To VIsuality newsletter

READ ALSO

Writing Chrome Extensions Is (probably) Easier Than You Think

14
.
11
.
2023
Antoni Smoliński
Tutorial
Frontend
Backend

Bounded Context - DDD in Ruby on Rails

17
.
03
.
2024
Paweł Strzałkowski
Ruby on Rails
Domain-Driven Design
Backend
Tutorial

The origin of Poltrax development - story of POLTRAX (part 2)

29
.
11
.
2023
Stanisław Zawadzki
Ruby on Rails
Startups
Business
Backend

Ruby Meetups in 2022 - Summary

14
.
11
.
2023
Michał Łęcicki
Ruby on Rails
Visuality
Conferences

Repository - DDD in Ruby on Rails

17
.
03
.
2024
Paweł Strzałkowski
Ruby on Rails
Domain-Driven Design
Backend
Tutorial

Example Application - DDD in Ruby on Rails

17
.
03
.
2024
Paweł Strzałkowski
Ruby on Rails
Domain-Driven Design
Backend
Tutorial

How to launch a successful startup - story of POLTRAX (part 1)

14
.
11
.
2023
Michał Piórkowski
Ruby on Rails
Startups
Business

How to use different git emails for different projects

14
.
11
.
2023
Michał Łęcicki
Backend
Tutorial

Aggregate - DDD in Ruby on Rails

17
.
03
.
2024
Paweł Strzałkowski
Ruby on Rails
Domain-Driven Design
Backend
Tutorial

Visuality at wroc_love.rb 2022: It's back and it's good!

14
.
11
.
2023
Patryk Ptasiński
Ruby on Rails
Conferences
Ruby

Our journey to Event Storming

14
.
11
.
2023
Michał Łęcicki
Visuality
Event Storming

Should I use Active Record Callbacks?

14
.
11
.
2023
Mateusz Woźniczka
Ruby on Rails
Backend
Tutorial

How to rescue a transaction to roll back changes?

17
.
03
.
2024
Paweł Strzałkowski
Ruby on Rails
Backend
Ruby
Tutorial

Safe navigation operator '&.' vs '.try' in Rails

14
.
11
.
2023
Mateusz Woźniczka
Ruby on Rails
Backend
Ruby
Tutorial

What does the ||= operator actually mean in Ruby?

14
.
11
.
2023
Mateusz Woźniczka
Ruby on Rails
Backend
Ruby
Tutorial

How to design an entity - DDD in Ruby on Rails

17
.
03
.
2024
Paweł Strzałkowski
Ruby on Rails
Domain-Driven Design
Backend
Tutorial

Entity - DDD in Ruby on Rails

17
.
03
.
2024
Paweł Strzałkowski
Ruby on Rails
Domain-Driven Design
Backend
Tutorial

Should I use instance variables in Rails views?

14
.
11
.
2023
Mateusz Woźniczka
Ruby on Rails
Frontend
Backend
Tutorial

Data Quality in Ruby on Rails

14
.
11
.
2023
Michał Łęcicki
Ruby on Rails
Backend
Software

We started using Event Storming. Here’s why!

14
.
11
.
2023
Mariusz Kozieł
Event Storming
Visuality

First Miłośnicy Ruby Warsaw Meetup

14
.
11
.
2023
Michał Łęcicki
Conferences
Visuality

Should I use Action Filters?

14
.
11
.
2023
Mateusz Woźniczka
Ruby on Rails
Backend
Tutorial

Value Object - DDD in Ruby on Rails

17
.
03
.
2024
Paweł Strzałkowski
Ruby on Rails
Domain-Driven Design
Backend
Tutorial