GuidesModules

Bluetooth in browsers (preview)

@ovok/native/next/bluetooth/web: run the integrated devices in a browser over Web Bluetooth.

  • Stable unknown
  • Release candidate since 1.16.1-rc.5
  • Development since 1.16.1-dev.2145+8eb3814

@ovok/native/next/bluetooth/web runs Bluetooth on the next client in a browser. It has the same BluetoothRuntime, devices, result policies and hooks, with a Web Bluetooth scanner in place of react-native-ble-plx. Every integrated device and every defineCustomDevice declaration runs through it, because the scanner hands the device classes a Web Bluetooth device with the react-native-ble-plx API they already use.

Nothing in it loads react-native-ble-plx or react-native-permissions, so a web bundle (React Native Web, Expo web) can import it. Import the web entry only in web code, for example from a .web.ts file.

Browsers

BrowserWeb Bluetooth
Chrome and Edge on desktop (Windows, macOS, Linux, ChromeOS) and AndroidYes
Bluefy on iOS and iPadOSYes
Safari, and every other browser on iOS and iPadOSNo: they all use WebKit, which has no Web Bluetooth
FirefoxNo

The page must be served over HTTPS (or from localhost). Use isWebBluetoothAvailable() to choose between the Bluetooth screen and a message that says which browser or app to use instead.

Pick, connect, save

A page can only reach the devices the user picks in the browser's chooser, and the chooser only opens from a user gesture:

import * as React from 'react';
import { Button } from 'react-native';
import {
  BluetoothProvider,
  builtInDevices,
  createWebBluetoothRuntime,
  IntegratedDevices,
  useBluetooth,
  webBluetoothScanner,
} from '@ovok/native/next/bluetooth/web';
import { ovok } from './ovok'; // the next Ovok client, signed in

const accepted = [IntegratedDevices.BP2, IntegratedDevices.F4];
const scanner = webBluetoothScanner({ acceptedDevices: accepted });
const bluetooth = createWebBluetoothRuntime({ client: ovok, scanner, devices: [builtInDevices(accepted)] });

export const App = () => <BluetoothProvider runtime={bluetooth}><AddDevice /></BluetoothProvider>;

const AddDevice = () => {
  const runtime = useBluetooth();

  const add = async () => {
    await runtime.startScan();
    const device = await scanner.chooseDevice(); // undefined when the user closes the chooser
    if (device) await runtime.pair(device.id);
  };

  return <Button title="Add a monitor" onPress={add} />;
};

chooseDevice() opens the browser's chooser with every nearby Bluetooth device. The picked device is reported to the running scan, so it appears in useNearbyDevices. pair connects to it and remembers it in pairedStorage, and its readings are saved with ovok.measurements.save, as on a phone.

Give webBluetoothScanner the same accepted devices as builtInDevices. The browser only lets the page use the services it named when the chooser opened. The scanner always names every integrated device's services (WEB_BLUETOOTH_SERVICES), and adds the services of the defineCustomDevice declarations among acceptedDevices.

Show the supported devices

The device catalog is exported here too, so a web app can list the products it supports without @ovok/native/bt-device, which loads react-native-ble-plx: SUPPORTED_DEVICES (with each product's photo, maker and acceptedDevices), Devices, and resultPoliciesFor, which picks the result policy for live devices.

Reconnecting

Where the browser has navigator.bluetooth.getDevices(), which Chrome has, a scan reports the devices this site may already use, so a paired device reconnects after a reload without the chooser. Elsewhere the user picks it again.

When the browser doesn't show advertisements to the page, the scanner connects to a device as soon as it is picked or reported, to read its services. That's how a device told apart by its services alone, such as a standard blood pressure monitor, is recognised. A remembered device out of range is reported only once it can be reached.

What a browser can't do

  • No background or automatic discovery. There is no scan of unknown devices: a new device always comes from the chooser, and nothing runs once the tab is closed.
  • No serial number. Browsers block the Device Information Serial Number String, so information.serialNumber is empty, and the device ID is a random ID the browser keeps per site. Readings keep their own recordedAt, so a device's stored history is still recognised when it comes again through the same browser, but the same reading taken on a phone has a different device.
  • Advertising data only with watchAdvertisements. Where the browser has it, the scanner reads the advertisement. Where it doesn't (most browsers by default), these devices can't be told apart or set up and don't work: Trasense S9 (recognised by its manufacturer data), BBSM S3, R20 and R200, and the Lepu and Viatom oximeters whose protocol or encryption the advertisement names.
  • No MAC address. Transtek A6 and the RPM scale derive their encryption key from the device's MAC address, which browsers don't reveal, so they don't work in a browser.
  • One GATT operation at a time. Browsers queue nothing themselves, so the device queues its reads, writes and notification starts. Commands are written in 20-byte chunks, as on a phone; the browser negotiates the MTU itself.
  • Pairing is done by the operating system's dialog, as on a phone. Some systems ask for it only when a protected characteristic is first read.

API reference

Every export, with signatures and types, is in the next/bluetooth/web API reference.

Last updated on

On this page