# Web Bluetooth - JavaScript bridge API

> The navigator.bluetooth shape, answered natively so a page written for Chrome works unchanged.

- **Applies to:** AppMint
- **Source:** extracted from the app runtime and its per-method example files; this page is generated from them.
- **HTML:** https://freewebtoapk.com/docs/api/webbluetooth

### `navigator.bluetooth`

```js
navigator.bluetooth — Web Bluetooth
```

**Example**

Standard Web Bluetooth: the same `navigator.bluetooth` code you would write for Chrome works in the installed app, for any Bluetooth Low Energy device.

**Returns:** Promises, as in the web standard. Values are `DataView`s. **Needs:** Bluetooth. There is no switch: when you build from a ZIP or an HTML page, the build turns it on by itself if your code mentions `navigator.bluetooth` (or `BluetoothUUID`). A website (URL) build cannot turn it on. Without it, `navigator.bluetooth` does not exist. The first `requestDevice()` asks for the Nearby devices permission (Location on Android 11 and older).

**Heart-rate strap, from a button tap.** Check for `navigator.bluetooth` inside the handler - the app installs it while the page loads, not before your first script runs:

```js
var hrDevice = null;

document.getElementById('connect').addEventListener('click', async function () {
  if (!navigator.bluetooth) { showStatus('Bluetooth is not available in this app.'); return; }
  try {
    hrDevice = await navigator.bluetooth.requestDevice({
      filters: [{ services: ['heart_rate'] }],        // or { name: 'Polar H10' } / { namePrefix: 'Polar' }
      optionalServices: ['battery_service']
    });
    hrDevice.addEventListener('gattserverdisconnected', function () {
      showStatus('Strap disconnected');
    });
    var server = await hrDevice.gatt.connect();
    var hr = await server.getPrimaryService('heart_rate');
    var chr = await hr.getCharacteristic('heart_rate_measurement');
    chr.addEventListener('characteristicvaluechanged', function (e) {
      var v = e.target.value;                                   // DataView
      var bpm = (v.getUint8(0) & 1) ? v.getUint16(1, true) : v.getUint8(1);
      document.getElementById('bpm').textContent = bpm;
    });
    await chr.startNotifications();

    var battery = await server.getPrimaryService('battery_service');
    var level = await (await battery.getCharacteristic('battery_level')).readValue();
    document.getElementById('battery').textContent = level.getUint8(0) + '%';
  } catch (err) {
    // NotFoundError: user cancelled the chooser, Bluetooth is off, or no such service.
    // SecurityError: permission denied.  NetworkError: connection lost.
    showStatus(err.name + ': ' + err.message);
  }
});

document.getElementById('disconnect').addEventListener('click', function () {
  if (hrDevice && hrDevice.gatt.connected) hrDevice.gatt.disconnect();
});
```

**Writing.** `writeValue` and `writeValueWithResponse` wait for the device to confirm; `writeValueWithoutResponse` does not:

```js
async function sendCommands(server) {
  // Heart Rate Control Point: 0x01 = reset energy expended
  var hr = await server.getPrimaryService('heart_rate');
  var ctrl = await hr.getCharacteristic('heart_rate_control_point');
  await ctrl.writeValueWithResponse(new Uint8Array([0x01]));

  // A custom UART-style service (full 128-bit UUIDs work everywhere)
  var uart = await server.getPrimaryService('6e400001-b5a3-f393-e0a9-e50e24dcca9e');
  var rx = await uart.getCharacteristic('6e400002-b5a3-f393-e0a9-e50e24dcca9e');
  await rx.writeValueWithoutResponse(new TextEncoder().encode('LED ON\n'));
}
```

**Notes:** The device chooser is drawn by the app and scans for 30 seconds; a page can only reach a device the user picked. A `services` filter matches only services the device advertises. After `connect()` every service the device has can be used, even ones not in `optionalServices` (keep them anyway, Chrome needs them). Also available: `getAvailability()`, `getPrimaryServices()`, `getCharacteristics()`, `stopNotifications()`, `characteristic.properties`, `device.forget()`, `BluetoothUUID.getService/getCharacteristic/canonicalUUID`. `getDevices()`, `requestLEScan()`, `watchAdvertisements()` and descriptors reject with `NotSupportedError`. For the standard fitness sensors with the numbers already parsed, see `bleStartScan`.

### `wbConnect`

```js
window.WebToApk.wbConnect(requestId: String, deviceId: String)
```

Raw transport for WebBluetoothPolyfill. A page never calls these directly - it calls navigator.bluetooth, exactly as it would in a browser.

_Described by its group, Web Bluetooth (navigator.bluetooth), rather than on its own._

**Example**

Internal transport behind `device.gatt.connect()`. Pages use `navigator.bluetooth`; do not call this directly.

**Returns:** (through `gatt.connect()`) a Promise of the `BluetoothRemoteGATTServer`, after the device's services are discovered. **Needs:** Bluetooth (turned on by a ZIP or HTML build when your code mentions `navigator.bluetooth`).

```js
async function connectAndList(device) {
  try {
    var server = await device.gatt.connect();
    var services = await server.getPrimaryServices();
    services.forEach(function (s) { console.log('service', s.uuid); });
    return server;
  } catch (err) {
    showStatus('Could not connect: ' + err.message);    // NetworkError, NotFoundError
    return null;
  }
}
```

**Reconnect when the device drops.** Calling `connect()` again on the same device works:

```js
device.addEventListener('gattserverdisconnected', async function () {
  showStatus('Reconnecting...');
  try { await connectAndList(device); showStatus('Connected'); } catch (e) {}
});
```

**Notes:** Raw signature `wbConnect(requestId, deviceId)`. Connecting to a device that is already connected resolves again at once. `getPrimaryService(s)` rejects with `NetworkError` before `connect()` has finished. Several devices can be connected at the same time.

### `wbDisconnect`

```js
window.WebToApk.wbDisconnect(deviceId: String)
```

Raw transport for WebBluetoothPolyfill. A page never calls these directly - it calls navigator.bluetooth, exactly as it would in a browser.

_Described by its group, Web Bluetooth (navigator.bluetooth), rather than on its own._

**Example**

Internal transport behind `device.gatt.disconnect()` (and `device.forget()`). Pages use `navigator.bluetooth`; do not call this directly.

**Returns:** nothing (`disconnect()` is synchronous, as in the web standard). The device then fires `gattserverdisconnected`. **Needs:** Bluetooth (turned on by a ZIP or HTML build when your code mentions `navigator.bluetooth`).

```js
var device = null;   // from navigator.bluetooth.requestDevice(...)

document.getElementById('stop').addEventListener('click', function () {
  if (device && device.gatt.connected) device.gatt.disconnect();
});

// Fires for your own disconnect() and when the device goes away by itself.
function watch(dev) {
  dev.addEventListener('gattserverdisconnected', function () {
    showStatus('Disconnected from ' + (dev.name || dev.id));
  });
}
```

**Notes:** Raw signature `wbDisconnect(deviceId)`. After a disconnect the old service and characteristic objects stop receiving notifications; after the next `connect()`, get them again and call `startNotifications()` again. The app drops every connection when it closes.

### `wbGetCharacteristics`

```js
window.WebToApk.wbGetCharacteristics(requestId: String, deviceId: String, service: String)
```

Raw transport for WebBluetoothPolyfill. A page never calls these directly - it calls navigator.bluetooth, exactly as it would in a browser.

_Described by its group, Web Bluetooth (navigator.bluetooth), rather than on its own._

**Example**

Internal transport behind `service.getCharacteristic(uuid)` and `service.getCharacteristics()`. Pages use `navigator.bluetooth`; do not call this directly.

**Returns:** (through the service) a Promise of one `BluetoothRemoteGATTCharacteristic`, or of an array of them, each with `uuid` and `properties` (`read`, `write`, `writeWithoutResponse`, `notify`, `indicate`, `broadcast`, `authenticatedSignedWrites`). **Needs:** Bluetooth (turned on by a ZIP or HTML build when your code mentions `navigator.bluetooth`).

```js
async function describe(server) {
  var service = await server.getPrimaryService('heart_rate');

  // One characteristic, by standard name, 16-bit number or full UUID:
  var measurement = await service.getCharacteristic('heart_rate_measurement'); // same as 0x2a37

  // All of them:
  var list = await service.getCharacteristics();
  list.forEach(function (c) {
    var p = c.properties;
    console.log(c.uuid, p.read ? 'read' : '', p.write ? 'write' : '', p.notify ? 'notify' : '');
  });
  return measurement;
}
```

**Notes:** Raw signature `wbGetCharacteristics(requestId, deviceId, service)`. A UUID the service does not have rejects with `NotFoundError`; an unknown name throws `TypeError` (pass a full UUID for custom characteristics). The list is fetched once per service and then reused.

### `wbIsAvailable`

```js
window.WebToApk.wbIsAvailable(): Boolean
```

Raw transport for WebBluetoothPolyfill. A page never calls these directly - it calls navigator.bluetooth, exactly as it would in a browser.

_Described by its group, Web Bluetooth (navigator.bluetooth), rather than on its own._

**Example**

Internal transport behind `navigator.bluetooth.getAvailability()`. Pages use `navigator.bluetooth`; do not call this directly.

**Returns:** (through `getAvailability()`) a Promise of `true` when the build has Bluetooth and the phone's Bluetooth is on, else `false`. **Needs:** Bluetooth (turned on by a ZIP or HTML build when your code mentions `navigator.bluetooth`).

```js
async function updateBluetoothHint() {
  var hint = document.getElementById('bt-hint');
  if (!navigator.bluetooth) { hint.textContent = 'Bluetooth is not available in this app.'; return; }
  var on = await navigator.bluetooth.getAvailability();
  hint.textContent = on ? '' : 'Turn on Bluetooth to connect your sensor.';
}
document.addEventListener('visibilitychange', function () {
  if (!document.hidden) updateBluetoothHint();
});
```

**Notes:** Raw signature `wbIsAvailable(): Boolean` (synchronous). It checks the adapter only - it does not ask for any permission. The `availabilitychanged` event of the web standard is not fired, so check again when the page becomes visible.

### `wbNotify`

```js
window.WebToApk.wbNotify(
```

Raw transport for WebBluetoothPolyfill. A page never calls these directly - it calls navigator.bluetooth, exactly as it would in a browser.

_Described by its group, Web Bluetooth (navigator.bluetooth), rather than on its own._

**Example**

Internal transport behind `characteristic.startNotifications()` and `stopNotifications()`. Pages use `navigator.bluetooth`; do not call this directly.

**Returns:** (through the characteristic) a Promise of the characteristic itself. Each new value fires `characteristicvaluechanged` on it, with the value in `event.target.value` (a `DataView`). **Needs:** Bluetooth (turned on by a ZIP or HTML build when your code mentions `navigator.bluetooth`).

```js
var cadenceChr = null;

async function startCadence(server) {
  var svc = await server.getPrimaryService('running_speed_and_cadence');
  cadenceChr = await svc.getCharacteristic('rsc_measurement');
  cadenceChr.addEventListener('characteristicvaluechanged', onRsc);
  await cadenceChr.startNotifications();
}

function onRsc(e) {
  var v = e.target.value;
  var kmh = v.getUint16(1, true) / 256 * 3.6;
  var spm = v.getUint8(3);
  show('run', kmh.toFixed(1) + ' km/h, ' + spm + ' steps/min');
}

async function stopCadence() {
  if (!cadenceChr) return;
  cadenceChr.removeEventListener('characteristicvaluechanged', onRsc);
  try { await cadenceChr.stopNotifications(); } catch (e) {}
}
```

**Notes:** Raw signature `wbNotify(requestId, deviceId, service, characteristic, enable)`. A characteristic that only supports indications is subscribed with indications automatically. A characteristic that cannot notify rejects with `NotSupportedError`. `oncharacteristicvaluechanged = fn` works too. After a disconnect, subscribe again.

### `wbRead`

```js
window.WebToApk.wbRead(requestId: String, deviceId: String, service: String, characteristic: String)
```

Raw transport for WebBluetoothPolyfill. A page never calls these directly - it calls navigator.bluetooth, exactly as it would in a browser.

_Described by its group, Web Bluetooth (navigator.bluetooth), rather than on its own._

**Example**

Internal transport behind `characteristic.readValue()`. Pages use `navigator.bluetooth`; do not call this directly.

**Returns:** (through `readValue()`) a Promise of a `DataView`; `characteristic.value` is updated too. **Needs:** Bluetooth (turned on by a ZIP or HTML build when your code mentions `navigator.bluetooth`).

```js
async function readInfo(server) {
  try {
    var battery = await server.getPrimaryService('battery_service');
    var level = await (await battery.getCharacteristic('battery_level')).readValue();
    show('battery', level.getUint8(0) + '%');

    var info = await server.getPrimaryService('device_information');
    var maker = await (await info.getCharacteristic('manufacturer_name_string')).readValue();
    show('maker', new TextDecoder().decode(maker));
  } catch (err) {
    // NotFoundError: the device has no such service/characteristic
    // NotSupportedError: read rejected by the device   NetworkError: not connected / read failed
    show('info', err.message);
  }
}
```

**Notes:** Raw signature `wbRead(requestId, deviceId, service, characteristic)`. Reads, writes and notification changes on one device run one after another in the order you call them, so you can fire several without waiting in between.

### `wbRequestDevice`

```js
window.WebToApk.wbRequestDevice(requestId: String, optionsJson: String)
```

Raw transport for WebBluetoothPolyfill. A page never calls these directly - it calls navigator.bluetooth, exactly as it would in a browser.

_Described by its group, Web Bluetooth (navigator.bluetooth), rather than on its own._

**Example**

Internal transport behind `navigator.bluetooth.requestDevice(options)`: it shows the app's device chooser. Pages use `navigator.bluetooth.requestDevice`; do not call this directly.

**Returns:** (through `requestDevice`) a Promise of a `BluetoothDevice` `{id, name, gatt}`. **Needs:** Bluetooth (turned on by a ZIP or HTML build when your code mentions `navigator.bluetooth`) and the Nearby devices permission, asked on first use.

```js
document.getElementById('pick').addEventListener('click', async function () {
  if (!navigator.bluetooth) { showStatus('Bluetooth is not available in this app.'); return; }
  try {
    var device = await navigator.bluetooth.requestDevice({
      filters: [{ services: ['cycling_power'] }, { namePrefix: 'KICKR' }],
      optionalServices: ['battery_service']
    });
    showStatus('Picked ' + (device.name || device.id));
  } catch (err) {
    if (err.name === 'NotFoundError') showStatus('No device chosen.');   // also: Bluetooth off
    else showStatus(err.name + ': ' + err.message);                     // SecurityError = permission
  }
});
```

To list every nearby device instead of filtering: `requestDevice({ acceptAllDevices: true })`.

**Notes:** Raw signature `wbRequestDevice(requestId, optionsJson)`; the answer comes back to the polyfill, not to the page. Call `requestDevice` from a tap. Filters use `services` (must be advertised), `name` (exact) and `namePrefix`; a filter matches when all its keys match, and the device matches when any filter matches. Neither `filters` nor `acceptAllDevices` → `TypeError`. Cancelling the chooser rejects with `NotFoundError`. The chooser scans for 30 seconds. `device.id` is the device's Bluetooth address.

### `wbWrite`

```js
window.WebToApk.wbWrite(
```

Raw transport for WebBluetoothPolyfill. A page never calls these directly - it calls navigator.bluetooth, exactly as it would in a browser.

_Described by its group, Web Bluetooth (navigator.bluetooth), rather than on its own._

**Example**

Internal transport behind `characteristic.writeValue()`, `writeValueWithResponse()` and `writeValueWithoutResponse()`. Pages use `navigator.bluetooth`; do not call this directly.

**Returns:** (through the characteristic) a Promise of `undefined`. **Needs:** Bluetooth (turned on by a ZIP or HTML build when your code mentions `navigator.bluetooth`).

**With response** (`writeValue` is the same): resolves when the device confirms the write.

```js
async function setTargetPower(server, watts) {
  var ftms = await server.getPrimaryService('fitness_machine');
  var cp = await ftms.getCharacteristic('00002ad9-0000-1000-8000-00805f9b34fb'); // FTMS control point
  await cp.writeValueWithResponse(new Uint8Array([0x00]));                       // request control
  var cmd = new DataView(new ArrayBuffer(3));
  cmd.setUint8(0, 0x05);                 // set target power
  cmd.setInt16(1, watts, true);
  await cp.writeValueWithResponse(cmd);
}
```

**Without response:** faster, for streams where a lost packet does not matter.

```js
async function sendText(characteristic, text) {
  if (!characteristic.properties.writeWithoutResponse) throw new Error('This characteristic needs writeValue');
  await characteristic.writeValueWithoutResponse(new TextEncoder().encode(text));
}
```

**Notes:** Raw signature `wbWrite(requestId, deviceId, service, characteristic, valueBase64, withResponse)`. The value can be an `ArrayBuffer`, any typed array, a `DataView` or a plain array of bytes; anything else throws `TypeError`. Errors: `NotSupportedError` (write rejected), `NetworkError` (write failed or not connected), `NotFoundError`.

