# Bluetooth in browsers (preview)

Canonical page: https://docs.ovok.com/native-sdk/guide/modules/next-bluetooth-web

`@ovok/native/next/bluetooth/web` runs [Bluetooth on the next client](/native-sdk/guide/modules/next-bluetooth) 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 [#browsers]

| Browser | Web Bluetooth |
|---|---|
| Chrome and Edge on desktop (Windows, macOS, Linux, ChromeOS) and Android | Yes |
| Bluefy on iOS and iPadOS | Yes |
| Safari, and every other browser on iOS and iPadOS | No: they all use WebKit, which has no Web Bluetooth |
| Firefox | No |

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 [#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:

```tsx
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 [#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 [#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 [#what-a-browser-cant-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 [#api-reference]

Every export, with signatures and types, is in the [`next/bluetooth/web` API reference](/native-sdk/reference/next-bluetooth-web).
