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

Value Object - DDD in Ruby on Rails

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

Introduction to DDD in Ruby on Rails

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

Safe data migrations in Rails

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

I love dev, and so do we!

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

Updated guide to recruitment process at Visuality

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

Visuality Academy for wannabe Junior Engineers

14
.
11
.
2023
Michał Piórkowski
HR
Visuality

How to approach funding as an MVP

14
.
11
.
2023
Michał Piórkowski
Business
Startups

Visuality 13th birthday

14
.
11
.
2023
Michał Piórkowski
HR
Visuality

How To Receive Emails With a Rails App in 2021

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

Project Quality in IT - How to Make Sure You Will Get What You Want?

14
.
11
.
2023
Wiktor De Witte
Ruby on Rails
Project Management
Business

5 Trends in HR Tech For 2021

14
.
11
.
2023
Maciej Zdunek
Business
Project Management

Is Go Language the Right Choice for Your Next Project?

14
.
11
.
2023
Maciej Zdunek
Backend
Business

SXSW Tradeshow 2020: Get Your FREE Tickets and Meet Us

14
.
11
.
2023
Michał Krochecki
Ruby on Rails
Conferences
Frontend
Backend
Business

How to build effective website: simplicity & McDonald's

14
.
11
.
2023
Lukasz Jackiewicz
Ruby on Rails
Frontend
Design

Thermal Printer Protocols for Image and Text

14
.
11
.
2023
Burak Aybar
Backend
Tutorial
Software

WebUSB - Print Image and Text in Thermal Printers

14
.
11
.
2023
Burak Aybar
Backend
Tutorial
Software

What happened in Visuality in 2019

14
.
11
.
2023
Maciej Zdunek
Visuality
HR

Three strategies that work in board games and in real life

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