1. Introduction
This section is non-normative.
Bluetooth is a standard for short-range wireless communication between devices. Bluetooth "Classic" (BR/EDR) defines a set of binary protocols and supports speeds up to about 24Mbps. Bluetooth 4.0 introduced a new "Low Energy" mode known as "Bluetooth Smart", BLE, or just LE which is limited to about 1Mbps but allows devices to leave their transmitters off most of the time. BLE provides most of its functionality through key/value pairs provided by the Generic Attribute Profile (GATT).
BLE defines multiple roles that devices can play. The Broadcaster and Observer roles are for transmitter- and receiver-only applications, respectively. Devices acting in the Peripheral role can receive connections, and devices acting in the Central role can connect to Peripheral devices.
A device acting in either the Peripheral or Central role can host a GATT Server, which exposes a hierarchy of Services, Characteristics, and Descriptors. See §5.1 GATT Information Model for more details about this hierarchy. Despite being designed to support BLE transport, the GATT protocol can also run over BR/EDR transport.
The first version of this specification allows web pages, running on a UA in the Central role, to connect to GATT Servers over either a BR/EDR or LE connection. While this specification cites the [BLUETOOTH42] specification, it intends to also support communication among devices that only implement Bluetooth 4.0 or 4.1.
1.1. Examples
To discover and retrieve data from a standard heart rate monitor, a website would use code like the following:
let chosenHeartRateService = null; navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'], }] }).then(device => device.gatt.connect()) .then(server => server.getPrimaryService('heart_rate')) .then(service => { chosenHeartRateService = service; return Promise.all([ service.getCharacteristic('body_sensor_location') .then(handleBodySensorLocationCharacteristic), service.getCharacteristic('heart_rate_measurement') .then(handleHeartRateMeasurementCharacteristic), ]); }); function handleBodySensorLocationCharacteristic(characteristic) { if (characteristic === null) { console.log("Unknown sensor location."); return Promise.resolve(); } return characteristic.readValue() .then(sensorLocationData => { let sensorLocation = sensorLocationData.getUint8(0); switch (sensorLocation) { case 0: return 'Other'; case 1: return 'Chest'; case 2: return 'Wrist'; case 3: return 'Finger'; case 4: return 'Hand'; case 5: return 'Ear Lobe'; case 6: return 'Foot'; default: return 'Unknown'; } }).then(location => console.log(location)); } function handleHeartRateMeasurementCharacteristic(characteristic) { return characteristic.startNotifications() .then(char => { characteristic.addEventListener('characteristicvaluechanged', onHeartRateChanged); }); } function onHeartRateChanged(event) { let characteristic = event.target; console.log(parseHeartRate(characteristic.value)); }
parseHeartRate() would be defined using the heart_rate_measurement documentation to read the DataView stored
in a BluetoothRemoteGATTCharacteristic's value field.
function parseHeartRate(data) { let flags = data.getUint8(0); let rate16Bits = flags & 0x1; let result = {}; let index = 1; if (rate16Bits) { result.heartRate = data.getUint16(index, /*littleEndian=*/true); index += 2; } else { result.heartRate = data.getUint8(index); index += 1; } let contactDetected = flags & 0x2; let contactSensorPresent = flags & 0x4; if (contactSensorPresent) { result.contactDetected = !!contactDetected; } let energyPresent = flags & 0x8; if (energyPresent) { result.energyExpended = data.getUint16(index, /*littleEndian=*/true); index += 2; } let rrIntervalPresent = flags & 0x10; if (rrIntervalPresent) { let rrIntervals = []; for (; index + 1 < data.byteLength; index += 2) { rrIntervals.push(data.getUint16(index, /*littleEndian=*/true)); } result.rrIntervals = rrIntervals; } return result; }
onHeartRateChanged() might log an object like
{ heartRate: 70, contactDetected: true, energyExpended: 750, // Meaning 750kJ. rrIntervals: [890, 870] // Meaning .87s and .85s. }
If the heart rate sensor reports the energyExpended field,
the web application can reset its value to 0 by writing to the heart_rate_control_point characteristic:
function resetEnergyExpended() { if (!chosenHeartRateService) { return Promise.reject(new Error('No heart rate sensor selected yet.')); } return chosenHeartRateService.getCharacteristic('heart_rate_control_point') .then(controlPoint => { let resetEnergyExpended = new Uint8Array([1]); return controlPoint.writeValue(resetEnergyExpended); }); }
2. Security and privacy considerations
2.1. Device access is powerful
When a website requests access to devices using requestDevice(),
it gets the ability to access all GATT services mentioned in the call.
The UA MUST inform the user what capabilities these services give the website
before asking which devices to entrust to it.
If any services in the list aren’t known to the UA,
the UA MUST assume they give the site complete control over the device
and inform the user of this risk.
The UA MUST also allow the user to inspect what sites have access to what devices
and revoke these pairings.
The UA MUST NOT allow the user to pair entire classes of devices with a website. It is possible to construct a class of devices for which each individual device sends the same Bluetooth-level identifying information. UAs are not required to attempt to detect this sort of forgery and MAY let a user pair this pseudo-device with a website.
To help ensure that only the entity the user approved for access actually has access, this specification requires that only secure contexts can access Bluetooth devices (requestDevice).
2.2. Trusted servers can serve malicious code
This section is non-normative.
Even if the user trusts an origin, that origin’s servers or developers could be compromised, or the origin’s site could be vulnerable to XSS attacks. Either could lead to users granting malicious code access to valuable devices. Origins should define a Content Security Policy ([CSP3]) to reduce the risk of XSS attacks, but this doesn’t help with compromised servers or developers.
The ability to retrieve granted devices after a page reload, provided by §3.1 Permission API Integration, makes this risk worse. Instead of having to get the user to grant access while the site is compromised, the attacker can take advantage of previously-granted devices if the user simply visits while the site is compromised. On the other hand, when sites can keep access to devices across page reloads, they don’t have to show as many permission prompts overall, making it more likely that users will pay attention to the prompts they do see.
2.3. Attacks on devices
This section is non-normative.
Communication from websites can break the security model of some devices, which assume they only receive messages from the trusted operating system of a remote device. Human Interface Devices are a prominent example, where allowing a website to communicate would allow that site to log keystrokes. This specification includes a blacklist of such vulnerable services, characteristics, and descriptors to prevent websites from taking advantage of them.
We expect that many devices are vulnerable to unexpected data delivered to their radio. In the past, these devices had to be exploited one-by-one, but this API makes it plausible to conduct large-scale attacks. This specification takes several approaches to make such attacks more difficult:
- Pairing individual devices instead of device classes requires at least a user action before a device can be exploited.
-
Constraining access to GATT, as opposed to generic byte-stream access,
denies malicious websites access to most parsers on the device.
On the other hand, GATT’s Characteristic and Descriptor values are still byte arrays, which may be set to lengths and formats the device doesn’t expect. UAs are encouraged to validate these values when they can.
- This API never exposes Bluetooth addressing, data signing or encryption keys (Definition of Keys and Values) to websites. This makes it more difficult for a website to predict the bits that will be sent over the radio, which blocks packet-in-packet injection attacks. Unfortunately, this only works over encrypted links, which not all BLE devices are required to support.
UAs can also take further steps to protect their users:
- A web service may collect lists of malicious websites and vulnerable devices. UAs can deny malicious websites access to any device and any website access to vulnerable devices.
2.4. Bluetooth device identifiers
This section is non-normative.
Each Bluetooth BR/EDR device has a unique 48-bit MAC address known as the BD_ADDR. Each Bluetooth LE device has at least one of a Public Device Address and a Static Device Address. The Public Device Address is a MAC address. The Static Device Address may be regenerated on each restart. A BR/EDR/LE device will use the same value for the BD_ADDR and the Public Device Address (specified in the Read BD_ADDR Command).
An LE device may also have a unique, 128-bit Identity Resolving Key, which is sent to trusted devices during the bonding process. To avoid leaking a persistent identifier, an LE device may scan and advertise using a random Resolvable or Non-Resolvable Private Address instead of its Static or Public Address. These are regenerated periodically (approximately every 15 minutes), but a bonded device can check whether one of its stored IRKs matches any given Resolvable Private Address using the Resolvable Private Address Resolution Procedure.
Each Bluetooth device also has a human-readable Bluetooth Device Name. These aren’t guaranteed to be unique, but may well be, depending on the device type.
2.4.1. Identifiers for remote Bluetooth devices
This section is non-normative.
If a website can retrieve any of the persistent device IDs, these can be used, in combination with a large effort to catalog ambient devices, to discover a user’s location. A device ID can also be used to identify that a user who pairs two different websites with the same Bluetooth device is a single user. On the other hand, many GATT services are available that could be used to fingerprint a device, and a device can easily expose a custom GATT service to make this easier.
This specification suggests that the UA use different device IDs for a single device when its user doesn’t intend scripts to learn that it’s a single device, which makes it difficult for websites to abuse the device address like this. Device makers can still design their devices to help track users, but it takes work.
2.4.2. The UA’s Bluetooth address
This section is non-normative.
In BR/EDR mode, or in LE mode during active scanning without the Privacy Feature, the UA broadcasts its persistent ID to any nearby Bluetooth radio. This makes it easy to scatter hostile devices in an area and track the UA. As of 2014-08, few or no platforms document that they implement the Privacy Feature, so despite this spec recommending it, few UAs are likely to use it. This spec does require a user gesture for a website to trigger a scan, which reduces the frequency of scans some, but it would still be better for more platforms to expose the Privacy Feature.
3. Device Discovery
dictionary BluetoothRequestDeviceFilter { sequence<BluetoothServiceUUID> services; DOMString name; DOMString namePrefix; }; dictionary RequestDeviceOptions { required sequence<BluetoothRequestDeviceFilter> filters; sequence<BluetoothServiceUUID> optionalServices = []; }; interface Bluetooth { [SecureContext] readonly attribute BluetoothDevice? referringDevice; [SecureContext] Promise<BluetoothDevice> requestDevice(RequestDeviceOptions options); }; Bluetooth implements EventTarget; Bluetooth implements BluetoothDeviceEventHandlers; Bluetooth implements CharacteristicEventHandlers; Bluetooth implements ServiceEventHandlers;
Bluetooth members referringDevice gives access to
the device from which the user opened this page, if any.
For example, an Eddystone beacon
might advertise a URL, which the UA allows the user to open.
A BluetoothDevice representing the beacon would be available
through navigator.bluetooth.. referringDevice
requestDevice(options) asks the user
to grant this origin access to a device
that matches any filter in options.filters.
To match a filter, the device has to:
- support all the GATT service UUIDs in the services list if that member is present,
- have a name equal to name if that member is present, and
- have a name starting with namePrefix if that member is present.
After the user selects a device to pair with this origin,
the origin is allowed to access to any service whose UUID was listed
in the services list
in any element of options.filters or in options.optionalServices.
This implies that if developers filter just by name,
they must use optionalServices to get access to any services.
Say the UA is close to the following devices:
| Device | Advertised Services |
|---|---|
| D1 | A, B, C, D |
| D2 | A, B, E |
| D3 | C, D |
| D4 | E |
| D5 | <none> |
If the website calls
navigator.bluetooth.requestDevice({ filters: [ {services: [A, B]} ] });
the user will be shown a dialog containing devices D1 and D2. If the user selects D1, the website will not be able to access services C or D. If the user selects D2, the website will not be able to access service E.
On the other hand, if the website calls
navigator.bluetooth.requestDevice({ filters: [ {services: [A, B]}, {services: [C, D]} ] });
the dialog will contain devices D1, D2, and D3, and if the user selects D1, the website will be able to access services A, B, C, and D.
The optionalServices list doesn’t add any devices
to the dialog the user sees,
but it does affect which services the website can use from the device the user picks.
navigator.bluetooth.requestDevice({ filters: [ {services: [A, B]} ], optionalServices: [E] });
Shows a dialog containing D1 and D2, but not D4, since D4 doesn’t contain the required services. If the user selects D2, unlike in the first example, the website will be able to access services A, B, and E.
The allowed services also apply if the device changes after the user grants access.
For example, if the user selects D1 in the previous requestDevice() call,
and D1 later adds a new E service,
that will fire the serviceadded event,
and the web page will be able to access service E.
Say the devices in the previous example also advertise names as follows:
| Device | Advertised Device Name |
|---|---|
| D1 | First De… |
| D2 | <none> |
| D3 | Device Third |
| D4 | Device Fourth |
| D5 | Unique Name |
The following table shows which devices the user can select between
for several values of filters passed to navigator.bluetooth.requestDevice({filters: filters}).
| filters | Devices | Notes |
|---|---|---|
[{name: "Unique Name"}] | D5 | |
[{namePrefix: "Device"}] | D3, D4 | |
[{name: "First De"}, {name: "First Device"}] | <none> | D1 only advertises a prefix of its name, so trying to match its whole name fails. |
[{namePrefix: "First"}, {name: "Unique Name"}] | D1, D5 | |
[{services: [C], namePrefix: "Device"}, {name: "Unique Name"}] | D3, D5 |
Filters that either accept or reject all possible devices cause TypeErrors.
| filters | Notes |
|---|---|
[]
| An empty list of filters doesn’t accept any devices. |
[{}]
| An empty filter accepts all devices, and so isn’t allowed either. |
[{namePrefix: ""}] | namePrefix, if present, must be non-empty to filter devices.
|
Instances of Bluetooth are created with the internal slots
described in the following table:
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| [[deviceInstanceMap]] | An empty map from Bluetooth devices
to instances.
| Ensures only one BluetoothDevice instance represents each Bluetooth device inside a single global object.
|
| [[attributeInstanceMap]] | An empty map from Bluetooth cache entries to Promises.
| The Promises resolve to either BluetoothRemoteGATTService, BluetoothRemoteGATTCharacteristic, or BluetoothRemoteGATTDescriptor instances.
|
| [[referringDevice]] | null
| Set to a BluetoothDevice while initialising a new Document object if the Document was opened from the device.
|
Getting navigator.bluetooth.referringDevice must return [[referringDevice]].
Some UAs may allow the user
to cause a browsing context to navigate in response to a Bluetooth device. For example, if an Eddystone beacon
advertises a URL,
the UA may allow the user to navigate to this URL. If this happens, then as part of initialising a new Document object,
the UA MUST run the following steps:
- Let referringDevice be the device that caused the navigation.
- Get the
BluetoothDevicerepresenting referringDevice insidenavigator.bluetooth, and let referringDeviceObj be the result. -
If the previous step threw an exception, abort these steps.
Note: This means the UA didn’t infer that the user intended to grant the current realm access to referringDevice. For example, the user might have denied GATT access globally.
- Set
navigator.bluetooth@to referringDeviceObj.[[referringDevice]]
A Bluetooth device device matches a filter filter if the following steps return match:
- If
filter.nameis present then, if device’s Bluetooth Device Name isn’t complete and equal tofilter.name, returnmismatch. - If
filter.namePrefixis present then if device’s Bluetooth Device Name isn’t present or doesn’t start withfilter.namePrefix, returnmismatch. - For each uuid in
filter.services, if the UA has not received advertising data, an extended inquiry response, or a service discovery response indicating that the device supports a primary (vs included) service with UUID uuid, returnmismatch. - Return
match.
The list of Service UUIDs that a device advertises might not include all the UUIDs the device supports. The advertising data does specify whether this list is complete. If a website filters for a UUID that a nearby device supports but doesn’t advertise, that device might not be included in the list of devices presented to the user. The UA would need to connect to the device to discover the full list of supported services, which can impair radio performance and cause delays, so this spec doesn’t require it.
The requestDevice(options) method,
when invoked, MUST return a new promise promise and run the following steps in parallel:
- Request Bluetooth devices,
passing
options.andfiltersoptions., and let devices be the result.optionalServices - If the previous step threw an exception, reject promise with that exception and abort these steps.
- If devices is an empty sequence, reject promise with a
NotFoundErrorand abort these steps. - Resolve promise with
devices[0].
To request Bluetooth devices,
given a sequence of BluetoothRequestDeviceFilters, filters,
and a sequence of BluetoothServiceUUIDs, optionalServices,
the UA MUST run the following steps:
Calls to this algorithm will eventually be able to request multiple devices, but for now it only ever returns a single one.
- If the algorithm is not triggered by user activation,
throw a
SecurityErrorand abort these steps. -
In order to convert the arguments from service names and aliases to just UUIDs,
do the following sub-steps:
- If
filters.length === 0, throw aTypeErrorand abort these steps. - Let uuidFilters be a new
Arrayand requiredServiceUUIDs be a newSet. -
For each filter in filters,
do the following steps:
- If none of filter’s
services,name, ornamePrefixmembers is present, throw aTypeErrorand abort these steps. - Let canonicalizedFilter be
{}. -
If
filter.servicesis present, do the following sub-steps:- If
filter.services.length === 0, throw aTypeErrorand abort these steps. - Let services be
.Array.prototype.map.call(filter.services,BluetoothUUID.getService) - If any of the
BluetoothUUID.getService()calls threw an exception, throw that exception and abort these steps. - If any service in services is blacklisted,
throw a
SecurityErrorand abort these steps. - Set
canonicalizedFilter.servicesto services. - Add the elements of services to requiredServiceUUIDs.
- If
-
If
filter.nameis present, do the following sub-steps.-
If the UTF-8 encoding of
filter.nameis more than 248 bytes long, throw aTypeErrorand abort these steps.248 is the maximum number of UTF-8 code units in a Bluetooth Device Name.
- Set
canonicalizedFilter.nametofilter.name.
-
If the UTF-8 encoding of
-
If
filter.namePrefixis present, do the following sub-steps.-
If
filter.namePrefix.length === 0or if the UTF-8 encoding offilter.namePrefixis more than 248 bytes long, throw aTypeErrorand abort these steps.248 is the maximum number of UTF-8 code units in a Bluetooth Device Name.
- Set
canonicalizedFilter.namePrefixtofilter.namePrefix.
-
If
- Append canonicalizedFilter to uuidFilters.
- If none of filter’s
- Let optionalServiceUUIDs be
.Array.prototype.map.call(optionalServices,BluetoothUUID.getService) - If any of the
BluetoothUUID.getService()calls threw an exception, throw that exception and abort these steps. - Remove from optionalServiceUUIDs any UUIDs that are blacklisted.
- If
-
Let descriptor be
{ name: "bluetooth", filters: uuidFilters, optionalServices: optionalServiceUUIDs, }
-
Let state be descriptor’s permission state.
Note: state will be
"denied"in non-secure contexts because"bluetooth"doesn’t set the allowed in non-secure contexts flag. - If state is
"denied", return[]and abort these steps. - Scan for devices with requiredServiceUUIDs as the set of Service UUIDs, and let scanResult be the result.
- Remove devices from scanResult if they do not match a filter in uuidFilters.
-
Even if scanResult is empty, prompt the user to choose one of the devices in scanResult,
associated with descriptor,
and let device be the result.
The UA MAY allow the user to select a nearby device that does not match uuidFilters.
The UA should show the user the human-readable name of each device. If this name is not available because, for example, the UA’s Bluetooth system doesn’t support privacy-enabled scans, the UA should allow the user to indicate interest and then perform a privacy-disabled scan to retrieve the name.
- If device is
"denied", return[]and abort these steps. - Choosing a device probably indicates that
the user intends that device to appear in
the
allowedDeviceslist of"bluetooth"'s extra permission data for at least the current settings object, and for all the services in the union of requiredServiceUUIDs and optionalServiceUUIDs to appear in itsallowedServiceslist, in addition to any services that were already there. - The UA MAY populate the Bluetooth cache with all Services inside device. Ignore any errors from this step.
- Get the
BluetoothDevicerepresenting device inside the context object, propagating any exception, and let deviceObj be the result. - Return
[deviceObj].
To scan for devices with an optional set of Service UUIDs, defaulting to the set of all UUIDs, the UA MUST perform the following steps:
- If the UA has scanned for devices recently TODO: Nail down the amount of time. with a set of UUIDs that was a superset of the UUIDs for the current scan, then the UA MAY return the result of that scan and abort these steps.
- Let nearbyDevices be a set of Bluetooth devices, initially equal to the set of devices that are connected (have an ATT Bearer) to the UA.
-
If the UA supports the LE transport, perform the General Discovery Procedure,
except that the UA may include devices that have no Discoverable Mode flag set,
and add the discovered Bluetooth devices to nearbyDevices.
The UA SHOULD enable the Privacy Feature.
Both passive scanning and the Privacy Feature avoid leaking the unique, immutable device ID. We ought to require UAs to use either one, but none of the OS APIs appear to expose either. Bluetooth also makes it hard to use passive scanning since it doesn’t require Central devices to support the Observation Procedure.
-
If the UA supports the BR/EDR transport, perform the Device Discovery Procedure and add the discovered Bluetooth devices to nearbyDevices.
All forms of BR/EDR inquiry/discovery appear to leak the unique, immutable device address.
- Let result be a set of Bluetooth devices, initially empty.
-
For each Bluetooth device device in nearbyDevices,
do the following sub-steps:
- If device’s supported physical transports include LE and its Bluetooth Device Name is partial or absent, the UA SHOULD perform the Name Discovery Procedure to acquire a complete name.
-
If device’s advertised Service UUIDs have a non-empty intersection with the set of Service UUIDs,
add device to result and abort these sub-steps.
For BR/EDR devices, there is no way to distinguish GATT from non-GATT services in the Extended Inquiry Response. If a site filters to the UUID of a non-GATT service, the user may be able to select a device for the result of
requestDevicethat this API provides no way to interact with. -
The UA MAY connect to device and populate the Bluetooth cache with all Services whose UUIDs are in the set of Service UUIDs.
If device’s supported physical transports include BR/EDR,
then in addition to the standard GATT procedures,
the UA MAY use the Service Discovery Protocol (Searching for Services)
when populating the cache.
Connecting to every nearby device to discover services costs power and can slow down other use of the Bluetooth radio. UAs should only discover extra services on a device if they have some reason to expect that device to be interesting.
UAs should also help developers avoid relying on this extra discovery behavior. For example, say a developer has previously connected to a device, so the UA knows the device’s full set of supported services. If this developer then filters using a non-advertised UUID, the dialog they see may include this device, even if the filter would likely exclude the device on users' machines. The UA could provide a developer option to warn when this happens or to include only advertised services in matching filters.
- If the Bluetooth cache contains known-present Services inside device with UUIDs in the set of Service UUIDs, the UA MAY add device to result.
- Return result from the scan.
We need a way for a site to register to receive an event when an interesting device comes within range.
3.1. Permission API Integration
The [permissions] API provides a uniform way for websites to request permissions from users and query which permissions they have.
Sites can use navigator.permissions. as an alternate spelling of request({name: "bluetooth", ...})navigator.bluetooth.. requestDevice()
navigator.permissions.request({ name: "bluetooth", filters: [{ services: ['heart_rate'], }] }).then(result => { if (result.devices.length > 1) { return result.devices[0]; } else { throw new DOMException("Chooser cancelled", "NotFoundError"); } }).then(device => { sessionStorage.lastDevice = device.id; });
Once a site has been granted access to a set of devices,
it can use navigator.permissions. to retrieve those devices after a reload. query({name: "bluetooth", ...})
navigator.permissions.query({ name: "bluetooth", deviceId: sessionStorage.lastDevice, }).then(result => { if (result.devices.length == 1) { return result.devices[0]; } else { throw new DOMException("Lost permission", "NotFoundError"); } }).then(...);
The "bluetooth" powerful feature’s permission-related algorithms and types are defined as follows:
- permission descriptor type
-
dictionary BluetoothPermissionDescriptor : PermissionDescriptor { DOMString deviceId; // These match RequestDeviceOptions. sequence<BluetoothRequestDeviceFilter> filters; sequence<BluetoothServiceUUID> optionalServices = []; };
- extra permission data type
-
BluetoothPermissionData, defined as:dictionary AllowedBluetoothDevice { required DOMString deviceId; // An allowedServices of "all" means all services are allowed. required (DOMString or sequence<UUID>) allowedServices; }; dictionary BluetoothPermissionData { required sequence<AllowedBluetoothDevice> allowedDevices = []; };
AllowedBluetoothDeviceinstances have an internal slot [[device]] that holds a Bluetooth device.Distinct elements of
allowedDevicesmust have different[[device]]s and differentdeviceIds.A
deviceIdallows a site to track that aBluetoothDeviceinstance seen at one time represents the same device as anotherBluetoothDeviceinstance seen at another time, possibly in a different realm. UAs should consider whether their user intends that tracking to happen or not-happen when returning"bluetooth"'s extra permission data.For example, users generally don’t intend two different origins to know that they’re interacting with the same device, and they generally don’t intend unique identifiers to persist after they’ve cleared an origin’s cookies.
- permission result type
-
interface BluetoothPermissionResult : PermissionStatus { attribute FrozenArray<BluetoothDevice> devices; };
- permission request algorithm
-
Given a
BluetoothPermissionDescriptoroptions and aBluetoothPermissionResultstatus, the UA MUST run the following steps:- Request Bluetooth devices,
passing
options.andfiltersoptions., propagating any exception, and let devices be the result.optionalServices - Set
status.devicesto a newFrozenArraywhose contents are the elements of devices.
- Request Bluetooth devices,
passing
- permission query algorithm
-
To query the "bluetooth" permission with
a
BluetoothPermissionDescriptordesc and aBluetoothPermissionResultstatus, the UA must:- Let global be the global object associated with status.
- Set
status.to desc’s permission state.state - If
status.isstate"denied", setstatus.devicesto an emptyFrozenArrayand abort these steps. - Let matchingDevices be a new
Array. - Let data, a
BluetoothPermissionData, be"bluetooth"'s extra permission data for the current settings object. -
For each allowedDevice in
data.allowedDevices, run the following sub-steps:- If
desc.deviceIdis set andallowedDevice.deviceId != desc.deviceId, continue to the next allowedDevice. - If
desc.filtersis set andallowedDevice@does not match a filter in[[device]]desc.filters, continue to the next allowedDevice. - Note: The
desc.optionalServicesfield does not affect the result. - Get the
BluetoothDevicerepresentingallowedDevice@within[[device]]global.navigator.bluetooth, and add the result to matchingDevices.
- If
- Set
status.devicesto a newFrozenArraywhose contents are matchingDevices.
- permission revocation algorithm
-
To revoke Bluetooth access to devices the user no longer intends to expose, the UA MUST run the following steps:
- Let data, a
BluetoothPermissionData, be"bluetooth"'s extra permission data for the current settings object. -
For each
BluetoothDeviceinstance deviceObj in the current realm, run the following sub-steps:-
If there is an
AllowedBluetoothDeviceallowedDevice indata.such that:allowedDevices-
allowedDevice.is the same device as[[device]]deviceObj., and[[representedDevice]] -
allowedDevice.,deviceId=== deviceObj.id
deviceObj.to be[[allowedServices]]allowedDevice., and continue to the next deviceObj.allowedServices -
- Otherwise, detach deviceObj from its device by running the remaining steps.
-
Call
deviceObj.gatt..disconnect()Note: This fires a
gattserverdisconnectedevent at deviceObj. - Set
deviceObj@to[[representedDevice]]null.
-
If there is an
- Let data, a
4. Device Representation
The UA needs to track Bluetooth device properties at several levels: globally, per origin, and per global object.
4.1. Global Bluetooth device properties
The physical Bluetooth device may be guaranteed to have some properties that the UA may not have received. Those properties are described as optional here.
A Bluetooth device has the following properties. Optional properties are not present, and sequence and map properties are empty, unless/until described otherwise. Other properties have a default specified or are specified when a device is introduced.
- A set of supported physical transports, including one or both of BR/EDR and LE. This set will generally be filled based on the transports over which the device was discovered and the Flags Data Type in the Advertising Data or Extended Inquiry Response.
- One or more of several kinds of 48-bit address: a Public Bluetooth Address, a (random) Static Address, and a resolvable or non-resolvable Private Address.
- An optional 128-bit Identity Resolving Key.
- An optional partial or complete Bluetooth Device Name. A device has a partial name when the Shortened Local Name AD data was received, but the full name hasn’t been read yet. The Bluetooth Device Name is encoded as UTF-8 and converted to a DOMString using the utf-8 decode without BOM algorithm.
- An optional ATT Bearer, over which all GATT communication happens. The ATT Bearer is created by procedures described in "Connection Establishment" under GAP Interoperability Requirements. It is disconnected in ways [BLUETOOTH42] isn’t entirely clear about.
- A list of advertised Service UUIDs from the Advertising Data or Extended Inquiry Response.
- A hierarchy of GATT attributes, described in §5.1 GATT Information Model.
The UA SHOULD determine that two Bluetooth devices are the same Bluetooth device if and only if they have the same Public Bluetooth Address, Static Address, Private Address, or Identity Resolving Key, or if the Resolvable Private Address Resolution Procedure succeeds using one device’s IRK and the other’s Resolvable Private Address. However, because platform APIs don’t document how they determine device identity, the UA MAY use another procedure.
4.2. BluetoothDevice
A BluetoothDevice instance represents a Bluetooth device for a particular global object (or, equivalently, for a particular Realm or Bluetooth instance).
interface BluetoothDevice { readonly attribute DOMString id; readonly attribute DOMString? name; readonly attribute BluetoothRemoteGATTServer? gatt; readonly attribute FrozenArray<UUID> uuids; Promise<void> watchAdvertisements(); void unwatchAdvertisements(); readonly attribute boolean watchingAdvertisements; }; BluetoothDevice implements EventTarget; BluetoothDevice implements BluetoothDeviceEventHandlers; BluetoothDevice implements CharacteristicEventHandlers; BluetoothDevice implements ServiceEventHandlers;
BluetoothDevice attributesid uniquely identifies a device to the extent that the UA can determine that two Bluetooth connections are to the same device and to the extent that the user wants to expose that fact to script.
name is the human-readable name of the device.
gatt provides a way to interact with this device’s GATT server
if the site has permission to use any of its services.
uuids lists
the UUIDs of GATT services known to be on the device,
that the current origin is allowed to access.
watchingAdvertisements is true if the UA is currently scanning for advertisements from this device and firing events for them.
Instances of BluetoothDevice are created with the internal slots
described in the following table:
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| [[context]] | undefined
| The Bluetooth object that returned this BluetoothDevice.
|
| [[representedDevice]] | undefined
| The Bluetooth device this object represents. |
| [[gatt]] | a new BluetoothRemoteGATTServer instance with
its device attribute initialized to this and its connected attribute initialized to false.
| Does not change. |
| [[allowedServices]] | undefined
| This device’s allowedServices list for this origin
or "all" if all services are allowed.
For example, a UA may grant an origin access to all services on
a referringDevice that advertised a URL on that origin.
|
| [[unfilteredUuids]] | new
| Service UUIDs known to be in this device’s GATT Server. This set may be incomplete if the UA hasn’t run a full service discovery. |
| [[cachedAllowedServices]] | undefined
| Value of [[allowedServices]] the last time [[filteredUuids]] was updated.
|
| [[cachedUnfilteredUuids]] | undefined
| Value of [[unfilteredUuids]] the last time [[filteredUuids]] was updated.
|
| [[filteredUuids]] | undefined
| Cached value of uuids.
Up to date if [[cachedAllowedServices]] is equal to [[allowedServices]].
|
To get the BluetoothDevice representing a Bluetooth device device inside a Bluetooth instance context,
the UA MUST run the following steps:
- Let data, a
BluetoothPermissionData, be"bluetooth"'s extra permission data for the current settings object. - Find the allowedDevice in
data.withallowedDevicesallowedDevice@the same device as device. If there is no such object, throw a[[device]]SecurityErrorand abort these steps. -
If there is no key in context@
[[deviceInstanceMap]]that is the same device as device, run the following sub-steps:- Let result be a new instance of
BluetoothDevice. - Initialize all of result’s optional fields to
null. - Initialize
result@to context.[[context]] - Initialize
result@to device.[[representedDevice]] - Initialize
result.idtoallowedDevice., and initializedeviceIdresult@to[[allowedServices]]allowedDevice..allowedServices - If device has a partial or complete Bluetooth Device Name,
set
result.nameto that string. - Initialize
result.watchingAdvertisementstofalse. - Add device’s
advertised Service UUIDs to
result@.[[unfilteredUuids]] - If the Bluetooth cache contains
known-present Services inside device,
add the UUIDs of those Services to
result@.[[unfilteredUuids]] - Add a mapping from device to result in context@
[[deviceInstanceMap]].
- Let result be a new instance of
- Return the value in context@
[[deviceInstanceMap]]whose key is the same device as device.
Getting the gatt attribute
MUST perform the following steps:
- If
this.is an empty list, return[[allowedServices]]null. - Return
this..[[gatt]]
Getting the uuids attribute
MUST perform the following steps:
-
If
[[cachedAllowedServices]]is not equal to[[allowedServices]]or[[cachedUnfilteredUuids]]is not equal to[[unfilteredUuids]], perform the following sub-steps:- Set
[[cachedAllowedServices]]to a copy of[[allowedServices]]. - Set
[[cachedUnfilteredUuids]]to a copy of[[unfilteredUuids]]. -
Set
[[filteredUuids]]to a newFrozenArrayconsisting of:- If
[[allowedServices]]is"all" - the elements of
[[unfilteredUuids]] - Otherwise,
- the intersection of
[[unfilteredUuids]]and[[allowedServices]].
- If
- Set
- Return
[[filteredUuids]].
Scanning costs power,
so websites should avoid watching for advertisements unnecessarily,
and should call unwatchAdvertisements() to stop using power as soon as possible.
The watchAdvertisements() method,
when invoked,
MUST return a new promise promise and
run the following steps in parallel:
- Ensure that the UA is scanning for this device’s advertisements. The UA SHOULD NOT filter out "duplicate" advertisements for the same device.
-
If the UA fails to enable scanning, reject promise with one of the following errors,
and abort these steps:
- The UA doesn’t support scanning for advertisements
NotSupportedError- Bluetooth is turned off
InvalidStateError- Other reasons
UnknownError
-
Queue a task to perform the following steps:
- Set
this.towatchingAdvertisementstrue. - Resolve promise with
undefined.
- Set
The unwatchAdvertisements() method,
when invoked,
MUST run the following steps:
- Set
this.towatchingAdvertisementsfalse. - If no more
BluetoothDevices in the whole UA havewatchingAdvertisementsset totrue, the UA SHOULD stop scanning for advertisements. Otherwise, if no moreBluetoothDevices representing the same device asthishavewatchingAdvertisementsset totrue, the UA SHOULD reconfigure the scan to avoid receiving reports for this device.
4.2.1. Responding to Advertising Events
When an advertising event arrives for a BluetoothDevice with watchingAdvertisements set,
the UA delivers an "advertisementreceived" event.
interface BluetoothManufacturerDataMap { readonly maplike<unsigned short, DataView>; }; interface BluetoothServiceDataMap { readonly maplike<UUID, DataView>; }; [Constructor(DOMString type, BluetoothAdvertisingEventInit init)] interface BluetoothAdvertisingEvent : Event { readonly attribute BluetoothDevice device; readonly attribute FrozenArray<UUID> uuids; readonly attribute DOMString? name; readonly attribute unsigned short? appearance; readonly attribute byte? txPower; readonly attribute byte? rssi; readonly attribute BluetoothManufacturerDataMap manufacturerData; readonly attribute BluetoothServiceDataMap serviceData; }; dictionary BluetoothAdvertisingEventInit : EventInit { required BluetoothDevice device; sequence<(DOMString or unsigned long)> uuids; DOMString name; unsigned short appearance; byte txPower; byte rssi; Map manufacturerData; Map serviceData; };
BluetoothAdvertisingEvent attributes device is the BluetoothDevice that sent this advertisement.
uuids lists the Service UUIDs that
this advertisement says device's GATT server supports.
name is device's local name, or a prefix of it.
appearance is an Appearance, one of the values defined by
the org.bluetooth.characteristic.gap.appearance characteristic.
txPower is
the transmission power at which the device is broadcasting, measured in dBm.
This is used to compute the path loss as this.txPower - this.rssi.
rssi is
the power at which the advertisement was received, measured in dBm.
This is used to compute the path loss as this.txPower - this.rssi.
manufacturerData maps unsigned short Company Identifier Codes
to DataViews.
To retrieve a device and read the iBeacon data out of it,
a developer could use the following code.
Note that this API currently doesn’t provide a way
to request devices with certain manufacturer data,
so the iBeacon will need to rotate its advertisements to include a known service
in order for users to select this device in the requestDevice dialog.
var known_service = "A service in the iBeacon’s GATT server"; return navigator.bluetooth.requestDevice({ filters: [{services: [known_service]}] }).then(device => { device.watchAdvertisements(); device.addEventListener('advertisementreceived', interpretIBeacon); }); function interpretIBeacon(event) { var rssi = event.rssi; var appleData = event.manufacturerData.get(0x004C); if (appleData.byteLength != 23 || appleData.getUint16(0, false) !== 0x0215) { console.log({isBeacon: false}); } var uuidArray = new Uint8Array(appleData.buffer, 2, 16); var major = appleData.getUint16(18, false); var minor = appleData.getUint16(20, false); var txPowerAt1m = -appleData.getInt8(22); console.log({ isBeacon: true, uuidArray, major, minor, pathLossVs1m: txPowerAt1m - rssi}); });
The format of iBeacon advertisements was derived from How do iBeacons work? by Adam Warski.
When the UA receives an advertising event (consisting of an advertising packet and an optional scan response), it MUST run the following steps:
- Let device be the Bluetooth device that sent the advertising event.
-
For each
BluetoothDevicedeviceObj in the UA such that device is the same device asdeviceObj@, queue a task on deviceObj’s relevant settings object’s responsible event loop to do the following sub-steps:[[representedDevice]]- If
deviceObj.iswatchingAdvertisementsfalse, abort these sub-steps. - Fire an
advertisementreceivedevent for the advertising event at deviceObj.
- If
To fire an advertisementreceived event for an advertising event adv at a BluetoothDevice deviceObj,
the UA MUST perform the following steps:
-
Let event be
{ bubbles: true, device: deviceObj, uuids: [], manufacturerData: new Map(), serviceData: new Map() }
- If the received signal strength is available
for any packet in adv,
set
event.rssito this signal strength in dBm. -
For each AD structure in adv’s advertising packet and scan response,
select from the following steps depending on the AD type:
- Incomplete List of 16-bit Service UUIDs
- Complete List of 16-bit Service UUIDs
- Incomplete List of 32-bit Service UUIDs
- Complete List of 32-bit Service UUIDs
- Incomplete List of 128-bit Service UUIDs
- Complete List of 128-bit Service UUIDs
- Complete List of 16-bit Service UUIDs
- Append the listed UUIDs to
event.uuids. - Shortened Local Name
- Complete Local Name
-
UTF-8 decode without BOM the AD data and
set
event.nameto the result.Note: We don’t expose whether the name is complete because existing APIs require reading the raw advertisement to get this information, and we want more evidence that it’s useful before adding a field to the API.
- Manufacturer Specific Data
- Add to
event.manufacturerDataa mapping from the 16-bit Company Identifier Code to anArrayBuffercontaining the manufacturer-specific data. - TX Power Level
- Set
event.txPowerto the AD data. - Service Data - 16 bit UUID
- Service Data - 32 bit UUID
- Service Data - 128 bit UUID
- Service Data - 32 bit UUID
- Add to
event.serviceDataa mapping from the UUID to anArrayBuffercontaining the service data. - Appearance
- Set
event.appearanceto the AD data. - Otherwise
- Skip to the next AD structure.
- Incomplete List of 16-bit Service UUIDs
- Fire an event initialized as
new, with itsBluetoothAdvertisingEvent("advertisementreceived", event)isTrustedattribute initialized totrue, at deviceObj.
All fields in BluetoothAdvertisingEvent return
the last value they were initialized or set to.
The BluetoothAdvertisingEvent(type, init) constructor MUST perform the following steps:
- Let event be the result of running the steps from DOM §3.4 Constructing events except for the
uuids,manufacturerData, andserviceDatamembers. - If
init.uuidsis set, initializeevent.uuidsto a newFrozenArraycontaining the elements ofinit.uuids.map(. Otherwise initializeBluetoothUUID.getService)event.uuidsto an emptyFrozenArray. -
For each mapping in
init.manufacturerData:- Let code be the key converted to an
unsigned short. - Let value be the value.
- If value is not a
BufferSource, throw aTypeError. - Let bytes be a new read only ArrayBuffer containing a copy of the bytes held by value.
- Add a mapping from code to
new DataView(bytes)inevent.manufacturerData@.[[data]]
- Let code be the key converted to an
-
For each mapping in
init.serviceData:- Let key be the key.
- Let service be the result of calling
BluetoothUUID.getService(key). - Let value be the value.
- Set value is not a
BufferSource, throw aTypeError. - Let bytes be a new read only ArrayBuffer containing a copy of the bytes held by value.
- Add a mapping from service to
new DataView(bytes)inevent.serviceData@.[[data]]
- Return event.
4.2.1.1. BluetoothManufacturerDataMap
Instances of BluetoothManufacturerDataMap are created with the internal slots
described in the following table:
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| [[data]] | new
| The advertised manufacturer data, converted to DataViews.
|
The map entries of a BluetoothManufacturerDataMap instance are
the entries in . [[data]]
4.2.1.2. BluetoothServiceDataMap
Instances of BluetoothServiceDataMap are created with the internal slots
described in the following table:
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| [[data]] | new
| The advertised service data, converted to DataViews.
|
The map entries of a BluetoothServiceDataMap instance are
the entries in . [[data]]
5. GATT Interaction
5.1. GATT Information Model
The GATT Profile Hierarchy describes how a GATT Server contains a hierarchy of Profiles, Primary Services, Included Services, Characteristics, and Descriptors.
Profiles are purely logical: the specification of a Profile describes the expected interactions between the other GATT entities the Profile contains, but it’s impossible to query which Profiles a device supports.
GATT Clients can discover and interact with the Services, Characteristics, and Descriptors on a device using a set of GATT procedures. This spec refers to Services, Characteristics, and Descriptors collectively as Attributes. All Attributes have a type that’s identified by a UUID. Each Attribute also has a 16-bit Attribute Handle that distinguishes it from other Attributes of the same type on the same GATT Server. Attributes are notionally ordered within their GATT Server by their Attribute Handle, but while platform interfaces provide attributes in some order, they do not guarantee that it’s consistent with the Attribute Handle order.
A Service contains a collection of Included Services and Characteristics. The Included Services are references to other Services, and a single Service can be included by more than one other Service. Services are known as Primary Services if they appear directly under the GATT Server, and Secondary Services if they’re only included by other Services, but Primary Services can also be included.
A Characteristic contains a value, which is an array of bytes, and a collection of Descriptors. Depending on the properties of the Characteristic, a GATT Client can read or write its value, or register to be notified when the value changes.
Finally, a Descriptor contains a value (again an array of bytes) that describes or configures its Characteristic.
5.1.1. The Bluetooth cache
The UA MUST maintain a Bluetooth cache of the hierarchy of Services, Characteristics, and Descriptors
it has discovered on a device.
The UA MAY share this cache between multiple origins accessing the same device.
Each potential entry in the cache is either known-present, known-absent, or unknown.
The cache MUST NOT contain two entries that are for the same attribute.
Each known-present entry in the cache is associated with an optional Promise<, BluetoothRemoteGATTService>Promise<,
or BluetoothRemoteGATTCharacteristic>Promise< instance
for each BluetoothRemoteGATTDescriptor>Bluetooth instance.
For example, if a user calls the serviceA.getCharacteristic(uuid1) function
with an initially empty Bluetooth cache,
the UA uses the Discover Characteristics by UUID procedure
to fill the needed cache entries,
and the UA ends the procedure early because
it only needs one Characteristic to fulfil the returned Promise,
then the first Characteristic with UUID uuid1 inside serviceA is known-present,
and any subsequent Characteristics with that UUID remain unknown.
If the user later calls serviceA.getCharacteristics(uuid1),
the UA needs to resume or restart the Discover Characteristics by UUID procedure.
If it turns out that serviceA only has one Characteristic with UUID uuid1,
then the subsequent Characteristics become known-absent.
The known-present entries in the Bluetooth cache are ordered: Primary Services appear in a particular order within a device, Included Services and Characteristics appear in a particular order within Services, and Descriptors appear in a particular order within Characteristics. The order SHOULD match the order of Attribute Handles on the device, but UAs MAY use another order if the device’s order isn’t available.
To populate the Bluetooth cache with entries matching some description, the UA MUST run the following steps. Note that these steps can block, so uses of this algorithm must be in parallel.
- Attempt to make all matching entries in the cache either known-present or known-absent, using any sequence of GATT procedures that [BLUETOOTH42] specifies will return enough information. Handle errors as described in §5.7 Error handling.
- If the previous step returns an error, return that error from this algorithm.
To query the Bluetooth cache in
a BluetoothDevice instance deviceObj for entries matching some description,
the UA MUST return a deviceObj.gatt-connection-checking wrapper around a new promise promise and run the following steps in parallel:
- If
deviceObj.gatt.isconnectedfalse, reject promise with aNetworkErrorand abort these steps. - Populate the Bluetooth cache with entries matching the description.
- If the previous step returns an error, reject promise with that error and abort these steps.
- Let entries be the sequence of known-present cache entries matching the description.
- Let context be
deviceObj@.[[context]] - Let result be a new sequence.
-
For each entry in entries:
- If entry has no associated
Promise<BluetoothGATT*>instance in context@[[attributeInstanceMap]], create aBluetoothRemoteGATTServicerepresenting entry, create aBluetoothRemoteGATTCharacteristicrepresenting entry, or create aBluetoothRemoteGATTDescriptorrepresenting entry, depending on whether entry is a Service, Characteristic, or Descriptor, and add a mapping from entry to the resultingPromisein context@[[attributeInstanceMap]]. - Append to result the
Promise<BluetoothGATT*>instance associated with entry in context@[[attributeInstanceMap]].
- If entry has no associated
- Resolve promise with the result of waiting for all elements of result.
5.1.2. Navigating the Bluetooth Hierarchy
To GetGATTChildren(attribute: GATT Attribute,
single: boolean,
uuidCanonicalizer: function,
uuid: optional (DOMString or unsigned int),
allowedUuids: optional ("all" or Array<DOMString>),
child type: GATT declaration type),
the UA MUST perform the following steps:
- If uuid is present, set it to uuidCanonicalizer(uuid). If uuidCanonicalizer threw an exception, return a promise rejected with that exception and abort these steps.
- If uuid is present and is blacklisted,
return a promise rejected with a
SecurityErrorand abort these steps. -
Let deviceObj be, depending on the type of attribute:
BluetoothDeviceattributeBluetoothRemoteGATTServiceattribute.deviceBluetoothRemoteGATTCharacteristicattribute.service.device
-
Query the Bluetooth cache in
deviceObjfor entries that:- are within the Bluetooth attribute or device represented by attribute,
- have a type described by child type,
- have a UUID that is not blacklisted,
- if uuid is present, have a UUID of uuid,
- if allowedUuids is present and not
"all", have a UUID in allowedUuids, and - if the single flag is set, are the first of these.
-
Return the result of transforming promise with a fulfillment handler that:
- If its argument is empty, throws a
NotFoundError, - Otherwise, if the single flag is set, returns the first (only) element of its argument.
- Otherwise, returns its argument.
- If its argument is empty, throws a
5.1.3. Identifying Services, Characteristics, and Descriptors
When checking whether two Services, Characteristics, or Descriptors a and b are the same attribute, the UA SHOULD determine that they are the same if a and b are inside the same device and have the same Attribute Handle, but MAY use any algorithm it wants with the constraint that a and b MUST NOT be considered the same attribute if they fit any of the following conditions:
- They are not both Services, both Characteristics, or both Descriptors.
- They are both Services, but are not both primary or both secondary services.
- They have different UUIDs.
- Their parent Devices aren’t the same device or their parent Services or Characteristics aren’t the same attribute.
This definition is loose because platform APIs expose their own notion of identity without documenting whether it’s based on Attribute Handle equality.
For two Javascript objects x and y representing Services, Characteristics, or Descriptors, x === y returns
whether the objects represent the same attribute,
because of how the query the Bluetooth cache algorithm
creates and caches new objects.
5.2. BluetoothRemoteGATTServer
BluetoothRemoteGATTServer represents a GATT Server on a remote device.
interface BluetoothRemoteGATTServer { readonly attribute BluetoothDevice device; readonly attribute boolean connected; Promise<BluetoothRemoteGATTServer> connect(); void disconnect(); Promise<BluetoothRemoteGATTService> getPrimaryService(BluetoothServiceUUID service); Promise<sequence<BluetoothRemoteGATTService>> getPrimaryServices(optional BluetoothServiceUUID service); };
BluetoothRemoteGATTServer attributesdevice is the device running this server.
connected is true while this instance
is connected to this.device.
It can be false while the UA is physically connected,
for example when there are other connected BluetoothRemoteGATTServer instances
for other global objects.
When no ECMAScript code can
observe an instance of BluetoothRemoteGATTServer server anymore,
the UA SHOULD run server.. Because disconnect()BluetoothDevice instances are stored in navigator.bluetooth.,
this can’t happen at least until navigation releases the global object or
closing the tab or window destroys the browsing context. Disconnecting on garbage collection ensures that
the UA doesn’t keep consuming resources on the remote device unnecessarily. [[deviceInstanceMap]]
Instances of BluetoothRemoteGATTServer are created with the internal slots
described in the following table:
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| [[activeAlgorithms]] | new
| Contains a Promise corresponding to each algorithm using this server’s connection. disconnect() empties this set so that
the algorithm can tell whether its realm was ever disconnected while it was running.
|
The connect() method, when invoked,
MUST return a new promise promise and
run the following steps in parallel:
- If
this.device@is[[representedDevice]]null, reject promise with aNetworkErrorand abort these steps. - If
this.device@has no ATT Bearer, attempt to create one using the procedures described in "Connection Establishment" under GAP Interoperability Requirements.[[representedDevice]] - If this attempt fails, reject promise with a
NetworkErrorand abort these steps. -
Queue a task to perform the following sub-steps:
- If
this.device@is[[representedDevice]]null, reject promise with aNetworkErrorand abort these steps. - Set
this.connectedtotrue. - Resolve promise with
this.
- If
The disconnect() method, when invoked,
MUST perform the following steps:
- If
this.isconnectedfalse, abort these steps. - Clear
this@.[[activeAlgorithms]] - Set
this.toconnectedfalse. -
Fire an event named
gattserverdisconnectedwith itsbubblesattribute initialized totrueatthis..deviceThis event is not fired at the
BluetoothRemoteGATTServer. - Let device be
this.device@.[[representedDevice]] - In parallel:
if, for all
BluetoothDevicesdeviceObjin the whole UA withdeviceObj@the same device as device,[[representedDevice]]deviceObj.gatt.isconnectedfalse, the UA SHOULD destroy device’s ATT Bearer.
Algorithms need to fail if
their BluetoothRemoteGATTServer was disconnected while they were running,
even if the UA stays connected the whole time
and the BluetoothRemoteGATTServer is subsequently re-connected before they finish.
We wrap the returned Promise to accomplish this.
To create a gattServer-connection-checking wrapper around a Promise promise, the UA MUST:
- If
gattServer.connectedistrue, add promise togattServer@.[[activeAlgorithms]] -
Return the result of transforming promise with fulfillment and rejection handlers that perform the following steps:
- fulfillment handler
-
- If promise is in
gattServer@, remove it and return the first argument.[[activeAlgorithms]] - Otherwise, throw a
NetworkError. Because gattServer was disconnected during the execution of the main algorithm.
- If promise is in
- rejection handler
-
- If promise is in
gattServer@, remove it and throw the first argument.[[activeAlgorithms]] - Otherwise, throw a
NetworkError. Because gattServer was disconnected during the execution of the main algorithm.
- If promise is in
The getPrimaryService(service) method, when invoked,
MUST perform the following steps:
- If
this.device@is not[[allowedServices]]"all"and service is not inthis.device@, return a promise rejected with a[[allowedServices]]SecurityErrorand abort these steps. - Return GetGATTChildren(attribute=
this.device,
single=true,
uuidCanonicalizer=BluetoothUUID.getService,
uuid=service,
allowedUuids=this.device@,[[allowedServices]]
child type="GATT Primary Service")
The getPrimaryServices(service) method, when invoked,
MUST perform the following steps:
- If
this.device@is not[[allowedServices]]"all", and service is present and not inthis.device@, return a promise rejected with a[[allowedServices]]SecurityErrorand abort these steps. - Return GetGATTChildren(attribute=
this.device@,[[representedDevice]]
single=false,
uuidCanonicalizer=BluetoothUUID.getService,
uuid=service,
allowedUuids=this.device@,[[allowedServices]]
child type="GATT Primary Service")
5.3. BluetoothRemoteGATTService
BluetoothRemoteGATTService represents a GATT Service,
a collection of characteristics and relationships to other services
that encapsulate the behavior of part of a device.
interface BluetoothRemoteGATTService { readonly attribute BluetoothDevice device; readonly attribute UUID uuid; readonly attribute boolean isPrimary; Promise<BluetoothRemoteGATTCharacteristic> getCharacteristic(BluetoothCharacteristicUUID characteristic); Promise<sequence<BluetoothRemoteGATTCharacteristic>> getCharacteristics(optional BluetoothCharacteristicUUID characteristic); Promise<BluetoothRemoteGATTService> getIncludedService(BluetoothServiceUUID service); Promise<sequence<BluetoothRemoteGATTService>> getIncludedServices(optional BluetoothServiceUUID service); }; BluetoothRemoteGATTService implements EventTarget; BluetoothRemoteGATTService implements CharacteristicEventHandlers; BluetoothRemoteGATTService implements ServiceEventHandlers;
BluetoothRemoteGATTService attributes device is the BluetoothDevice representing
the remote peripheral that the GATT service belongs to.
uuid is the UUID of the service,
e.g. '0000180d-0000-1000-8000-00805f9b34fb' for the Heart Rate service.
isPrimary indicates whether the type of this service is primary or secondary.
To create a BluetoothRemoteGATTService representing a Service service,
the UA must return a new promise promise and run the following steps in parallel.
- Let result be a new instance of
BluetoothRemoteGATTService. - Get the
BluetoothDevicerepresenting the device in which service appears, and let device be the result. - If the previous step threw an error, reject promise with that error and abort these steps.
- Initialize
result.devicefrom device. - Initialize
result.uuidfrom the UUID of service. - If service is a Primary Service,
initialize
result.isPrimaryto true. Otherwise initializeresult.isPrimaryto false. - Resolve promise with result.
The getCharacteristic(characteristic) method
retrieves a Characteristic inside this Service.
When invoked, it MUST return
GetGATTChildren(attribute=this,
single=true,
uuidCanonicalizer=BluetoothUUID.getCharacteristic,
uuid=characteristic,
allowedUuids=undefined,
child type="GATT Characteristic")
The getCharacteristics(characteristic) method
retrieves a list of Characteristics inside this Service.
When invoked, it MUST return
GetGATTChildren(attribute=this,
single=false,
uuidCanonicalizer=BluetoothUUID.getCharacteristic,
uuid=characteristic,
allowedUuids=undefined,
child type="GATT Characteristic")
The getIncludedService(service) method
retrieves an Included Service inside this Service.
When invoked, it MUST return
GetGATTChildren(attribute=this,
single=true,
uuidCanonicalizer=BluetoothUUID.getService,
uuid=service,
allowedUuids=undefined,
child type="GATT Included Service")
The getIncludedServices(service) method
retrieves a list of Included Services inside this Service.
When invoked, it MUST return
GetGATTChildren(attribute=this,
single=false,
uuidCanonicalizer=BluetoothUUID.getService,
uuid=service,
allowedUuids=undefined,
child type="GATT Included Service")
5.4. BluetoothRemoteGATTCharacteristic
BluetoothRemoteGATTCharacteristic represents a GATT Characteristic, which is a basic data element that provides further information about a peripheral’s service.
interface BluetoothRemoteGATTCharacteristic { readonly attribute BluetoothRemoteGATTService service; readonly attribute UUID uuid; readonly attribute BluetoothCharacteristicProperties properties; readonly attribute DataView? value; Promise<BluetoothRemoteGATTDescriptor> getDescriptor(BluetoothDescriptorUUID descriptor); Promise<sequence<BluetoothRemoteGATTDescriptor>> getDescriptors(optional BluetoothDescriptorUUID descriptor); Promise<DataView> readValue(); Promise<void> writeValue(BufferSource value); Promise<BluetoothRemoteGATTCharacteristic> startNotifications(); Promise<BluetoothRemoteGATTCharacteristic> stopNotifications(); }; BluetoothRemoteGATTCharacteristic implements EventTarget; BluetoothRemoteGATTCharacteristic implements CharacteristicEventHandlers;
BluetoothRemoteGATTCharacteristic attributesservice is the GATT service this characteristic belongs to.
uuid is the UUID of the characteristic,
e.g. '00002a37-0000-1000-8000-00805f9b34fb' for the Heart Rate Measurement characteristic.
properties holds the properties of this characteristic.
value is the currently cached characteristic value. This value gets updated when the value of the characteristic is read or updated via a notification or indication.
To create a BluetoothRemoteGATTCharacteristic representing a Characteristic characteristic,
the UA must return a new promise promise and run the following steps in parallel.
- Let result be a new instance of
BluetoothRemoteGATTCharacteristic. - Initialize
result.servicefrom theBluetoothRemoteGATTServiceinstance representing the Service in which characteristic appears. - Initialize
result.uuidfrom the UUID of characteristic. - Create a
BluetoothCharacteristicPropertiesinstance from the Characteristic characteristic, and let propertiesPromise be the result. - Wait for propertiesPromise to settle.
- If propertiesPromise was rejected, resolve promise with propertiesPromise and abort these steps.
- Initialize
result.propertiesfrom the value propertiesPromise was fulfilled with. - Initialize
result.valuetonull. The UA MAY initializeresult.valueto a newDataViewwrapping a newArrayBuffercontaining the most recently read value from characteristic if this value is available. - Resolve promise with result.
The getDescriptor(descriptor) method
retrieves a Descriptor inside this Characteristic.
When invoked, it MUST return
GetGATTChildren(attribute=this,
single=true,
uuidCanonicalizer=BluetoothUUID.getDescriptor,
uuid=descriptor,
allowedUuids=undefined,
child type="GATT Descriptor")
The getDescriptors(descriptor) method
retrieves a list of Descriptors inside this Characteristic.
When invoked, it MUST return
GetGATTChildren(attribute=this,
single=false,
uuidCanonicalizer=BluetoothUUID.getDescriptor,
uuid=descriptor,
allowedUuids=undefined,
child type="GATT Descriptor")
The readValue() method, when invoked,
MUST run the following steps:
- If
this.uuidis blacklisted for reads, return a promise rejected with aSecurityErrorand abort these steps. - If
this.service.device.gatt.isconnectedfalse, return a promise rejected with aNetworkErrorand abort these steps. - Let characteristic be the Characteristic that
thisrepresents. -
Return a
this.service.device.gatt-connection-checking wrapper around a new promise promise and run the following steps in parallel:- If the
Readbit is not set in characteristic’s properties, reject promise with aNotSupportedErrorand abort these steps. - Use any combination of the sub-procedures in the Characteristic Value Read procedure to retrieve the value of characteristic. Handle errors as described in §5.7 Error handling.
- If the previous step returned an error, reject promise with that error and abort these steps.
-
Queue a task to perform the following steps:
- If promise is not in
this.service.device.gatt@, reject promise with a[[activeAlgorithms]]NetworkErrorand abort these steps. - Let buffer be an
ArrayBufferholding the retrieved value, and assignnew DataView(buffer)tothis.value. - Fire an event named
characteristicvaluechangedwith itsbubblesattribute initialized totrueatthis. - Resolve promise with
this.value.
- If promise is not in
- If the
The writeValue(value) method, when invoked,
MUST run the following steps:
- If
this.uuidis blacklisted for writes, return a promise rejected with aSecurityErrorand abort these steps. - Let characteristic be the Characteristic that
thisrepresents. - Let bytes be a copy of the bytes held by
value. - If bytes is more than 512 bytes long
(the maximum length of an attribute value, per Long Attribute Values)
return a promise rejected with an
InvalidModificationErrorand abort these steps. - If
this.service.device.gatt.isconnectedfalse, return a promise rejected with aNetworkErrorand abort these steps. -
Return a
this.service.device.gatt-connection-checking wrapper around a new promise promise and run the following steps in parallel.- If none of the
Write,Write Without ResponseorAuthenticated Signed Writesbits are set in characteristic’s properties, reject promise with aNotSupportedErrorand abort these steps. - Use any combination of the sub-procedures in the Characteristic Value Write procedure to write bytes to characteristic. Handle errors as described in §5.7 Error handling.
- If the previous step returned an error, reject promise with that error and abort these steps.
-
Queue a task to perform the following steps:
- If promise is not in
this.service.device.gatt@, reject promise with a[[activeAlgorithms]]NetworkErrorand abort these steps. - Set
this.valueto a newDataViewwrapping a newArrayBuffercontaining bytes. - Resolve promise with
undefined.
- If promise is not in
- If none of the
The UA MUST maintain a map from each known GATT Characteristic to
a set of Bluetooth objects known as
the characteristic’s active notification context set. The set for a given characteristic holds
the navigator.bluetooth objects for
each Realm that has registered for notifications.
The startNotifications() method, when invoked,
MUST return a new promise promise and run the following steps in parallel.
See §5.6.4 Responding to Notifications and Indications for details of receiving notifications.
- If
this.uuidis blacklisted for reads, reject promise with aSecurityErrorand abort these steps. - Let characteristic be
the GATT Characteristic that
thisrepresents. - If neither of the
NotifyorIndicatebits are set in characteristic’s properties, reject promise with aNotSupportedErrorand abort these steps. - If characteristic’s active notification context set contains
navigator.bluetooth, resolve promise withthisand abort these steps. - If
this.service.device.gatt.isconnectedfalse, reject promise with aNetworkErrorand abort these steps. - Use any of the Characteristic Descriptors procedures
to ensure that one of the
NotificationorIndicationbits in characteristic’s Client Characteristic Configuration descriptor is set, matching the constraints in characteristic’s properties. The UA SHOULD avoid setting both bits, and MUST deduplicate value-change events if both bits are set. Handle errors as described in §5.7 Error handling. - If the previous step returned an error, reject promise with that error and abort these steps.
- Add
navigator.bluetoothto characteristic’s active notification context set. - Resolve promise with
this.
After notifications are enabled,
the resulting value-change events won’t be delivered
until after the current microtask checkpoint.
This allows a developer to set up handlers
in the .then handler of the result promise.
The stopNotifications() method, when invoked,
MUST return a new promise promise and run the following steps in parallel:
- Let characteristic be
the GATT Characteristic that
thisrepresents. - If characteristic’s active notification context set contains
navigator.bluetooth, remove it. - If characteristic’s active notification context set became empty,
the UA SHOULD use any of the Characteristic Descriptors procedures
to clear the
NotificationandIndicationbits in characteristic’s Client Characteristic Configuration descriptor. - Queue a task to resolve promise with
this.
Queuing a task to resolve the promise ensures that no value change events due to notifications arrive after the promise resolves.
5.4.1. BluetoothCharacteristicProperties
Each BluetoothRemoteGATTCharacteristic exposes its characteristic properties through a BluetoothCharacteristicProperties object.
These properties express what operations are valid on the characteristic.
interface BluetoothCharacteristicProperties { readonly attribute boolean broadcast; readonly attribute boolean read; readonly attribute boolean writeWithoutResponse; readonly attribute boolean write; readonly attribute boolean notify; readonly attribute boolean indicate; readonly attribute boolean authenticatedSignedWrites; readonly attribute boolean reliableWrite; readonly attribute boolean writableAuxiliaries; };
To create a BluetoothCharacteristicProperties instance
from the Characteristic characteristic,
the UA MUST return a new promise promise and run the following steps in parallel:
- Let propertiesObj be a new instance of
BluetoothCharacteristicProperties. - Let properties be the characteristic properties of characteristic.
-
Initialize the attributes of propertiesObj from
the corresponding bits in properties:
Attribute Bit broadcastBroadcast readRead writeWithoutResponseWrite Without Response writeWrite notifyNotify indicateIndicate authenticatedSignedWritesAuthenticated Signed Writes -
If the Extended Properties bit of the characteristic properties is not set,
initialize
propertiesObj.reliableWriteandpropertiesObj.writableAuxiliariestofalse. Otherwise, run the following steps:-
Discover the Characteristic Extended Properties descriptor for characteristic and read its value into extendedProperties.
Handle errors as described in §5.7 Error handling.
Characteristic Extended Properties isn’t clear whether the extended properties are immutable for a given Characteristic. If they are, the UA should be allowed to cache them.
- If the previous step returned an error, reject promise with that error and abort these steps.
- Initialize
propertiesObj.reliableWritefrom the Reliable Write bit of extendedProperties. - Initialize
propertiesObj.writableAuxiliariesfrom the Writable Auxiliaries bit of extendedProperties.
-
Discover the Characteristic Extended Properties descriptor for characteristic and read its value into extendedProperties.
Handle errors as described in §5.7 Error handling.
- Resolve promise with propertiesObj.
5.5. BluetoothRemoteGATTDescriptor
BluetoothRemoteGATTDescriptor represents a GATT Descriptor, which provides further information about a Characteristic’s value.
interface BluetoothRemoteGATTDescriptor { readonly attribute BluetoothRemoteGATTCharacteristic characteristic; readonly attribute UUID uuid; readonly attribute DataView? value; Promise<DataView> readValue(); Promise<void> writeValue(BufferSource value); };
BluetoothRemoteGATTDescriptor attributescharacteristic is the GATT characteristic this descriptor belongs to.
uuid is the UUID of the characteristic descriptor,
e.g. '00002902-0000-1000-8000-00805f9b34fb' for the Client Characteristic Configuration descriptor.
value is the currently cached descriptor value. This value gets updated when the value of the descriptor is read.
To create a BluetoothRemoteGATTDescriptor representing a Descriptor descriptor,
the UA must return a new promise promise and run the following steps in parallel.
- Let result be a new instance of
BluetoothRemoteGATTDescriptor. - Initialize
result.characteristicfrom theBluetoothRemoteGATTCharacteristicinstance representing the Characteristic in which descriptor appears. - Initialize
result.uuidfrom the UUID of descriptor. - Initialize
result.valuetonull. The UA MAY initializeresult.valueto a newDataViewwrapping a newArrayBuffercontaining the most recently read value from descriptor if this value is available. - Resolve promise with result.
The readValue() method, when invoked,
MUST run the following steps:
- If
this.uuidis blacklisted for reads, return a promise rejected with aSecurityErrorand abort these steps. - If
this.characteristic.service.device.gatt.isconnectedfalse, return a promise rejected with aNetworkErrorand abort these steps. - Let descriptor be the Descriptor that
thisrepresents. -
Return a
this.characteristic.service.device.gatt-connection-checking wrapper around a new promise promise and run the following steps in parallel:- Use either the Read Characteristic Descriptors or the Read Long Characteristic Descriptors sub-procedure to retrieve the value of descriptor. Handle errors as described in §5.7 Error handling.
- If the previous step returned an error, reject promise with that error and abort these steps.
-
Queue a task to perform the following steps:
- If promise is not in
this.characteristic.service.device.gatt@, reject promise with a[[activeAlgorithms]]NetworkErrorand abort these steps. - Let buffer be an
ArrayBufferholding the retrieved value, and assignnew DataView(buffer)tothis.value. - Resolve promise with
this.value.
- If promise is not in
The writeValue(value) method, when invoked,
MUST run the following steps:
- If
this.uuidis blacklisted for writes, return a promise rejected with aSecurityErrorand abort these steps. - Let descriptor be the Descriptor that
thisrepresents. - Let bytes be a copy of the bytes held by
value. - If bytes is more than 512 bytes long
(the maximum length of an attribute value, per Long Attribute Values)
return a promise rejected with an
InvalidModificationErrorand abort these steps. - If
this.characteristic.service.device.gatt.isconnectedfalse, return a promise rejected with aNetworkErrorand abort these steps. -
Return a
this.characteristic.service.device.gatt-connection-checking wrapper around a new promise promise and run the following steps in parallel.- Use either the Write Characteristic Descriptors or the Write Long Characteristic Descriptors sub-procedure to write bytes to descriptor. Handle errors as described in §5.7 Error handling.
- If the previous step returned an error, reject promise with that error and abort these steps.
-
Queue a task to perform the following steps:
- If promise is not in
this.characteristic.service.device.gatt@, reject promise with a[[activeAlgorithms]]NetworkErrorand abort these steps. - Set
this.valueto a newDataViewwrapping a newArrayBuffercontaining bytes. - Resolve promise with
undefined.
- If promise is not in
5.6. Events
5.6.1. Bluetooth Tree
navigator.bluetooth and
objects implementing the BluetoothDevice, BluetoothRemoteGATTService, BluetoothRemoteGATTCharacteristic, or BluetoothRemoteGATTDescriptor interface participate in a tree,
simply named the Bluetooth tree.
- The children of
navigator.bluetoothare theBluetoothDeviceobjects representing devices in theallowedDeviceslist in"bluetooth"'s extra permission data fornavigator.bluetooth's relevant settings object, in an unspecified order. - The children of a
BluetoothDeviceare theBluetoothRemoteGATTServiceobjects representing Primary and Secondary Services on its GATT Server whose UUIDs are on the origin and device’sallowedServiceslist. The order of the primary services MUST be consistent with the order returned by the Discover Primary Service by Service UUID procedure, but secondary services and primary services with different UUIDs may be in any order. - The children of a
BluetoothRemoteGATTServiceare theBluetoothRemoteGATTCharacteristicobjects representing its Characteristics. The order of the characteristics MUST be consistent with the order returned by the Discover Characteristics by UUID procedure, but characteristics with different UUIDs may be in any order. - The children of a
BluetoothRemoteGATTCharacteristicare theBluetoothRemoteGATTDescriptorobjects representing its Descriptors in the order returned by the Discover All Characteristic Descriptors procedure.
5.6.2. Event types
advertisementreceived- Fired on a
BluetoothDevicewhen an advertising event is received from that device. characteristicvaluechanged- Fired on a
BluetoothRemoteGATTCharacteristicwhen its value changes, either as a result of a read request, or a value change notification/indication. gattserverdisconnected- Fired on a
BluetoothDevicewhen an active GATT connection is lost. serviceadded- Fired on a new
BluetoothRemoteGATTServicewhen it has been discovered on a remote device, just after it is added to the Bluetooth tree. servicechanged- Fired on a
BluetoothRemoteGATTServicewhen its state changes. This involves any characteristics and/or descriptors that get added or removed from the service, as well as Service Changed indications from the remote device. serviceremoved- Fired on a
BluetoothRemoteGATTServicewhen it has been removed from its device, just before it is removed from the Bluetooth tree.
5.6.3. Responding to Disconnection
When a Bluetooth device device’s ATT Bearer is lost
(e.g. because the remote device moved out of range
or the user used a platform feature to disconnect it),
for each BluetoothDevice deviceObj the UA MUST queue a task on deviceObj’s relevant settings object’s responsible event loop to perform the following steps:
- If
deviceObj@is not the same device as device, abort these steps.[[representedDevice]] - If
!deviceObj.gatt., abort these steps.connected - Set
deviceObj.gatt.toconnectedfalse. - Clear
deviceObj.gatt@.[[activeAlgorithms]] -
Fire an event named
gattserverdisconnectedwith itsbubblesattribute initialized totrueatdeviceObj.This event is not fired at the
BluetoothRemoteGATTServer.
5.6.4. Responding to Notifications and Indications
When the UA receives a Bluetooth Characteristic Value Notification or Indication, it must perform the following steps:
-
For each bluetoothGlobal in
the Characteristic’s active notification context set, queue a task on the event loop of the script settings object of bluetoothGlobal to do the following sub-steps:
- Let characteristicObject be the
BluetoothRemoteGATTCharacteristicin the Bluetooth tree rooted at bluetoothGlobal that represents the Characteristic. - If
characteristicObject.service.device.gatt.isconnectedfalse, abort these sub-steps. - Set
characteristicObject.valueto a newDataViewwrapping a newArrayBufferholding the new value of the Characteristic. - Fire an event named
characteristicvaluechangedwith itsbubblesattribute initialized totrueat characteristicObject.
- Let characteristicObject be the
5.6.5. Responding to Service Changes
The Bluetooth Attribute Caching system allows clients to track changes to Services, Characteristics, and Descriptors. Before discovering any of these attributes for the purpose of exposing them to a web page the UA MUST subscribe to Indications from the Service Changed characteristic, if it exists. When the UA receives an Indication on the Service Changed characteristic, it MUST perform the following steps.
- Let removedAttributes be the list of attributes in the range indicated by the Service Changed characteristic that the UA had discovered before the Indication.
- Use the Primary Service Discovery, Relationship Discovery, Characteristic Discovery, and Characteristic Descriptor Discovery procedures to re-discover attributes in the range indicated by the Service Changed characteristic. The UA MAY skip discovering all or part of the indicated range if it can prove that the results of that discovery could not affect the events fired below.
- Let addedAttributes be the list of attributes discovered in the previous step.
- If an attribute with the same definition, ignoring Characteristic and Descriptor values, appears in both removedAttributes and addedAttributes, remove it from both.
- Let changedServices be a set of Services, initially empty.
- If the same Service appears in both removedAttributes and addedAttributes, remove it from both, and add it to changedServices.
- For each Characteristic and Descriptor in removedAttributes and addedAttributes, remove it from its original list, and add its parent Service to changedServices. After this point, removedAttributes and addedAttributes contain only Services.
- If a Service in addedAttributes would not have been returned from any previous call to
getPrimaryService,getPrimaryServices,getIncludedService, orgetIncludedServicesif it had existed at the time of the call, the UA MAY remove the Service from addedAttributes. - Let changedDevices be the set of Bluetooth devices that contain any Service in removedAttributes, addedAttributes, and changedServices.
-
For each
BluetoothDevicedeviceObj that is connected to a device in changedDevices, queue a task on its global object’s responsible event loop to do the following steps:-
For each Service service in removedAttributes:
- If no remaining Service in
deviceObj@has the same UUID as service, remove the UUID from[[representedDevice]]deviceObj@.[[unfilteredUuids]] - If
deviceObj@is[[allowedServices]]"all"or contains the Service’s UUID, fire an event namedserviceremovedwith itsbubblesattribute initialized totrueat theBluetoothRemoteGATTServicerepresenting the Service. - Remove this
BluetoothRemoteGATTServicefrom the Bluetooth tree.
- If no remaining Service in
- For each Service in addedAttributes,
add the Service’s UUID to
deviceObj@. If[[unfilteredUuids]]deviceObj@is[[allowedServices]]"all"or contains the Service’s UUID, add theBluetoothRemoteGATTServicerepresenting this Service to the Bluetooth tree and then fire an event namedserviceaddedwith itsbubblesattribute initialized totrueat theBluetoothRemoteGATTService. - For each Service in changedServices,
if
deviceObj@is[[allowedServices]]"all"or contains the Service’s UUID, fire an event namedservicechangedwith itsbubblesattribute initialized totrueat theBluetoothRemoteGATTServicerepresenting the Service.
-
For each Service service in removedAttributes:
5.6.6. IDL event handlers
[NoInterfaceObject] interface CharacteristicEventHandlers { attribute EventHandler oncharacteristicvaluechanged; };
oncharacteristicvaluechanged is an Event handler IDL attribute for the characteristicvaluechanged event type.
[NoInterfaceObject] interface BluetoothDeviceEventHandlers { attribute EventHandler ongattserverdisconnected; };
ongattserverdisconnected is an Event handler IDL attribute for the gattserverdisconnected event type.
[NoInterfaceObject] interface ServiceEventHandlers { attribute EventHandler onserviceadded; attribute EventHandler onservicechanged; attribute EventHandler onserviceremoved; };
onserviceadded is an Event handler IDL attribute for the serviceadded event type.
onservicechanged is an Event handler IDL attribute for the servicechanged event type.
onserviceremoved is an Event handler IDL attribute for the serviceremoved event type.
5.7. Error handling
This section primarily defines the mapping from system errors to Javascript error names and allows UAs to retry certain operations. The retry logic and possible error distinctions are highly constrained by the operating system, so places these requirements don’t reflect reality are likely spec bugs instead of browser bugs.
When the UA is using a GATT procedure to execute a step in an algorithm or to handle a query to the Bluetooth cache (both referred to as a "step", here),
and the GATT procedure returns an Error Response,
the UA MUST perform the following steps:
- If the procedure times out or
the ATT Bearer (described in Profile Fundamentals) is
absent or terminated for any reason,
return a
NetworkErrorfrom the step and abort these steps. -
Take the following actions depending on the
Error Code:Invalid HandleInvalid PDUInvalid OffsetAttribute Not FoundUnsupported Group Type- These error codes indicate that something unexpected happened at the protocol layer,
likely either due to a UA or device bug.
Return a
NotSupportedErrorfrom the step. Invalid Attribute Value Length- Return an
InvalidModificationErrorfrom the step. Attribute Not Long-
If this error code is received without having used a "Long" sub-procedure, this may indicate a device bug. Return a
NotSupportedErrorfrom the step.Otherwise, retry the step without using a "Long" sub-procedure. If this is impossible due to the length of the value being written, return an
InvalidModificationErrorfrom the step. Insufficient AuthenticationInsufficient EncryptionInsufficient Encryption Key Size- The UA SHOULD attempt to increase the security level of the connection.
If this attempt fails or the UA doesn’t support any higher security,
Return a
SecurityErrorfrom the step. Otherwise, retry the step at the new higher security level. Insufficient Authorization- Return a
SecurityErrorfrom the step. Application Error- If the GATT procedure was a Write,
return an
InvalidModificationErrorfrom the step. Otherwise, return aNotSupportedErrorfrom the step. Read Not PermittedWrite Not PermittedRequest Not SupportedPrepare Queue FullInsufficient ResourcesUnlikely Error- Anything else
- Return a
NotSupportedErrorfrom the step.
6. UUIDs
typedef DOMString UUID;
A UUID string represents a 128-bit [RFC4122] UUID.
A valid UUID is a string that matches
the [ECMAScript] regexp /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.
That is, a valid UUID is lower-case and
does not use the 16- or 32-bit abbreviations defined by the Bluetooth standard.
All UUIDs returned from functions and attributes in this specification
MUST be valid UUIDs.
If a function in this specification takes a parameter whose type is UUID or a dictionary including a UUID attribute,
and the argument passed in any UUID slot is not a valid UUID,
the function MUST return a promise rejected with a TypeError and abort its other steps.
This standard provides the BluetoothUUID.canonicalUUID(alias) function
to map a 16- or 32-bit Bluetooth UUID alias to its 128-bit form.
Bluetooth devices are required to convert 16- and 32-bit UUIDs to 128-bit UUIDs before comparing them (as described in Attribute Type), but not all devices do so. To interoperate with these devices, if the UA has received a UUID from the device in one form (16-, 32-, or 128-bit), it should send other aliases of that UUID back to the device in the same form.
6.1. Standardized UUIDs
The Bluetooth SIG maintains a registry at [BLUETOOTH-ASSIGNED] of UUIDs that identify services, characteristics, descriptors, and other entities. This section provides a way for script to look up those UUIDs by name so they don’t need to be replicated in each application.
interface BluetoothUUID { static UUID getService((DOMString or unsigned long) name); static UUID getCharacteristic((DOMString or unsigned long) name); static UUID getDescriptor((DOMString or unsigned long) name); static UUID canonicalUUID([EnforceRange] unsigned long alias); }; typedef (DOMString or unsigned long) BluetoothServiceUUID; typedef (DOMString or unsigned long) BluetoothCharacteristicUUID; typedef (DOMString or unsigned long) BluetoothDescriptorUUID;
The static BluetoothUUID.canonicalUUID(alias) method, when invoked,
MUST return the 128-bit UUID represented by the 16- or 32-bit UUID alias alias.
This algorithm consists of
replacing the top 32 bits of "00000000-0000-1000-8000-00805f9b34fb"
with the bits of the alias.
For example, canonicalUUID(0xDEADBEEF) returns "deadbeef-0000-1000-8000-00805f9b34fb".
BluetoothServiceUUID represents
16- and 32-bit UUID aliases, valid UUIDs,
and names defined in [BLUETOOTH-ASSIGNED-SERVICES],
or, equivalently, the values for which BluetoothUUID.getService() does not throw an exception.
BluetoothCharacteristicUUID represents
16- and 32-bit UUID aliases, valid UUIDs,
and names defined in [BLUETOOTH-ASSIGNED-CHARACTERISTICS],
or, equivalently, the values for which BluetoothUUID.getCharacteristic() does not throw an exception.
BluetoothDescriptorUUID represents
16- and 32-bit UUID aliases, valid UUIDs,
and names defined in [BLUETOOTH-ASSIGNED-DESCRIPTORS],
or, equivalently, the values for which BluetoothUUID.getDescriptor() does not throw an exception.
To ResolveUUIDName(name, assigned numbers table, prefix), the UA MUST perform the following steps:
- If name is an
unsigned long, returnBluetoothUUID.canonicalUUID(name) and abort these steps. - If name is a valid UUID, return name and abort these steps.
- If the string
prefix + "." + nameappears in assigned numbers table, let alias be its assigned number, and returnBluetoothUUID.canonicalUUID(alias). - Otherwise, throw a
SyntaxError.
The static BluetoothUUID.getService(name) method, when invoked,
MUST return ResolveUUIDName(name, [BLUETOOTH-ASSIGNED-SERVICES], "org.bluetooth.service").
The static BluetoothUUID.getCharacteristic(name) method, when invoked,
MUST return ResolveUUIDName(name, [BLUETOOTH-ASSIGNED-CHARACTERISTICS], "org.bluetooth.characteristic").
The static BluetoothUUID.getDescriptor(name) method, when invoked,
MUST return ResolveUUIDName(name, [BLUETOOTH-ASSIGNED-DESCRIPTORS], "org.bluetooth.descriptor").
returns BluetoothUUID.getService("cycling_power")"00001818-0000-1000-8000-00805f9b34fb".
returns BluetoothUUID.getService("00001801-0000-1000-8000-00805f9b34fb")"00001801-0000-1000-8000-00805f9b34fb".
throws a BluetoothUUID.getService("unknown-service")SyntaxError.
returns BluetoothUUID.getCharacteristic("ieee_11073-20601_regulatory_certification_data_list")"00002a2a-0000-1000-8000-00805f9b34fb".
returns BluetoothUUID.getDescriptor("gatt.characteristic_presentation_format")"00002904-0000-1000-8000-00805f9b34fb".
7. The GATT Blacklist
This specification relies on a blacklist file in the https://github.com/WebBluetoothCG/registries repository to restrict the set of GATT attributes a website can access.
The result of parsing the blacklist at a URL url is a map from valid UUIDs to tokens, or an error, produced by the following algorithm:
- Fetch url, and let contents be its body, decoded as UTF-8.
- Let lines be contents split on
'\n'. - Let result be an empty map.
-
For each line in lines, do the following sub-steps:
- If line is empty or its first character is
'#', continue to the next line. - If line consists of just a valid UUID,
let uuid be that UUID and
let token be "
exclude". - If line consists of a valid UUID, a space (U+0020),
and one of the tokens "
exclude-reads" or "exclude-writes", let uuid be that UUID and let token be that token. - Otherwise, return an error and abort these steps.
- If uuid is already in result, return an error and abort these steps.
- Add a mapping in result from uuid to token.
- If line is empty or its first character is
- Return result.
The GATT blacklist is the result of parsing the blacklist at https://github.com/WebBluetoothCG/registries/blob/master/gatt_blacklist.txt. The UA should re-fetch the blacklist periodically, but it’s unspecified how often.
A UUID is blacklisted if either
the GATT blacklist’s value is an error,
or the UUID maps to "exclude" in the GATT blacklist.
A UUID is blacklisted for reads if either
the GATT blacklist’s value is an error,
or the UUID maps to either "exclude" or "exclude-reads"
in the GATT blacklist.
A UUID is blacklisted for writes if either
the GATT blacklist’s value is an error,
or the UUID maps to either "exclude" or "exclude-writes"
in the GATT blacklist.
8. Extensions to the Navigator Interface
partial interface Navigator { readonly attribute Bluetooth bluetooth; };
9. Terminology and Conventions
This specification uses a few conventions and several terms from other specifications. This section lists those and links to their primary definitions.
Inspired by the Streams specification, we use the notation x@[[y]] to refer to internal slots of an object, instead of saying "the [[y]] internal slot of x."
When an algorithm in this specification uses a name defined in this or another specification,
the name MUST resolve to its initial value,
ignoring any changes that have been made to the name in the current execution environment.
For example, when the requestDevice() algorithm says to call ,
this MUST apply the Array.prototype.map.call(filter.services, BluetoothUUID.getService)Array.prototype.map algorithm defined in [ECMAScript] with filter.services as its this parameter and
the algorithm defined in §6.1 Standardized UUIDs for BluetoothUUID.getService as its callbackfn parameter,
regardless of any modifications that have been made to window, Array, Array.prototype, Array.prototype.map, Function, Function.prototype, BluetoothUUID, BluetoothUUID.getService, or other objects.
This specification uses a read-only type
that is similar to WebIDL’s FrozenArray.
- A read only ArrayBuffer has
ArrayBuffer's values and interface, except that attempting to write to its contents or transfer it has the same effect as trying to write to aFrozenArray's contents. This applies toTypedArrays andDataViews wrapped around theArrayBuffertoo.
- [BLUETOOTH42]
-
-
Architecture & Terminology Overview
-
General Description
- Overview of Bluetooth Low Energy Operation (defines advertising events)
-
Communication Topology and Operation
-
Operational Procedures and Modes
-
BR/EDR Procedures
-
Inquiry (Discovering) Procedure
- Extended Inquiry Response
-
Inquiry (Discovering) Procedure
-
BR/EDR Procedures
-
Operational Procedures and Modes
-
General Description
-
Core System Package [BR/EDR Controller volume]
-
Host Controller Interface Functional Specification
-
HCI Commands and Events
-
Informational Parameters
- Read BD_ADDR Command
-
Status Parameters
- Read RSSI Command
-
Informational Parameters
-
HCI Commands and Events
-
Host Controller Interface Functional Specification
-
Core System Package [Host volume]
-
Service Discovery Protocol (SDP) Specification
-
Overview
-
Searching for Services
- UUID (defines UUID aliases and the algorithm to compute the 128-bit UUID represented by a UUID alias)
-
Searching for Services
-
Overview
-
Generic Access Profile
-
Profile Overview
-
Profile Roles
-
Roles when Operating over an LE Physical Transport
- Broadcaster Role
- Observer Role
- Peripheral Role
- Central Role
-
Roles when Operating over an LE Physical Transport
-
Profile Roles
-
User Interface Aspects
-
Representation of Bluetooth Parameters
- Bluetooth Device Name (the user-friendly name)
-
Representation of Bluetooth Parameters
-
Idle Mode Procedures — BR/EDR Physical Transport
- Device Discovery Procedure
- Operational Modes and Procedures — LE Physical Transport
-
Security Aspects — LE Physical Transport
- Privacy Feature
-
Random Device Address
- Static Address
-
Private address
- Resolvable Private Address Resolution Procedure
- Advertising Data and Scan Response Data Format (defines AD structure)
-
Bluetooth Device Requirements
-
Bluetooth Device Address (defines BD_ADDR)
-
Bluetooth Device Address Types
- Public Bluetooth Address
-
Bluetooth Device Address Types
-
Bluetooth Device Address (defines BD_ADDR)
-
Profile Overview
-
Attribute Protocol (ATT)
-
Protocol Requirements
-
Basic Concepts
- Attribute Type
- Attribute Handle
- Long Attribute Values
-
Attribute Protocol Pdus
-
Error Handling
- Error Response
-
Error Handling
-
Basic Concepts
-
Protocol Requirements
-
Generic Attribute Profile (GATT)
-
Profile Overview
- Configurations and Roles (defines GATT Client and GATT Server)
- Profile Fundamentals, defines the ATT Bearer
-
Attribute Protocol
- Attribute Caching
-
GATT Profile Hierarchy
- Service
- Included Services
- Characteristic
-
Service Interoperability Requirements
-
Characteristic Definition
-
Characteristic Declaration
- Characteristic Properties
-
Characteristic Descriptor Declarations
- Characteristic Extended Properties
- Client Characteristic Configuration
-
Characteristic Declaration
-
Characteristic Definition
-
GATT Feature Requirements — defines the GATT procedures.
- Primary Service Discovery
- Relationship Discovery
- Characteristic Discovery
-
Characteristic Descriptor Discovery
- Discover All Characteristic Descriptors
- Characteristic Value Read
- Characteristic Value Write
- Characteristic Value Notification
- Characteristic Value Indications
-
Characteristic Descriptors
- Read Characteristic Descriptors
- Read Long Characteristic Descriptors
- Write Characteristic Descriptors
- Write Long Characteristic Descriptors
- Procedure Timeouts
-
GAP Interoperability Requirements
-
BR/EDR GAP Interoperability Requirements
- Connection Establishment
-
LE GAP Interoperability Requirements
- Connection Establishment
-
BR/EDR GAP Interoperability Requirements
-
Defined Generic Attribute Profile Service
- Service Changed
-
Profile Overview
-
Security Manager Specification
-
Security Manager
-
Security in Bluetooth Low Energy
- Definition of Keys and Values, defines the Identity Resolving Key (IRK)
-
Security in Bluetooth Low Energy
-
Security Manager
-
Service Discovery Protocol (SDP) Specification
-
Core System Package [Low Energy Controller volume]
-
Link Layer Specification
-
General Description
-
Device Address
- Public Device Address
-
Random Device Address
- Static Device Address
-
Device Address
-
Air Interface Protocol
-
Non-Connected States
-
Scanning State
- Passive Scanning
-
Scanning State
-
Non-Connected States
-
General Description
-
Link Layer Specification
-
Architecture & Terminology Overview
- [BLUETOOTH-SUPPLEMENT6]
-
-
Data Types Specification
-
Data Types Definitions and Formats
- Service UUID Data Type
- Local Name Data Type
- Flags Data Type (defines the Discoverable Mode flags)
- Manufacturer Specific Data
- TX Power Level
- Service Data
- Appearance
-
Data Types Definitions and Formats
-
Data Types Specification