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

How to become a Ruby Certified Programmer Title image

How to become a Ruby Certified Programmer

14
.
11
.
2023
Michał Łęcicki
Ruby
Visuality
Vector Search in Ruby - Paweł Strzałkowski

Vector Search in Ruby

17
.
03
.
2024
Paweł Strzałkowski
ChatGPT
Embeddings
Postgresql
Ruby
Ruby on Rails
LLM Embeddings in Ruby - Paweł Strzałkowski

LLM Embeddings in Ruby

17
.
03
.
2024
Paweł Strzałkowski
Ruby
LLM
Embeddings
ChatGPT
Ollama
Handling Errors in Concurrent Ruby, Michał Łęcicki

Handling Errors in Concurrent Ruby

14
.
11
.
2023
Michał Łęcicki
Ruby
Ruby on Rails
Tutorial
Recap of Friendly.rb 2024 conference

Insights and Inspiration from Friendly.rb: A Ruby Conference Recap

02
.
10
.
2024
Kaja Witek
Conferences
Ruby on Rails

Covering indexes - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Ruby on Rails
Postgresql
Backend
Ula Sołogub - SQL Injection in Ruby on Rails

The Deadly Sins in RoR security - SQL Injection

14
.
11
.
2023
Urszula Sołogub
Backend
Ruby on Rails
Software
Michal - Highlights from Ruby Unconf 2024

Highlights from Ruby Unconf 2024

14
.
11
.
2023
Michał Łęcicki
Conferences
Visuality
Cezary Kłos - Optimizing Cloud Infrastructure by $40 000 Annually

Optimizing Cloud Infrastructure by $40 000 Annually

14
.
11
.
2023
Cezary Kłos
Backend
Ruby on Rails

Smooth Concurrent Updates with Hotwire Stimulus

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

Freelancers vs Software house

02
.
10
.
2024
Michał Krochecki
Visuality
Business

Table partitioning in Rails, part 2 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
Ruby on Rails

N+1 in Ruby on Rails

14
.
11
.
2023
Katarzyna Melon-Markowska
Ruby on Rails
Ruby
Backend

Turbo Streams and current user

29
.
11
.
2023
Mateusz Bilski
Hotwire
Ruby on Rails
Backend
Frontend

Showing progress of background jobs with Turbo

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

Table partitioning in Rails, part 1 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend
Ruby on Rails

Table partitioning types - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend

Indexing partitioned table - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
SQL Views in Ruby on Rails

SQL views in Ruby on Rails

14
.
11
.
2023
Jan Grela
Backend
Ruby
Ruby on Rails
Postgresql
Design your bathroom in React

Design your bathroom in React

14
.
11
.
2023
Bartosz Bazański
Frontend
React
Lazy Attributes in Ruby - Krzysztof Wawer

Lazy attributes in Ruby

14
.
11
.
2023
Krzysztof Wawer
Ruby
Software

Exporting CSV files using COPY - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Ruby
Ruby on Rails
Michał Łęcicki - From Celluloid to Concurrent Ruby

From Celluloid to Concurrent Ruby: Practical Examples Of Multithreading Calls

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