Using Web Bluetooth API in Chrome Extensions
Alexey Korepanov
by Alexey Korepanov
4 min read

Categories

Tags

How to create a Chrome extension to connect Bluetooth devices using Web Bluetooth API.

Chrome extension architecture

For those who doesn’t know what the chrome extensions are, here is the detailed introduction from Google developers.

Every Chrome extension might contain four main parts:

  • Background script
  • Content script
  • Popup page/script
  • Users options page/script

Roughly speaking, background script is associcated with the entire browser window, content scripts are embedded into the laded web pages, the popup script is running as long as the popup page is displayed and the users options exist as long as the options pages are open.

Chrome extension contexts

All these scripts have different life cycle, i.e. they are created and destroyed at the different moments:

Chrome extension lifecycle

Web Bluetooth API

Web Bluetooth API is there for years already and is supported by most of the major platforms, except for iOS. Let’s see how to use this API in Chrome extensions.

Let’s start from some basics. There are different types of Bluetooth protocols (called profiles), each of them have their own purpose. For example, A2DP profile is supported by audio headsets and HID profile is used by keyboards and mouses.

Web Bluetooth API specifically supports Generic Attribute Profile (GATT) which is used by many devices like smart watches, activity trackers, smart lamps, weight scales and so on.

If you connect your GATT device to your phone or computer, your devices acts as a GATT server and your phone/computer is a GATT client. The general GATT server structure is shown below:

GATT server

In order to connect to the GATT servier, your application should perform the following steps:

  1. Application calls navigator.bluetooth.requestDevice method. This method checks all required security and shows up a device selection dialog: Web Bluetooth device selection popup
  2. If user selected a Bluetooth device, navigator.bluetooth.requestDevice returns connected BluetoothDevice object.
  3. Now application can connect to the device using device.gatt.connect(). This method returns BluetoothRemoteGATTServer object which now can be used to control the device.
  4. Call server.getPrimaryService to obtain the GATT service.
  5. And finally, call service.getCharacteristics to get GATT characteristics.

Now you can use the characteristics to control the device using the following methods:

  • readValue - to read data from the characteristics, like battery level or temperature.
  • writeValue - to control your device, like setting lamp color or brightness.
  • startNotifications - to subscribe to the sensor notifications, like getting constant updates about pulse of number of steps.
  • stopNotifications - to unsubscribe from the notifications

There are several limitations though.

  • Web Bluetooth API can be used only on secure pages (pages loaded via HTTPS) or on the local pages. In case of the Chrome extensions, the extension code is considered safe so there are no limitations.
  • Web Bluetooth connection can be only established in context of the web page. When such connection is established, your browser indicates it with a small Bluetooth logo in the page tab, to tell the user that the page uses Bluetooth devices: Web Bluetooth Tab For us it means two things:
    • It’s not possible to establish a connection from the extension background script.
    • Creating a connection from the extension popup makes no sense since it will live only as long as the popup dialog is visible.
  • You can only connect to the devices which have been selected by user via standard browser device selection popup. This popup is not customizable by the application.
  • Web Bluetooth connections cannot be persisted to the storage or somehow saved between the page reloads or passed between the pages. Web Bluetooth API specification contains permissions.request feature which allows to connect to the devices which were previously connected after the page reload, but currently (January 2020) it’s not supported by any of the browsers.
  • Bluetooth connection can be only initiated by a user gesture which means that we need something like a button click in order to show the device connection dialog.

Sample extension

Let’s create a simple extension that runs on Gmail web page, connects to the Mipow Playbulb Bluetooth Candle (simply because I have it at home), checks your inbox and keeps blinking as long as you have unread e-mails.

In order to get access to the number of unread e-mails, you need to have a content script. We’ll simply read the page tilte and get number of unread e-mail from it.

Blutooth connection will also be established from the content script.

Let’s create manifest.json:

{
  "name": "bluetooth-chrome-extension-test",
  "description" : "Bluetooth Chrome Extension Test",
  "version": "1.0",
  "permissions": [
    "https://mail.google.com/mail/*"
  ],
  "manifest_version": 2,
  "content_scripts": [
    {
      "all_frames": true,
      "js": ["content_script.js"],
      "matches": ["https://mail.google.com/mail/*"],
      "run_at": "document_idle"
    }
  ]
}

This file describes the extension. The most important things are premissions field which says that the extension needs access to the Gmail web pages and content_scripts field which tells the browser that content_script.js should be running in context of the Gmail web page.

Now let’s create the content script file named content_script.js. It will do the following:

  1. Read the document title to see if it matches Inbox (*) pattern, which means that we have unread mail.
  2. Add a small “Connect” button to the web page. Clicking the button start the device connection sequence:
var options = {
  filters: [{services: [SERVICE_UUID]}],
}
const device = await navigator.bluetooth.requestDevice(options);
const server = await device.gatt.connect();
const service = await server.getPrimaryService(SERVICE_UUID);
const characteristics = await service.getCharacteristics(CHARACTERISTIC_UUID);
const characteristic = characteristics.find(char => char.uuid === CHARACTERISTIC_UUID);

As soon as the device is connected, we start checking the page title every 2 seconds and if there are unread e-mails, keep blinking the lamp by calling blink function:

const setColor = async (characteristic, red, green, blue, saturation) => {
  var bytes = new Uint8Array([red, green, blue, saturation])
  await characteristic.writeValue(bytes)
}

const blink = (characteristic) => {
  setColor(characteristic, 255, 255, 255, 255);
  new Promise(resolve => {
    setTimeout(async () => {
      setColor(characteristic, 0, 0, 0, 0);
      resolve()
    }, 200)
  })
}

Gmail checker Chrome Extension with Web Bluetooth

The full source code can be found on the github.

You can connect me via Twitter if you have any corrections or comments regarding this article.