Title: Web Bluetooth
Repository: WebBluetoothCG/web-bluetooth
Status: CG-DRAFT
ED: https://webbluetoothcg.github.io/web-bluetooth/
Shortname: web-bluetooth
Level: 1
Translation: ja https://tkybpp.github.io/web-bluetooth-jp/
Editor: Reilly Grant 83788, Google LLC https://www.google.com, reillyg@google.com
Editor: Ovidio Ruiz-Henríquez 106543, Google LLC https://www.google.com, odejesush@google.com
Editor: See contributors on GitHub, , https://github.com/WebBluetoothCG/web-bluetooth/graphs/contributors
Abstract: This document describes an API to discover and communicate with devices
Abstract: over the Bluetooth 4 wireless standard using the Generic Attribute Profile (GATT).

Group: web-bluetooth-cg
!Participate: Join the W3C Community Group
!Participate: Fix the text through GitHub
!Participate: public-web-bluetooth@w3.org (archives)
!Participate: IRC: #web-bluetooth on W3C's IRC

Markup Shorthands: css no, markdown yes
{
  "BLUETOOTH42": {
    "href": "https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439",
    "title": "BLUETOOTH SPECIFICATION Version 4.2",
    "publisher": "Bluetooth SIG",
    "date": "2 December 2014"
  },
  "BLUETOOTH-ASSIGNED": {
    "href": "https://www.bluetooth.com/specifications/assigned-numbers/",
    "title": "Assigned Numbers",
    "status": "Living Standard",
    "publisher": "Bluetooth SIG"
  },
  "BLUETOOTH-SUPPLEMENT6": {
    "href": "https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=302735",
    "title": "Supplement to the Bluetooth Core Specification Version 6",
    "date": "14 July 2015",
    "publisher": "Bluetooth SIG"
  }
}
spec: assigned-numbers
    type: enum; urlPrefix: https://github.com/WebBluetoothCG/registries/
        urlPrefix: blob/master/gatt_assigned_characteristics.txt
            text: body_sensor_location; url: #:~:text=body_sensor_location
            text: gap.appearance; url: #:~:text=gap.appearance
            text: heart_rate_control_point; url: #:~:text=heart_rate_control_point
            text: heart_rate_measurement; url: #:~:text=heart_rate_measurement
            text: ieee_11073-20601_regulatory_certification_data_list; url: #:~:text=ieee_11073-20601_regulatory_certification_data_list
        urlPrefix: blob/master/gatt_assigned_descriptors.txt
            text: gatt.characteristic_presentation_format; url: #:~:text=gatt.characteristic_presentation_format
            text: gatt.client_characteristic_configuration; url: #:~:text=gatt.client_characteristic_configuration
        urlPrefix: blob/master/gatt_assigned_services.txt
            text: cycling_power; url: #:~:text=cycling_power
            text: heart_rate; url: #:~:text=heart_rate

spec: BLUETOOTH-ASSIGNED
    type: dfn
        text: Shortened Local Name; url: https://www.bluetooth.com/specifications/assigned-numbers/#:~:text=Generic%20Access%20Profile

spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/#
    type: dfn
        text: current realm; url: current-realm
        text: fulfilled; url: sec-promise-objects
        text: internal slot; url: sec-object-internal-methods-and-internal-slots
        text: realm; url: sec-code-realms
    type: method
        text: Array.prototype.map; url: sec-array.prototype.map
    type: interface
        text: Array; url: sec-array-objects
        text: ArrayBuffer; url: sec-arraybuffer-constructor
        text: DataView; url: sec-dataview-constructor
        text: Map; url: sec-map-constructor
        text: Promise; url:sec-promise-objects
        text: Set; url: sec-set-objects
        text: TypeError; url: sec-native-error-types-used-in-this-standard-typeerror
        text: TypedArray; url: sec-typedarray-constructors

spec: fingerprinting-guidance; urlPrefix: https://w3c.github.io/fingerprinting-guidance/#
    type: dfn
        text: fingerprinting surface; url: dfn-fingerprinting-surface

spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/browsing-the-web.html#
    type: dfn
        text: initializing the Document object; url: initialise-the-document-object

spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/document-sequences.html#
    type: dfn
        text: navigable; url: navigable

spec: WebIDL; urlPrefix: https://heycam.github.io/webidl/#
    type: dfn
        text: a copy of the bytes held; url: dfn-get-buffer-source-copy

spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/browsing-the-web.html/#
    type: dfn
        text: initializing a new Document object; url: dfn-initialise-the-document-object

spec: PAGE-VISIBILITY; urlPrefix: https://www.w3.org/TR/page-visibility-2/#
    type: dfn
        text: document visibility state; url: dom-visibilitystate

spec: WEBDRIVER; urlPrefix: https://w3c.github.io/webdriver/
    type: dfn
        text: local end; url: dfn-local-ends
        text: invalid element state; url: dfn-invalid-element-state

# Introduction # {#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 [[#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. ## Examples ## {#introduction-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 => {
      const 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) {
    const 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 {{BluetoothRemoteGATTCharacteristic/value}} field.
  function parseHeartRate(data) {
    const flags = data.getUint8(0);
    const rate16Bits = flags & 0x1;
    const result = {};
    let index = 1;
    if (rate16Bits) {
      result.heartRate = data.getUint16(index, /*littleEndian=*/true);
      index += 2;
    } else {
      result.heartRate = data.getUint8(index);
      index += 1;
    }
    const contactDetected = flags & 0x2;
    const contactSensorPresent = flags & 0x4;
    if (contactSensorPresent) {
      result.contactDetected = !!contactDetected;
    }
    const energyPresent = flags & 0x8;
    if (energyPresent) {
      result.energyExpended = data.getUint16(index, /*littleEndian=*/true);
      index += 2;
    }
    const rrIntervalPresent = flags & 0x10;
    if (rrIntervalPresent) {
      const 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 => {
      const resetEnergyExpended = new Uint8Array([1]);
      return controlPoint.writeValue(resetEnergyExpended);
    });
  }
# Security considerations # {#security-and-privacy} Issue(575): See section. # Privacy considerations # {#privacy} ## Device access is powerful ## {#device-access-is-powerful} When a website requests access to devices using {{Bluetooth/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. ## Trusted servers can serve malicious code ## {#server-takeovers} *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 , 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. ## Attacks on devices ## {#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 GATT blocklist 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. * The integration with [[#permissions-policy]] provides protection against unwanted access to Bluetooth capabilities, which requires the top-level document to explicitly allow a cross-origin iframe to use the API's methods. 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. ## Bluetooth device identifiers ## {#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. ### Identifiers for remote Bluetooth devices ### {#remote-device-identifiers} *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. ### The UA's Bluetooth address ### {#ua-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. ## Exposing Bluetooth availability ## {#availability-fingerprint} *This section is non-normative.* navigator.bluetooth.{{getAvailability()}} exposes whether a Bluetooth radio is available on the user's system, regardless of whether it is powered on or not. The availability is also affected if the user has configured the UA to block Web Bluetooth. Some users might consider this private, although it's hard to imagine the damage that would result from revealing it. This information also increases the UA's fingerprinting surface by a bit. This function returns a {{Promise}}, so UAs have the option of asking the user what value they want to return, but we expect the increased risk to be small enough that UAs will choose not to prompt. # Device Discovery # {#device-discovery} dictionary BluetoothDataFilterInit { BufferSource dataPrefix; BufferSource mask; }; dictionary BluetoothManufacturerDataFilterInit : BluetoothDataFilterInit { required [EnforceRange] unsigned short companyIdentifier; }; dictionary BluetoothServiceDataFilterInit : BluetoothDataFilterInit { required BluetoothServiceUUID service; }; dictionary BluetoothLEScanFilterInit { sequence<BluetoothServiceUUID> services; DOMString name; DOMString namePrefix; sequence<BluetoothManufacturerDataFilterInit> manufacturerData; sequence<BluetoothServiceDataFilterInit> serviceData; }; dictionary RequestDeviceOptions { sequence<BluetoothLEScanFilterInit> filters; sequence<BluetoothLEScanFilterInit> exclusionFilters; sequence<BluetoothServiceUUID> optionalServices = []; sequence<unsigned short> optionalManufacturerData = []; boolean acceptAllDevices = false; }; [Exposed=Window, SecureContext] interface Bluetooth : EventTarget { Promise<boolean> getAvailability(); attribute EventHandler onavailabilitychanged; [SameObject] readonly attribute BluetoothDevice? referringDevice; Promise<sequence<BluetoothDevice>> getDevices(); Promise<BluetoothDevice> requestDevice(optional RequestDeviceOptions options = {}); }; Bluetooth includes BluetoothDeviceEventHandlers; Bluetooth includes CharacteristicEventHandlers; Bluetooth includes ServiceEventHandlers; Methods defined in this specification typically complete asynchronously, queuing work on a Bluetooth [=task source=].
Note: {{Bluetooth/getAvailability()}} informs the page whether Bluetooth is available at all. An adapter that's disabled through software should count as available. Changes in availability, for example when the user physically attaches or detaches an adapter, are reported through the {{availabilitychanged}} event.

{{Bluetooth/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}}.

{{Bluetooth/getDevices()}} enables the page to retrieve Bluetooth devices that the user has granted access to.

{{Bluetooth/requestDevice(options)}} asks the user to grant this origin access to a device that matches any filter in options.filters but does not match any filter in options.exclusionFilters. 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, * have a name starting with namePrefix if that member is present, * advertise manufacturer specific data matching all of the values in manufacturerData if that member is present, and *
advertise service data matching all of the values in serviceData if that member is present.

Both Manufacturer Specific Data and Service Data map a key to an array of bytes. {{BluetoothDataFilterInit}} filters these arrays. An array matches if it has a |prefix| such that |prefix| & {{mask}} is equal to {{dataPrefix}} & {{mask}}.

Note that if a device changes its behavior significantly when it connects, for example by not advertising its identifying manufacturer data anymore and instead having the client discover some identifying GATT services, the website may need to include filters for both behaviors. In rare cases, a device may not advertise enough distinguishing information to let a site filter out uninteresting devices. In those cases, a site can set acceptAllDevices to `true` and omit all {{RequestDeviceOptions/filters}} and {{RequestDeviceOptions/exclusionFilters}}. This puts the burden of selecting the right device entirely on the site's users. If a site uses {{RequestDeviceOptions/acceptAllDevices}}, it will only be able to use services listed in {{RequestDeviceOptions/optionalServices}}. After the user selects a device to pair with this origin, the origin is allowed to access any service whose UUID was listed in the {{BluetoothLEScanFilterInit/services}} list in any element of options.filters or in options.optionalServices. The origin is also allowed to access any manufacturer data from manufacturer codes defined in options.optionalManufacturerData from the device's advertisement data. This implies that if developers filter just by name, they must use {{RequestDeviceOptions/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. If the website then calls
  navigator.bluetooth.getDevices();
the resulting {{Promise}} will resolve into an array containing device D1, and 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. If the website calls
  navigator.bluetooth.getDevices();
again, then the resulting {{Promise}} will resolve into an array containing the devices D1 and D2. The A, B, C, and D services will be accessible on device D1, while A, B, and E services will be accessible on device D2. 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 DevicesNotes
        [{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
The following table shows which devices the user can select between for several values of filters and exclusionFilters passed to navigator.bluetooth.requestDevice({filters: filters, exclusionFilters: exclusionFilters}).
filters exclusionFilters Devices
        [{namePrefix: "Device"}]  // D3, D4
      
        [{name: "Device Third"}]   // D3
      
D4
        [{namePrefix: "Device"}]  // D3, D4
      
        [{namePrefix: "Device F"}] // D4
      
D3
        [{services: \[C]},         // D1, D3
          {namePrefix: "Device"}] // D3, D4
      
        [{services: \[A]},          // D1
          {name: "Device Fourth"}] // D4
      
D3
Say the devices in the previous example also advertise manufacturer or service data as follows:
Device Manufacturer Data Service Data
D1 17: 01 02 03
D2 A: 01 02 03
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
[{ manufacturerData: [{ companyIdentifier: 17 }] }]
      
D1
[{ serviceData: [{ service: "A" }] }]
      
D2
[
  { manufacturerData: [{ companyIdentifier: 17 }] },
  { serviceData: [{ service: "A" }] },
]
      
D1, D2
[
  {
    manufacturerData: [{ companyIdentifier: 17 }],
    serviceData: [{ service: "A" }],
  },
]
      
<none>
[
  {
    manufacturerData: [
      {
        companyIdentifier: 17,
        dataPrefix: new Uint8Array([1, 2, 3])
      },
    ],
  },
]
      
D1
[
  {
    manufacturerData: [
      {
        companyIdentifier: 17,
        dataPrefix: new Uint8Array([1, 2, 3, 4])
      },
    ],
  },
]
      
<none>
[
  {
    manufacturerData: [
      {
        companyIdentifier: 17,
        dataPrefix: new Uint8Array([1])
      },
    ],
  },
]
      
D1
[
  {
    manufacturerData: [
      {
        companyIdentifier: 17,
        dataPrefix: new Uint8Array([0x91, 0xAA]),
        mask: new Uint8Array([0x0f, 0x57]),
      },
    ],
  },
]
      
D1
[
  {
    manufacturerData: [
      { companyIdentifier: 17 },
      { companyIdentifier: 18 },
    ]
  }
]
      
<none>
Filters that either accept or reject all possible devices cause {{TypeError}}s. To accept all devices, use {{acceptAllDevices}} instead.
Call Notes
        requestDevice({})
      
Invalid: An absent list of filters doesn't accept any devices.
        requestDevice({filters:[]})
      
Invalid: An empty list of filters doesn't accept any devices.
        requestDevice({filters:[{}]})
      
Invalid: An empty filter accepts all devices, and so isn't allowed either.
        requestDevice({
          acceptAllDevices:true
        })
      
Valid: Explicitly accept all devices with {{acceptAllDevices}}.
        requestDevice({
          filters: [...],
          acceptAllDevices:true
        })
      
Invalid: {{acceptAllDevices}} would override any {{filters}}.
        requestDevice({
          exclusionFilters: [...],
          acceptAllDevices:true
        })
      
Invalid: {{acceptAllDevices}} would override any {{exclusionFilters}}.
        requestDevice({
          exclusionFilters: [...]
        })
      
Invalid: {{exclusionFilters}} require {{filters}}.
        requestDevice({
          filters: [...],
          exclusionFilters: []
        })
      
Invalid: {{exclusionFilters}} must be non-empty to exclude devices.
        requestDevice({
          filters: [{namePrefix: ""}]
        })
      
Invalid: `namePrefix`, if present, must be non-empty to filter devices.
        requestDevice({
          filters: [{manufacturerData: []}]
        })
      
Invalid: {{BluetoothLEScanFilterInit/manufacturerData}}, if present, must be non-empty to filter devices.
        requestDevice({
          filters: [{serviceData: []}]
        })
      
Invalid: {{BluetoothLEScanFilterInit/serviceData}}, 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 {{BluetoothDevice}} instances. Ensures only one {{BluetoothDevice}} instance represents each Bluetooth device inside a single global object.
\[[attributeInstanceMap]] An empty map from Bluetooth cache entries to {{Promise}}s. The {{Promise}}s resolve to either {{BluetoothRemoteGATTService}}, {{BluetoothRemoteGATTCharacteristic}}, or {{BluetoothRemoteGATTDescriptor}} instances.
\[[referringDevice]] `null` Set to a {{BluetoothDevice}} while initializing the `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.
Note: 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 initializing the `Document` object, the UA MUST run the following steps: 1. Let referringDevice be the device that caused the navigation. 1. Get the BluetoothDevice representing referringDevice inside `navigator.bluetooth`, and let |referringDeviceObj| be the result. 1. 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.
1. Set navigator.bluetooth.{{[[referringDevice]]}} to |referringDeviceObj|.
A Bluetooth device device matches a filter filter if the following steps return `match`: 1. If filter.{{BluetoothLEScanFilterInit/name}} is present then, if device's Bluetooth Device Name isn't complete and equal to filter.name, return `mismatch`. 1. If filter.{{BluetoothLEScanFilterInit/namePrefix}} is present then if device's Bluetooth Device Name isn't present or doesn't start with filter.namePrefix, return `mismatch`. 1. For each uuid in filter.{{BluetoothLEScanFilterInit/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, return `mismatch`. 1. For each manufacturerData in |filter|["{{BluetoothLEScanFilterInit/manufacturerData}}"], if device hasn't advertised manufacturer specific data with a company identifier code equal to |manufacturerData|["{{BluetoothManufacturerDataFilterInit/companyIdentifier}}"] and with data that matches |manufacturerData| return `mismatch`. 1. For each serviceData in |filter|["{{BluetoothLEScanFilterInit/serviceData}}"], if device hasn't advertised service data with a UUID whose 128-bit form is |serviceData|["{{BluetoothServiceDataFilterInit/service}}"] and with data that matches |serviceData| return `mismatch`. 1. Return `match`.
An array of bytes |data| matches a {{BluetoothDataFilterInit}} |filter| if the following steps return `match`.
Note: This algorithm assumes that |filter| has already been canonicalized.
1. Let |expectedPrefix| be a copy of the bytes held by |filter|.{{BluetoothDataFilterInit/dataPrefix}}. 1. Let |mask| be a copy of the bytes held by |filter|.{{BluetoothDataFilterInit/mask}}. 1. If |data| has fewer bytes than |expectedPrefix|, return `mismatch`. 1. For each `1` bit in |mask|, if the corresponding bit in |data| is not equal to the corresponding bit in |expectedPrefix|, return `mismatch`. 1. Return `match`.
A {{BluetoothDataFilterInit}} |filter1| is a strict subset of a {{BluetoothDataFilterInit}} |filter2| if the following steps return `true`: 1. If the length of |filter1| is less than the length of |filter2|, return `false`. 1. Let |byteIndex| be `0`. 1. While |byteIndex| is less than the length of |filter2|, do the following sub-steps: 1. If |filter1|.{{BluetoothDataFilterInit/mask}}[|byteIndex|] & |filter2|.{{BluetoothDataFilterInit/mask}}[|byteIndex|] is not equal to |filter2|.{{BluetoothDataFilterInit/mask}}[|byteIndex|], return `false`. 1. If |filter1|.{{BluetoothDataFilterInit/dataPrefix}}[|byteIndex|] & |filter2|.{{BluetoothDataFilterInit/mask}}[|byteIndex|] it not equal to |filter2|.{{BluetoothDataFilterInit/dataPrefix}}[|byteIndex|] & |filter2|.{{BluetoothDataFilterInit/mask}}[|byteIndex|], return `false`. 1. Set |byteIndex| to |byteIndex| + 1. 1. Return `true`.
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.
To getDevices() method, when invoked and given a {{BluetoothPermissionStorage}} |storage|, MUST run the following steps: 1. Let |global| be [=this=]'s [=relevant global object=]. 1. If |global|'s [=associated Document=] is not [=allowed to use=] the [=policy-controlled feature=] named "[=policy-controlled feature/bluetooth=]", return [=a promise rejected with=] a {{SecurityError}}. 1. Let |promise| be [=a new promise=]. 1. Run the following steps [=in parallel=]: 1. Let |devices| be a new empty {{Array}}. 1. For each |allowedDevice| in |storage|.{{BluetoothPermissionStorage/allowedDevices}}, add the {{BluetoothDevice}} object representing |allowedDevice|@{{[[device]]}} to |devices|. 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with |devices|.
Note: The {{BluetoothDevice}}s in |devices| may not be in range of the Bluetooth radio. For a given |device| in |devices|, the {{BluetoothDevice/watchAdvertisements()}} method can be used to observe when |device| is in range and broadcasting advertisement packets. When an {{advertisementreceived}} event |event| is fired on a |device|, it may indicate that it is close enough for a connection to be established by calling |event|.device.gatt.{{BluetoothRemoteGATTServer/connect()}}.
1. Return |promise|.
The requestDevice(options) method, when invoked, MUST run the following steps: 1. If |options|.{{RequestDeviceOptions/exclusionFilters}} is present and |options|.{{RequestDeviceOptions/filters}} is not present, return [=a promise rejected with=] a {{TypeError}}. 1. If |options|.{{RequestDeviceOptions/filters}} is present and |options|.{{RequestDeviceOptions/acceptAllDevices}} is `true`, or if |options|.{{RequestDeviceOptions/filters}} is not present and |options|.{{RequestDeviceOptions/acceptAllDevices}} is `false`, return [=a promise rejected with=] a {{TypeError}}.
Note: This enforces that exactly one of {{RequestDeviceOptions/filters}} or {{RequestDeviceOptions/acceptAllDevices}}:true is present.
1. Let |promise| be [=a new promise=]. 1. Run the following steps [=in parallel=]: 1. Request Bluetooth devices, passing |options|.{{RequestDeviceOptions/filters}} if |options|.{{RequestDeviceOptions/acceptAllDevices}} is `false` or `null` otherwise, passing |options|.{{RequestDeviceOptions/exclusionFilters}} if it exists or `null` otherwise, passing |options|.{{RequestDeviceOptions/optionalServices}}, and passing |options|.{{RequestDeviceOptions/optionalManufacturerData}}, and let |devices| be the result. 1. [=Queue a global task=] on the [=Bluetooth task source=] given [=this=]'s [=relevant global object=] to run the following steps: 1. If the previous step threw an exception, [=reject=] |promise| with that exception and abort these steps. 1. If |devices| is an empty sequence, [=reject=] |promise| with a {{NotFoundError}} and abort these steps. 1. [=Resolve=] |promise| with |devices|[0]. 1. Return |promise|.
To request Bluetooth devices, given a {{BluetoothPermissionStorage}} |storage| and a sequence of {{BluetoothLEScanFilterInit}}s |filters|, which can be `null` to represent that all devices can match, a sequence of {{BluetoothLEScanFilterInit}}s |exclusionFilters|, which can be `null` if no exclusion filters have been set, a sequence of {{BluetoothServiceUUID}}s |optionalServices|, and a sequence of {{unsigned short}}s |optionalManufacturerData|, the UA MUST run the following steps:
Note: These steps can block, so uses of this algorithm must be in parallel.
Note: Calls to this algorithm will eventually be able to request multiple devices, but for now it only ever returns a single one.
1. Let |global| be the [=relevant global object=] for |storage|. 1. Let |document| be |global|'s [=associated Document=]. 1. If |document| is not [=allowed to use=] the [=policy-controlled feature=] named "[=policy-controlled feature/bluetooth=]", throw a {{SecurityError}} and abort these steps. 1.

Check that the algorithm is triggered while its [=relevant global object=] has a transient activation, otherwise throw a {{SecurityError}} and abort these steps.

1. In order to convert the arguments from service names and aliases to just UUIDs, do the following sub-steps: 1. If |filters| !== null && |filters|.length === 0, throw a {{TypeError}} and abort these steps. 1. If |exclusionFilters| !== null && |exclusionFilters|.length === 0, throw a {{TypeError}} and abort these steps. 1. Let uuidFilters be a new {{Array}}, uuidExclusionFilters be a new {{Array}}, and requiredServiceUUIDs be a new {{Set}}. 1. If |filters| is `null`, then set |requiredServiceUUIDs| to the set of all UUIDs. 1. If |filters| isn't `null`, then for each |filter| in |filters|, do the following steps: 1. Let |canonicalFilter| be the result of canonicalizing |filter|. 1. Append |canonicalFilter| to |uuidFilters|. 1. Add the contents of |canonicalFilter|.services to |requiredServiceUUIDs|. 1. If |exclusionFilters| isn't `null`, then for each |exclusionFilter| in |exclusionFilters|, do the following steps: 1. Let |canonicalExclusionFilter| be the result of canonicalizing |exclusionFilter|. 1. Append |canonicalExclusionFilter| to |uuidExclusionFilters|. 1. Let optionalServiceUUIDs be {{Array.prototype.map}}.call(|optionalServices|, {{BluetoothUUID/getService()|BluetoothUUID.getService}}). 1. If any of the {{BluetoothUUID/getService()|BluetoothUUID.getService()}} calls threw an exception, throw that exception and abort these steps. 1. Remove from optionalServiceUUIDs any UUIDs that are blocklisted. 1. Let |descriptor| be
    {
      name: "bluetooth",
      filters: uuidFilters
      optionalServices: optionalServiceUUIDs,
      optionalManufacturerData: optionalManufacturerData
      acceptAllDevices: filters !== null,
    }
    
1. Let |state| be |descriptor|'s permission state.
Note: |state| will be "{{PermissionState/denied}}" in non-secure contexts because powerful features can't be used in non-secure contexts.
1. If |state| is "{{PermissionState/denied}}", return `[]` and abort these steps. 1. If the UA can prove that no devices could possibly be found in the next step, for example because there is no Bluetooth adapter with which to scan, or because the filters can't be matched by any possible advertising packet, the UA MAY return `[]` and abort these steps. 1. Let |scanResult| be the result of invoking [=scan for devices=] with |global| and |requiredServiceUUIDs|. 1. If |filters| isn't `null`, do the following sub-steps: 1. Remove devices from scanResult if they do not match a filter in uuidFilters. 1. If |exclusionFilters| isn't `null`, remove devices from scanResult if they match a filter in uuidExclusionFilters. 1. Let |navigable| be |document|'s [=navigable=]. 1. Let |promptId| be a new unique opaque string. Issue: In practice, the device list is dynamically updated while a prompt is open. The spec text currently does not reflect that but this event might be emitted multiple times with the same `promptId` and the fresh device list. See https://github.com/WebBluetoothCG/web-bluetooth/issues/621. 1. [=Trigger a prompt updated event=] given |navigable|, |promptId|, and |scanResult|. 1.

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.

Note: 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.
1. [=map/Remove=] [=map of navigables to device prompts=][|navigable|'s [=navigable id=]]. 1. The UA MAY add |device| to |storage|.
Note: Choosing a |device| probably indicates that the user intends that device to appear in the {{BluetoothPermissionStorage/allowedDevices}} list of "bluetooth"'s extra permission data for at least the current settings object, for its {{AllowedBluetoothDevice/mayUseGATT}} field to be `true`, for all the services in the union of |requiredServiceUUIDs| and |optionalServiceUUIDs| to appear in its {{AllowedBluetoothDevice/allowedServices}} list, in addition to any services that were already there, and for the manufacturer codes in |optionalManufacturerData| to appear in its {{AllowedBluetoothDevice/allowedManufacturerData}} list.
1. If |device| is "{{PermissionState/denied}}", return `[]` and abort these steps. 1. The UA MAY populate the Bluetooth cache with all Services inside device. Ignore any errors from this step. 1. Get the BluetoothDevice representing device inside [=this=], propagating any exception, and let |deviceObj| be the result. 1. Return [|deviceObj|].
The result of canonicalizing the {{BluetoothLEScanFilterInit}} |filter|, is the {{BluetoothLEScanFilterInit}} returned from the following steps: 1. If none of filter's members is present, throw a {{TypeError}} and abort these steps. 1. Let canonicalizedFilter be `{}`. 1. If filter.{{BluetoothLEScanFilterInit/services}} is present, do the following sub-steps: 1. If filter.services.length === 0, throw a {{TypeError}} and abort these steps. 1. Let services be {{Array.prototype.map}}.call(filter.services, {{BluetoothUUID/getService()|BluetoothUUID.getService}}). 1. If any of the {{BluetoothUUID/getService()|BluetoothUUID.getService()}} calls threw an exception, throw that exception and abort these steps. 1. If any service in services is blocklisted, throw a {{SecurityError}} and abort these steps. 1. Set canonicalizedFilter.services to services. 1. If filter.{{BluetoothLEScanFilterInit/name}} is present, do the following sub-steps. 1. If the UTF-8 encoding of filter.name is more than 248 bytes long, throw a {{TypeError}} and abort these steps.
Note: 248 is the maximum number of UTF-8 code units in a Bluetooth Device Name.
1. Set canonicalizedFilter.name to filter.name. 1. If filter.{{BluetoothLEScanFilterInit/namePrefix}} is present, do the following sub-steps. 1. If filter.namePrefix.length === 0 or if the UTF-8 encoding of filter.namePrefix is more than 248 bytes long, throw a {{TypeError}} and abort these steps.
Note: 248 is the maximum number of UTF-8 code units in a Bluetooth Device Name.
1. Set canonicalizedFilter.namePrefix to filter.namePrefix. 1. Set |canonicalizedFilter|["manufacturerData"] to `[]`. 1. If |filter|["{{BluetoothLEScanFilterInit/manufacturerData}}"] is present and |filter|["manufacturerData"].length === 0, throw a {{TypeError}} and abort these steps. 1. For each |manufacturerData| in |filter|["{{BluetoothLEScanFilterInit/manufacturerData}}"], do the following sub-steps: 1. If |manufacturerData| is a blocklisted manufacturer data filter, throw a {{SecurityError}} and abort these steps. 1. If there exists an object |existing| in |canonicalizedFilter|["manufacturerData"] where |existing|["companyIdentifier"] === |manufacturerData|["{{BluetoothManufacturerDataFilterInit/companyIdentifier}}"], throw a {{TypeError}} and abort these steps. 1. Let |canonicalizedManufacturerDataFilter| be the result of canonicalizing |manufacturerData|, converted to an ECMAScript value. If this throws an exception, propagate that exception and abort these steps. 1. Set |canonicalizedManufacturerDataFilter|["companyIdentifier"] to |manufacturerData|["{{BluetoothManufacturerDataFilterInit/companyIdentifier}}"]. 1. Append |canonicalizedManufacturerDataFilter| to |canonicalizedFilter|["manufacturerData"]. 1. Set |canonicalizedFilter|.serviceData to `[]`. 1. If |filter|["{{BluetoothLEScanFilterInit/serviceData}}"] is present and |filter|["serviceData"].length === 0, throw a {{TypeError}} and abort these steps. 1. For each |serviceData| in |filter|["{{BluetoothLEScanFilterInit/serviceData}}"], do the following sub-steps: 1. Let |service| be {{BluetoothUUID/getService()|BluetoothUUID.getService}}(|serviceData|["{{BluetoothServiceDataFilterInit/service}}"]). If this throws an exception, propagate that exception and abort these steps. 1. If |service| is blocklisted, throw a {{SecurityError}} and abort these steps. 1. Let |canonicalizedServiceDataFilter| be the result of canonicalizing |serviceData|, converted to an ECMAScript value. If this throws an exception, propagate that exception and abort these steps. 1. Set |canonicalizedServiceDataFilter|["service"] to |service|. 1. Append |canonicalizedServiceDataFilter| to |canonicalizedFilter|["serviceData"]. 1. Return |canonicalizedFilter|.
The result of canonicalizing the {{BluetoothDataFilterInit}} |filter|, is the {{BluetoothDataFilterInit}} returned from the following steps: 1. If |filter|.{{BluetoothDataFilterInit/dataPrefix}} is not present, let |dataPrefix| be an empty sequence of bytes. Otherwise, do the following sub-steps: 1. Let |dataPrefix| be a copy of the bytes held by |filter|.dataPrefix. 1. If the length of |dataPrefix| is zero, throw a {{TypeError}} and abort these steps. 1. If |filter|.{{BluetoothDataFilterInit/mask}} is present, let |mask| be a copy of the bytes held by |filter|.mask. Otherwise, let |mask| be a sequence of `0xFF` bytes the same length as |dataPrefix|. 1. If |mask| is not the same length as |dataPrefix|, throw a {{TypeError}} and abort these steps. 1. Return `{dataPrefix: new Uint8Array(|dataPrefix|), mask: new Uint8Array(|mask|)}`.
To scan for devices with parameters global and an optional set of Service UUIDs, defaulting to the set of all UUIDs, the UA MUST perform the following steps: 1. If the UA has scanned for devices recently 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. Issue: TODO: Nail down the amount of time. 1. 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. 1. Let |topLevelTraversable| be the |global|'s [=Window/navigable=]'s [=navigable/top-level traversable=]. 1. Let |simulatedBluetoothDevices| be an empty list. 1. If |topLevelTraversable| has a [=simulated Bluetooth adapter=], let |simulatedBluetoothDevices| be the result of [=getting the values=] of its [=simulated Bluetooth device mapping=]. Issue: Support asynchronous device discovery. 1. 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. Issue: 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. 1. If the UA supports the BR/EDR transport, perform the Device Discovery Procedure and add the discovered Bluetooth devices to nearbyDevices. Issue: All forms of BR/EDR inquiry/discovery appear to leak the unique, immutable device address. 1. Let result be a set of Bluetooth devices, initially empty. 1. For each Bluetooth device device in nearbyDevices and simulatedBluetoothDevices, do the following sub-steps: 1. 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. 1. 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.
Note: 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 requestDevice that this API provides no way to interact with.
1. 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.
Note: 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.
1. 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. 1. Return result from the scan.
Issue: We need a way for a site to register to receive an event when an interesting device comes within range.
To add an allowed [=Bluetooth device=] |device| to {{BluetoothPermissionStorage}} |storage| given a set of |requiredServiceUUIDs| and a set of |optionalServiceUUIDs|, the UA MUST run the following steps: 1. Let |grantedServiceUUIDs| be a new {{Set}}. 1. Add the contents of |requiredServiceUUIDs| to |grantedServiceUUIDs|. 1. Add the contents of |optionalServiceUUIDs| to |grantedServiceUUIDs|. 1. Search for an element |allowedDevice| in |storage|.{{BluetoothPermissionStorage/allowedDevices}} where |device| is equal to |allowedDevice|@{{[[device]]}}. If one is found, perform the following sub-steps: 1. Add the contents of |allowedDevice|.{{AllowedBluetoothDevice/allowedServices}} to |grantedServiceUUIDs|. If one is not found, perform the following sub-steps: 1. Let |allowedDevice|.{{AllowedBluetoothDevice/deviceId}} be a unique ID to the extent that the UA can determine that two Bluetooth connections are the same device and to the extent that the user wants to expose that fact to script. 1. Set |allowedDevice|.{{AllowedBluetoothDevice/allowedServices}} to |grantedServiceUUIDs|. 1. Set |allowedDevice|.{{AllowedBluetoothDevice/mayUseGATT}} to `true`.
To remove an allowed Bluetooth device |device|, given a {{BluetoothPermissionStorage}} |storage|, the UA MUST run the following steps: 1. Search for an element |allowedDevice| in |storage|.{{BluetoothPermissionStorage/allowedDevices}} where |device| is equal to |allowedDevice|@{{[[device]]}}. If no such element exists, abort these steps. 1. Remove |allowedDevice| from |storage|.{{BluetoothPermissionStorage/allowedDevices}}.
## Permission API Integration ## {#permission-api-integration} The [[permissions]] API provides a uniform way for websites to query which permissions they have.
Once a site has been granted access to a set of devices, it can use navigator.permissions.{{Permissions/query()|query}}({name: "bluetooth", ...}) to retrieve those devices after a reload.
  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 Web Bluetooth API is a [=powerful feature=] that is identified by the [=powerful feature/name=] "bluetooth". Its permission-related algorithms and types are defined as follows:
permission descriptor type
dictionary BluetoothPermissionDescriptor : PermissionDescriptor { DOMString deviceId; // These match RequestDeviceOptions. sequence<BluetoothLEScanFilterInit> filters; sequence<BluetoothServiceUUID> optionalServices = []; sequence<unsigned short> optionalManufacturerData = []; boolean acceptAllDevices = false; };
extra permission data type
{{BluetoothPermissionStorage}}, defined as: dictionary AllowedBluetoothDevice { required DOMString deviceId; required boolean mayUseGATT; // An allowedServices of "all" means all services are allowed. required (DOMString or sequence<UUID>) allowedServices; required sequence<unsigned short> allowedManufacturerData; }; dictionary BluetoothPermissionStorage { required sequence<AllowedBluetoothDevice> allowedDevices; }; {{AllowedBluetoothDevice}} instances have an internal slot \[[device]] that holds a Bluetooth device.
extra permission data constraints
Distinct elements of {{BluetoothPermissionStorage/allowedDevices}} must have different {{AllowedBluetoothDevice/[[device]]}}s and different {{AllowedBluetoothDevice/deviceId}}s. If {{AllowedBluetoothDevice/mayUseGATT}} is `false`, {{AllowedBluetoothDevice/allowedServices}} and {{AllowedBluetoothDevice/allowedManufacturerData}} must both be `[]`.
Note: A {{AllowedBluetoothDevice/deviceId}} allows a site to track that a {{BluetoothDevice}} instance seen at one time represents the same device as another {{BluetoothDevice}} instance 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
[Exposed=Window] interface BluetoothPermissionResult : PermissionStatus { attribute FrozenArray<BluetoothDevice> devices; };
permission query algorithm
To query the "bluetooth" permission with a {{BluetoothPermissionDescriptor}} desc and a {{BluetoothPermissionResult}} status, the UA must: 1. Let global be the relevant global object for status. 1. Set status.{{PermissionStatus/state}} to |desc|'s permission state. 1. If |status|.{{PermissionStatus/state}} is "{{PermissionState/denied}}", set |status|.devices to an empty {{FrozenArray}} and abort these steps. 1. Let matchingDevices be a new {{Array}}. 1. Let |storage|, a {{BluetoothPermissionStorage}}, be "bluetooth"'s extra permission data for the current settings object. 1. For each |allowedDevice| in |storage|.allowedDevices, run the following sub-steps: 1. If desc.deviceId is set and allowedDevice.deviceId != desc.deviceId, continue to the next allowedDevice. 1. If desc.filters is set, do the following sub-steps: 1. Replace each filter in |desc|.filters with the result of canonicalizing it. If any of these canonicalizations throws an error, return that error and abort these steps. 1. If allowedDevice.{{[[device]]}} does not match a filter in |desc|.filters, continue to the next allowedDevice. 1. Get the `BluetoothDevice` representing allowedDevice.{{[[device]]}} within global.navigator.bluetooth, and add the result to matchingDevices.
Note: The |desc|.optionalServices and |desc|.optionalManufacturerData fields do not affect the result.
1. Set status.devices to a new {{FrozenArray}} whose 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: 1. Let |storage|, a {{BluetoothPermissionStorage}}, be "bluetooth"'s extra permission data for the current settings object. 1. For each {{BluetoothDevice}} instance |deviceObj| in the current realm, run the following sub-steps: 1. If there is an {{AllowedBluetoothDevice}} |allowedDevice| in |storage|.{{allowedDevices}} such that: * |allowedDevice|.{{[[device]]}} is the same device as |deviceObj|.{{[[representedDevice]]}}, and * |allowedDevice|.{{AllowedBluetoothDevice/deviceId}} === |deviceObj|.{{BluetoothDevice/id}}, then update |deviceObj|.{{[[allowedServices]]}} to be |allowedDevice|.{{allowedServices}}, and continue to the next |deviceObj|. 1. Otherwise, detach |deviceObj| from its device by running the remaining steps. 1. Call deviceObj.gatt.{{BluetoothRemoteGATTServer/disconnect()}}.
Note: This fires a {{gattserverdisconnected}} event at |deviceObj|.
1. Set deviceObj.{{[[representedDevice]]}} to `null`.
## Overall Bluetooth availability ## {#availability} The UA may be running on a computer that has no Bluetooth radio. {{requestDevice()}} handles this by failing to discover any devices, which results in a {{NotFoundError}}, but websites may be able to handle it more gracefully.
To show Bluetooth UI only to users who have a Bluetooth adapter:
const bluetoothUI = document.querySelector('#bluetoothUI');
navigator.bluetooth.getAvailability().then(isAvailable => {
  bluetoothUI.hidden = !isAvailable;
});
navigator.bluetooth.addEventListener('availabilitychanged', e => {
  bluetoothUI.hidden = !e.value;
});
The getAvailability() method, when invoked, MUST return a new promise |promise| and run the following steps in parallel: 1. Let |global| be [=this=]'s [=relevant global object=]. 1. If |global|'s [=associated Document=] is not [=allowed to use=] the [=policy-controlled feature=] named "[=policy-controlled feature/bluetooth=]", [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with `false`, and abort these steps. 1.

If the user has configured the UA to return a particular answer from this function for the current origin, queue a task to resolve |promise| with the configured answer, and abort these steps.

Note: If the Web Bluetooth permission has been blocked by the user, the UA may resolve |promise| with `false`.
1. Let |simulatedBluetoothAdapter| be [=this=]'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is not empty, 1. If |simulatedBluetoothAdapter|'s [=adapter state=] is "absent", [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with `false`. 1. If |simulatedBluetoothAdapter|'s [=LE supported state=] is `false`, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with `false`. 1. Otherwise, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with `true`. 1. Abort these steps. 1. If the UA is running on a system that has a Bluetooth radio [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with `true` regardless of the powered state of the Bluetooth radio. 1. Otherwise, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with `false`.
Note: The promise is resolved in parallel to let the UA call out to other systems to determine whether Bluetooth is available.
If the user has blocked the permission and the UA resolves the getAvailability promise with false, the following can be used to detect when Bluetooth is available again to show Bluetooth UI:
function checkAvailability() {
  const bluetoothUI = document.querySelector('#bluetoothUI');
  navigator.bluetooth.getAvailability().then(isAvailable => {
    bluetoothUI.hidden = !isAvailable;
  });
}

navigator.permissions.query({name: "bluetooth"}).then(status => {
  if (status.state !== 'denied') checkAvailability();

  // Bluetooth is blocked, listen for change in PermissionStatus.
  status.onchange = () => {
    if (this.state !== 'denied') checkAvailability();
  };
});
If the UA becomes able or unable to use Bluetooth, for example because a radio was physically attached or detached, or the user has changed their configuration for the answer returned from {{getAvailability()}}, the UA must [=queue a global task=] on the [=Bluetooth task source=] given each [=global object=] |global| to run the following steps: 1. Let |oldAvailability| be the value {{getAvailability()}} would have returned before the change. 1. Let |newAvailability| be the value {{getAvailability()}} would return after the change. 1. If |oldAvailability| is not the same as |newAvailability|, 1. Let |navigator| be |global|'s associated Navigator. 1. Let |bluetooth| be |navigator|'s associated Bluetooth. 1. [=Fire an event=] named {{availabilitychanged}} using the {{ValueEvent}} interface at |bluetooth| with its {{ValueEvent/value}} attribute initialized to |newAvailability|.
  [
    Exposed=Window,
    SecureContext
  ]
  interface ValueEvent : Event {
    constructor(DOMString type, optional ValueEventInit initDict = {});
    readonly attribute any value;
  };

  dictionary ValueEventInit : EventInit {
    any value = null;
  };
{{ValueEvent}} instances are constructed as specified in [[DOM#constructing-events]]. The value attribute must return the value it was initialized to. Issue: Such a generic event type belongs in [[HTML]] or [[DOM]], not here. # Device Representation # {#device-representation} The UA needs to track Bluetooth device properties at several levels: globally, per origin, and per global object. ## Global Bluetooth device properties ## {#global-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. 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. ## BluetoothDevice ## {#bluetoothdevice-interface} A {{BluetoothDevice}} instance represents a Bluetooth device for a particular global object (or, equivalently, for a particular Realm or {{Bluetooth}} instance). [Exposed=Window, SecureContext] interface BluetoothDevice : EventTarget { readonly attribute DOMString id; readonly attribute DOMString? name; readonly attribute BluetoothRemoteGATTServer? gatt; Promise<undefined> forget(); Promise<undefined> watchAdvertisements( optional WatchAdvertisementsOptions options = {}); readonly attribute boolean watchingAdvertisements; }; BluetoothDevice includes BluetoothDeviceEventHandlers; BluetoothDevice includes CharacteristicEventHandlers; BluetoothDevice includes ServiceEventHandlers; dictionary WatchAdvertisementsOptions { AbortSignal signal; };
id 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. {{BluetoothDevice/gatt}} provides a way to interact with this device's GATT server if the site has permission to do so. {{BluetoothDevice/forget()}} enables the page to revoke access to the device that the user has granted access to. 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]] <always set in prose> The {{Bluetooth}} object that returned this {{BluetoothDevice}}.
\[[representedDevice]] <always set in prose> The Bluetooth device this object represents, or `null` if access has been revoked.
\[[gatt]] a new {{BluetoothRemoteGATTServer}} instance with its {{BluetoothRemoteGATTServer/device}} attribute initialized to `this` and its {{BluetoothRemoteGATTServer/connected}} attribute initialized to `false`. Does not change.
\[[allowedServices]] <always set in prose> This device's {{AllowedBluetoothDevice/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.
\[[allowedManufacturerData]] <always set in prose> This device's {{AllowedBluetoothDevice/allowedManufacturerData}} list for this origin.
\[[watchAdvertisementsState]] `'not-watching'` An string enumeration describing the current state of a {{BluetoothDevice/watchAdvertisements()}} operation. The possible enumeration values are: * `'not-watching'` * `'pending-watch'` * `'watching'`
To get the BluetoothDevice representing a Bluetooth device device inside a {{Bluetooth}} instance context, the UA MUST run the following steps: 1. Let |storage|, a {{BluetoothPermissionStorage}}, be "bluetooth"'s extra permission data for the current settings object. 1. Find the |allowedDevice| in |storage|.{{allowedDevices}} with allowedDevice.{{[[device]]}} the same device as device. If there is no such object, throw a {{SecurityError}} and abort these steps. 1. If there is no key in context.{{Bluetooth/[[deviceInstanceMap]]}} that is the same device as device, run the following sub-steps: 1. Let result be a new instance of {{BluetoothDevice}}. 1. Initialize all of result's optional fields to null. 1. Initialize result.{{[[context]]}} to context. 1. Initialize result.{{[[representedDevice]]}} to device. 1. Initialize result.id to allowedDevice.{{AllowedBluetoothDevice/deviceId}}, and initialize result.{{BluetoothDevice/[[allowedServices]]}} to allowedDevice.{{allowedServices}}. 1. If device has a partial or complete Bluetooth Device Name, set result.name to that string. 1. Initialize result.watchingAdvertisements to `false`. 1. Add a mapping from device to result in context.{{Bluetooth/[[deviceInstanceMap]]}}. 1. Return the value in context.{{Bluetooth/[[deviceInstanceMap]]}} whose key is the same device as device.
Getting the gatt attribute MUST perform the following steps: 1. If "bluetooth"'s extra permission data for `this`'s relevant settings object has an {{AllowedBluetoothDevice}} |allowedDevice| in its {{BluetoothPermissionStorage/allowedDevices}} list with |allowedDevice|.{{AllowedBluetoothDevice/[[device]]}} the same device as this.{{[[representedDevice]]}} and |allowedDevice|.{{AllowedBluetoothDevice/mayUseGATT}} equal to `true`, return this.{{[[gatt]]}}. 1. Otherwise, return `null`.
The forget() method, when invoked, MUST return a new promise |promise| and run the following steps: 1. Let |device| be the target {{BluetoothDevice}} object. 1. Let |storage| be the {{BluetoothPermissionStorage}} object in the current script execution environment. 1. Remove |device| from storage with |storage|. 1. Resolve |promise|.
A user agent has an associated watch advertisements manager which is the result of [=starting a new parallel queue=].
The watchAdvertisements(|options|) method, when invoked, MUST return a new promise |promise| and run the following steps: 1. Let |global| be the [=relevant global object=] of [=this=]. 1. If |options|.{{AbortSignal|signal}} is present, then perform the following sub-steps: 1. If |options|.{{AbortSignal|signal}} is [=AbortSignal/aborted=], then [=abort watchAdvertisements=] with `this` and abort these steps. 1. [=AbortSignal/add|Add the following abort steps=] to |options|.{{AbortSignal|signal}}: 1. [=Abort watchAdvertisements=] with `this`. 1. [=Reject=] |promise| with {{AbortError}} 1. If this.{{[[watchAdvertisementsState]]}} is:
: `'not-watching'` :: 1. Set this.{{[[watchAdvertisementsState]]}} to `'pending-watch'`. 1. [=parallel queue/Enqueue the following steps=] to the [=watch advertisements manager=], but abort when this.{{[[watchAdvertisementsState]]}} becomes `not-watching`: 1. Ensure that the UA is scanning for this device's advertisements. The UA SHOULD NOT filter out "duplicate" advertisements for the same device. 1. If the UA fails to enable scanning, [=queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following steps, and abort these steps: 1. Set this.{{[[watchAdvertisementsState]]}} to `'not-watching'`. 1. [=Reject=] |promise| with one of the following errors:
: The UA doesn't support scanning for advertisements :: {{NotSupportedError}} : Bluetooth is turned off :: {{InvalidStateError}} : Other reasons :: {{UnknownError}}
1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following steps, but [=abort when=] this.{{[[watchAdvertisementsState]]}} becomes `not-watching`: 1. Set this.{{[[watchAdvertisementsState]]}} to `watching`. 1. Set this.{{watchingAdvertisements}} to `true`. 1. [=Resolve=] |promise| with `undefined`. : `'pending-watch'` :: 1. [=Reject=] |promise| with {{InvalidStateError}}. : `'watching'` :: 1. [=Resolve=] |promise| with `undefined`.
1. [=If aborted=], [=reject=] |promise| with {{AbortError}}. Note: Scanning costs power, so websites should avoid watching for advertisements unnecessarily, and should use their {{AbortController}} to stop using power as soon as possible.
To abort {{BluetoothDevice/watchAdvertisements}} for a {{BluetoothDevice}} |device|, run these steps: 1. Set this.{{[[watchAdvertisementsState]]}} to `'not-watching'`. 1. Set |device|.{{watchingAdvertisements}} to `false`. 1. [=parallel queue/Enqueue the following steps=] to [=watch advertisements manager=]: 1. If no more {{BluetoothDevice}}s in the whole UA have {{watchingAdvertisements}} set to `true`, the UA SHOULD stop scanning for advertisements. Otherwise, if no more {{BluetoothDevice}}s representing the same device as `this` have {{watchingAdvertisements}} set to `true`, the UA SHOULD reconfigure the scan to avoid receiving reports for this device.
To abort all active {{BluetoothDevice/watchAdvertisements}} operations given a {{Bluetooth}} |bluetooth|, run these steps: 1. [=map/For each=] |device| in |bluetooth|.{{[[deviceInstanceMap]]}}, perform the following steps: 1. If |device|.{{[[watchAdvertisementsState]]}} is `pending-watch` or `watching`, run [=abort watchAdvertisements=] with |device|.
### Handling Visibility Change ### {#handling-visibility-change} Operations that initiate a scan for Bluetooth devices may only run in a visible [=document=]. When [=document visibility state|visibility state=] is no longer "visible", scanning operations for that [=document=] need to be aborted.
This specification defines the following [=page visibility change steps=] given |visibilityState| and |document|: 1. Let |global| be |document|'s [=relevant global object=]. 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following steps: 1. Let |navigator| be |global|'s associated Navigator. 1. Let |bluetooth| be |navigator|'s associated Bluetooth. 1. If |visibilityState| is not "visible", then [=abort all active watchAdvertisements=] operations on |bluetooth|.
### Handling Document Loss of Full Activity ### {#handling-full-activity-loss} Operations that initiate a scan for Bluetooth devices may only run in a [=fully active=] [=document=]. When [=fully active|full activity=] is lost, scanning operations for that [=document=] need to be aborted.
When the user agent determines that a {{Document}} is no longer [=fully active=], it must run these steps: 1. Let |document| be the {{Document}} that is no longer [=fully active=]. 1. Let |global| be |document|'s [=relevant global object=]. 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following steps: 1. Let |navigator| be |global|'s associated Navigator. 1. Let |bluetooth| be |navigator|'s associated Bluetooth. 1. Run [=abort all active watchAdvertisements=] operations on |bluetooth|.
### Responding to Advertising Events ### {#advertising-events} When an advertising event arrives for a {{BluetoothDevice}} with {{BluetoothDevice/watchingAdvertisements}} set, the UA delivers an "{{advertisementreceived}}" event. [Exposed=Window, SecureContext] interface BluetoothManufacturerDataMap { readonly maplike<unsigned short, DataView>; }; [Exposed=Window, SecureContext] interface BluetoothServiceDataMap { readonly maplike<UUID, DataView>; }; [ Exposed=Window, SecureContext ] interface BluetoothAdvertisingEvent : Event { constructor(DOMString type, BluetoothAdvertisingEventInit init); [SameObject] 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; [SameObject] readonly attribute BluetoothManufacturerDataMap manufacturerData; [SameObject] 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; BluetoothManufacturerDataMap manufacturerData; BluetoothServiceDataMap serviceData; };
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 {{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 {{DataView}}s. serviceData maps {{UUID}}s to {{DataView}}s.
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: 1. Let device be the Bluetooth device that sent the advertising event. 1. For each {{BluetoothDevice}} deviceObj in the UA such that device is the same device as deviceObj.{{[[representedDevice]]}}, queue a task on deviceObj's relevant settings object's responsible event loop to do the following sub-steps: 1. If deviceObj.{{watchingAdvertisements}} is `false`, abort these sub-steps. 1. Fire an `advertisementreceived` event for the advertising event at |deviceObj|.
To fire an {{advertisementreceived}} event for an advertising event |adv| at a {{BluetoothDevice}} |deviceObj|, the UA MUST perform the following steps: 1. Let event be
      {
        bubbles: true,
        device: deviceObj,
        uuids: [],
        manufacturerData: new Map(),
        serviceData: new Map()
      }
    
1. If the received signal strength is available for any packet in |adv|, set event.rssi to this signal strength in dBm. 1. 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
For each |uuid| in the listed UUIDs, if it is in this.device.{{BluetoothDevice/[[allowedServices]]}}, then append |uuid| to event.uuids.
Shortened Local Name
Complete Local Name
UTF-8 decode without BOM the AD data and set event.name to 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
For each 16-bit Company Identifier Code |manufacturerCode|, if it is in this.device.{{BluetoothDevice/[[allowedManufacturerData]]}}, and the manufacturer data is not a blocklisted manufacturer data then add a mapping of |manufacturerCode| to an {{ArrayBuffer}} containing the manufacturer-specific data to event.manufacturerData.
TX Power Level
Set event.txPower to the AD data.
Service Data - 16 bit UUID
Service Data - 32 bit UUID
Service Data - 128 bit UUID
For each UUID |uuid| in the service data, if it is in this.device.{{BluetoothDevice/[[allowedServices]]}}, then add a mapping of |uuid| to an {{ArrayBuffer}} containing the service data to event.serviceData.
Appearance
Set event.appearance to the AD data.
Otherwise
Skip to the next AD structure.
1. Fire an event named "{{advertisementreceived}}" using {{BluetoothAdvertisingEvent}} initialized with event, with its {{Event/isTrusted}} attribute initialized to `true`, 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: 1. Let event be the result of running the steps from [[dom#constructing-events]] except for the {{BluetoothAdvertisingEventInit/uuids}}, {{BluetoothAdvertisingEventInit/manufacturerData}}, and {{BluetoothAdvertisingEventInit/serviceData}} members. 1. If init.uuids is set, initialize event.uuids to a new {{FrozenArray}} containing the elements of init.uuids.map( {{BluetoothUUID/getService()|BluetoothUUID.getService}}). Otherwise initialize event.uuids to an empty {{FrozenArray}}. 1. For each mapping in init.manufacturerData: 1. Let code be the key converted to an {{unsigned short}}. 1. Let value be the value. 1. If value is not a {{BufferSource}}, throw a {{TypeError}}. 1. Let bytes be a new read only ArrayBuffer containing a copy of the bytes held by value. 1. Add a mapping from code to new DataView(bytes) in event.manufacturerData.{{BluetoothManufacturerDataMap/[[BackingMap]]}}. 1. For each mapping in init.serviceData: 1. Let key be the key. 1. Let service be the result of calling BluetoothUUID.{{BluetoothUUID/getService()|getService}}(key). 1. Let value be the value. 1. If value is not a {{BufferSource}}, throw a {{TypeError}}. 1. Let bytes be a new read only ArrayBuffer containing a copy of the bytes held by value. 1. Add a mapping from service to new DataView(bytes) in event.serviceData.{{BluetoothServiceDataMap/[[BackingMap]]}}. 1. Return event.
BluetoothManufacturerDataMap

Instances of {{BluetoothManufacturerDataMap}} have a `[[BackingMap]]` slot because they are [=maplike=], which maps manufacturer codes to the manufacturer's data, converted to {{DataView}}s.

BluetoothServiceDataMap

Instances of {{BluetoothServiceDataMap}} have a `[[BackingMap]]` slot because they are [=maplike=], which maps service UUIDs to the service's data, converted to {{DataView}}s.

# GATT Interaction # {#gatt-interaction} ## GATT Information Model ## {#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.
### Persistence across connections ### {#persistence} The Bluetooth Attribute Caching system allows bonded clients to save references to attributes from one connection to the next. Web Bluetooth treats websites as not bonded to devices they have permission to access: {{BluetoothRemoteGATTService}}, {{BluetoothRemoteGATTCharacteristic}}, and {{BluetoothRemoteGATTDescriptor}} objects become invalid on disconnection, and the site must retrieved them again when it re-connects. ### The Bluetooth cache ### {#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<{{BluetoothRemoteGATTCharacteristic}}>, or Promise<{{BluetoothRemoteGATTDescriptor}}> instance for each {{Bluetooth}} instance.
Note: 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: These steps can block, so uses of this algorithm must be in parallel.
1. 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 . 1. 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|.{{BluetoothDevice/gatt}}-[=connection-checking wrapper=] around [=a new promise=] |promise| and run the following steps [=in parallel=]: 1. Let |global| be |deviceObj|'s [=relevant global object=]. 1. [=Populate the Bluetooth cache=] with entries matching the description. 1. If the previous step returns an error, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with that error and abort these steps. 1. Let |entries| be the sequence of known-present cache entries matching the description. 1. Let |context| be |deviceObj|.{{BluetoothDevice/[[context]]}}. 1. Let |result| be a new sequence. 1. For each |entry| in |entries|: 1. If |entry| has no associated Promise<BluetoothGATT*> instance in |context|.{{Bluetooth/[[attributeInstanceMap]]}}, create a BluetoothRemoteGATTService representing |entry|, create a BluetoothRemoteGATTCharacteristic representing |entry|, or create a BluetoothRemoteGATTDescriptor representing |entry|, depending on whether |entry| is a Service, Characteristic, or Descriptor, and add a mapping from |entry| to the resulting {{Promise}} in |context|.{{Bluetooth/[[attributeInstanceMap]]}}. 1. Append to |result| the Promise<BluetoothGATT*> instance associated with |entry| in |context|.{{Bluetooth/[[attributeInstanceMap]]}}. 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with the result of [=waiting for all=] elements of |result|.
Represented(|obj|: Device or GATT Attribute) returns, depending on the type of |obj|:
{{BluetoothDevice}}
|obj|.{{[[representedDevice]]}}
{{BluetoothRemoteGATTService}}
|obj|.{{[[representedService]]}}
{{BluetoothRemoteGATTCharacteristic}}
|obj|.{{[[representedCharacteristic]]}}
{{BluetoothRemoteGATTDescriptor}}
|obj|.{{[[representedDescriptor]]}}
### Navigating the Bluetooth Hierarchy ### {#navigating-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: 1. 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. 1. If uuid is present and is blocklisted, return a promise rejected with a {{SecurityError}} and abort these steps. 1. Let deviceObj be, depending on the type of attribute:
{{BluetoothDevice}}
attribute
{{BluetoothRemoteGATTService}}
attribute.{{BluetoothRemoteGATTService/device}}
{{BluetoothRemoteGATTCharacteristic}}
attribute.{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}
1. If deviceObj.gatt.{{BluetoothRemoteGATTServer/connected}} is `false`, return a promise rejected with with a {{NetworkError}} and abort these steps. 1. If Represented(|attribute|) is `null`, return a promise rejected with an {{InvalidStateError}} and abort these steps. Note: This happens when a Service or Characteristic is removed from the device or invalidated by a disconnection, and then its object is used again. 1. Query the Bluetooth cache in deviceObj for entries that: * are within Represented(|attribute|), * have a type described by child type, * have a UUID that is not blocklisted, * 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. Let promise be the result. 1. Upon fulfillment of promise with |result|, run the following steps: * If |result| is empty, throw a {{NotFoundError}}. * Otherwise, if the single flag is set, returns the first (only) element of |result|. * Otherwise, return |result|.
### Identifying Services, Characteristics, and Descriptors ### {#identifying-attributes} 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.
Note: This definition is loose because platform APIs expose their own notion of identity without documenting whether it's based on Attribute Handle equality.
Note: 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.
## BluetoothRemoteGATTServer ## {#bluetoothgattremoteserver-interface} {{BluetoothRemoteGATTServer}} represents a GATT Server on a remote device. [Exposed=Window, SecureContext] interface BluetoothRemoteGATTServer { [SameObject] readonly attribute BluetoothDevice device; readonly attribute boolean connected; Promise<BluetoothRemoteGATTServer> connect(); undefined disconnect(); Promise<BluetoothRemoteGATTService> getPrimaryService(BluetoothServiceUUID service); Promise<sequence<BluetoothRemoteGATTService>> getPrimaryServices(optional BluetoothServiceUUID service); };
device 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.{{BluetoothRemoteGATTServer/disconnect()}}.
Note: Because {{BluetoothDevice}} instances are stored in navigator.bluetooth.{{Bluetooth/[[deviceInstanceMap]]}}, this can't happen at least until navigation releases the global object or closing the tab or window destroys the browsing context.
Note: Disconnecting on garbage collection ensures that the UA doesn't keep consuming resources on the remote device unnecessarily.
Instances of {{BluetoothRemoteGATTServer}} are created with the internal slots described in the following table:
Internal Slot Initial Value Description (non-normative)
\[[activeAlgorithms]] new {{Set}}() 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.
\[[automatedGATTConnectionResponse]] "not-expected" The simulated GATT connection response code for a GATT connection attempt.
The connect() method, when invoked, MUST perform the following steps: 1. Let |global| be [=this=]'s [=relevant global object=]. 1. If [=this=].{{BluetoothRemoteGATTServer/device}}.{{[[representedDevice]]}} is `null`, return [=a promise rejected with=] a "{{NetworkError}}" {{DOMException}}. 1. If the UA is currently using the Bluetooth system, it MAY return [=a promise rejected with=] a "{{NetworkError}}" {{DOMException}}. Issue(188): Implementations may be able to avoid this {{NetworkError}}, but for now sites need to serialize their use of this API and/or give the user a way to retry failed operations. 1. Let |promise| be [=a new promise=]. 1. If [=this=].{{BluetoothRemoteGATTServer/connected}} is `true`, [=resolve=] |promise| with [=this=] and return |promise|. 1. Add |promise| to [=this=].{{[[activeAlgorithms]]}}. 1. Run the following steps [=in parallel=]: 1. If |global|'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s simulated Bluetooth adapter is not empty, run the following steps: 1. [=Trigger a gatt connection attempted event=] given |global|'s [=Window/navigable=] and [=this=].{{BluetoothRemoteGATTServer/device}}. 1. If [=this=].{{[[automatedGATTConnectionResponse]]}} is `"not-expected"`, set it to `"expected"`. 1. If [=this=].{{[[automatedGATTConnectionResponse]]}} is `"expected"`, wait for it to change. 1. Let |response| be [=this=].{{[[automatedGATTConnectionResponse]]}}. 1. Set [=this=].{{[[automatedGATTConnectionResponse]]}} to `"not-expected"`. 1. If |response| is not `0`, do the following sub-steps: 1. Remove |promise| from [=this=].{{[[activeAlgorithms]]}}. 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. Otherwise, run the following steps: 1. If [=this=].{{BluetoothRemoteGATTServer/device}}.{{[[representedDevice]]}} has no [=ATT Bearer=], do the following sub-steps: 1.

Attempt to create an ATT Bearer using the procedures described in "Connection Establishment" under GAP Interoperability Requirements. Abort this attempt if |promise| is removed from [=this=].{{[[activeAlgorithms]]}}.

Note: These procedures can wait forever if a connectable advertisement isn't received. The website should call {{disconnect()}} if it no longer wants to connect.
1. If this attempt was aborted because |promise| was removed from [=this=].{{[[activeAlgorithms]]}}, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with an "{{AbortError}}" {{DOMException}} and abort these steps. 1. If this attempt failed for another reason, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. Use the [=Exchange MTU=] procedure to negotiate the largest supported MTU. Ignore any errors from this step. 1. The UA MAY attempt to bond with the remote device using the BR/EDR Bonding Procedure or the [=LE Bonding Procedure=]. Note: We would normally prefer to give the website control over whether and when bonding happens, but the [Core Bluetooth](https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/AboutCoreBluetooth/Introduction.html) platform API doesn't provide a way for UAs to implement such a knob. Having a bond is more secure than not having one, so this specification allows the UA to opportunistically create one on platforms where that's possible. This may cause a user-visible pairing dialog to appear when a connection is created, instead of when a restricted characteristic is accessed. 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following sub-steps: 1. If |promise| is not in [=this=].{{[[activeAlgorithms]]}}, [=reject=] |promise| with an "{{AbortError}}" {{DOMException}}, [=garbage-collect the connection=] of [=this=].{{BluetoothRemoteGATTServer/device}}.{{[[representedDevice]]}}, and abort these steps. 1. Remove |promise| from [=this=].{{[[activeAlgorithms]]}}. 1. If [=this=].{{BluetoothRemoteGATTServer/device}}.{{[[representedDevice]]}} is `null`, [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}}, [=garbage-collect the connection=] of [=this=].{{BluetoothRemoteGATTServer/device}}.{{[[representedDevice]]}}, and abort these steps. 1. Set [=this=].{{BluetoothRemoteGATTServer/connected}} to `true`. 1. [=Resolve=] |promise| with [=this=]. 1. Return |promise|.
The disconnect() method, when invoked, MUST perform the following steps: 1. Clear this.{{[[activeAlgorithms]]}} to abort any active `connect()` calls. 1. If this.{{connected}} is false, abort these steps. 1. Clean up the disconnected device this.device. 1. Let device be this.device.{{[[representedDevice]]}}. 1. Garbage-collect the connection of |device|.
To garbage-collect the connection of a |device|, the UA must, do the following steps in parallel: 1. If systems that aren't using this API, either inside or outside of the UA, are using the device's ATT Bearer, abort this algorithm. 1. For all {{BluetoothDevice}}s deviceObj in the whole UA: 1. If deviceObj.{{[[representedDevice]]}} is not the same device as device, continue to the next |deviceObj|. 1. If deviceObj.gatt.{{BluetoothRemoteGATTServer/connected}} is `true`, abort this algorithm. 1. If deviceObj.gatt.{{[[activeAlgorithms]]}} contains the {{Promise}} of a call to {{connect()}}, abort this algorithm. 1. Destroy device's ATT Bearer.
Note: 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: 1. If gattServer.connected is `true`, add promise to gattServer.{{BluetoothRemoteGATTServer/[[activeAlgorithms]]}}. 1. React to promise: * If |promise| was fulfilled with value |result|, then: 1. If promise is in gattServer.{{BluetoothRemoteGATTServer/[[activeAlgorithms]]}}, remove it and return |result|. 1. Otherwise, throw a {{NetworkError}}.
Note: This error is thrown because gattServer was disconnected during the execution of the main algorithm.
* If |promise| was rejected with reason |error|, then: 1. If promise is in gattServer.{{BluetoothRemoteGATTServer/[[activeAlgorithms]]}}, remove it and throw |error|. 1. Otherwise, throw a {{NetworkError}}.
Note: This error is thrown because gattServer was disconnected during the execution of the main algorithm.
The getPrimaryService(service) method, when invoked, MUST perform the following steps: 1. If this.device.{{BluetoothDevice/[[allowedServices]]}} is not `"all"` and service is not in this.device.{{BluetoothDevice/[[allowedServices]]}}, return a promise rejected with a {{SecurityError}} and abort these steps. 1. Return GetGATTChildren(attribute=this.device,
single=true,
uuidCanonicalizer={{BluetoothUUID/getService()|BluetoothUUID.getService}},
uuid=service,
allowedUuids=this.device.{{BluetoothDevice/[[allowedServices]]}},
child type="GATT Primary Service")
The getPrimaryServices(service) method, when invoked, MUST perform the following steps: 1. If this.device.{{BluetoothDevice/[[allowedServices]]}} is not `"all"`, and service is present and not in this.device.{{BluetoothDevice/[[allowedServices]]}}, return a promise rejected with a {{SecurityError}} and abort these steps. 1. Return GetGATTChildren(attribute=this.device,
single=false,
uuidCanonicalizer={{BluetoothUUID/getService()|BluetoothUUID.getService}},
uuid=service,
allowedUuids=this.device.{{BluetoothDevice/[[allowedServices]]}},
child type="GATT Primary Service")
## BluetoothRemoteGATTService ## {#bluetoothgattservice-interface} {{BluetoothRemoteGATTService}} represents a GATT Service, a collection of characteristics and relationships to other services that encapsulate the behavior of part of a device. [Exposed=Window, SecureContext] interface BluetoothRemoteGATTService : EventTarget { [SameObject] 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 includes CharacteristicEventHandlers; BluetoothRemoteGATTService includes ServiceEventHandlers;
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.
Instances of {{BluetoothRemoteGATTService}} are created with the internal slots described in the following table:
Internal Slot Initial Value Description (non-normative)
\[[representedService]] <always set in prose> The Service this object represents, or `null` if the Service has been removed or otherwise invalidated.
To create a BluetoothRemoteGATTService representing a Service service, the UA must run the following steps: 1. Let |global| be [=this=]'s [=relevant global object=]. 1. Let |promise| be [=a new promise=]. 1. Run the following steps in parallel: 1. Let |result| be a new instance of {{BluetoothRemoteGATTService}} with its {{[[representedService]]}} slot initialized to |service|. 1. Get the BluetoothDevice representing the device in which |service| appears, and let |device| be the result. 1. If the previous step threw an error, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with that error and abort these steps. 1. Initialize |result|.{{BluetoothRemoteGATTService/device}} from |device|. 1. Initialize |result|.{{BluetoothRemoteGATTService/uuid}} from the UUID of |service|. 1. If |service| is a Primary Service, initialize |result|.{{BluetoothRemoteGATTService/isPrimary}} to `true`. Otherwise, initialize |result|.{{BluetoothRemoteGATTService/isPrimary}} to `false`. 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with |result|. 1. Return |promise|.
The getCharacteristic(characteristic) method retrieves a Characteristic inside this Service. When invoked, it MUST return > GetGATTChildren(attribute=this,
> single=true,
> uuidCanonicalizer={{BluetoothUUID/getCharacteristic()|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()|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()|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()|BluetoothUUID.getService}},
> uuid=service,
> allowedUuids=undefined,
> child type="GATT Included Service")
## BluetoothRemoteGATTCharacteristic ## {#bluetoothgattcharacteristic-interface} {{BluetoothRemoteGATTCharacteristic}} represents a GATT Characteristic, which is a basic data element that provides further information about a peripheral's service. [Exposed=Window, SecureContext] interface BluetoothRemoteGATTCharacteristic : EventTarget { [SameObject] 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<undefined> writeValue(BufferSource value); Promise<undefined> writeValueWithResponse(BufferSource value); Promise<undefined> writeValueWithoutResponse(BufferSource value); Promise<BluetoothRemoteGATTCharacteristic> startNotifications(); Promise<BluetoothRemoteGATTCharacteristic> stopNotifications(); }; BluetoothRemoteGATTCharacteristic includes CharacteristicEventHandlers;
service 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.
Instances of {{BluetoothRemoteGATTCharacteristic}} are created with the internal slots described in the following table:
Internal Slot Initial Value Description (non-normative)
\[[representedCharacteristic]] <always set in prose> The Characteristic this object represents, or `null` if the Characteristic has been removed or otherwise invalidated.
\[[automatedCharacteristicReadResponse]] "not-expected" The simulated GATT characteristic response code for a GATT characteristic read attempt.
\[[automatedCharacteristicReadResponseData]] Empty byte sequence The simulated GATT characteristic response data for a GATT characteristic read attempt.
\[[automatedCharacteristicWriteResponse]] "not-expected" The simulated GATT characteristic response code for a GATT characteristic write attempt.
\[[automatedCharacteristicSubscribeToNotificationsResponse]] "not-expected" The simulated GATT characteristic response code for an attempt to subscribe to GATT characteristic notifications.
\[[automatedCharacteristicUnsubscribeFromNotificationsResponse]] "not-expected" The simulated GATT characteristic response code for an attempt to unsubscribe from GATT characteristic notifications.
To create a BluetoothRemoteGATTCharacteristic representing a Characteristic |characteristic|, the UA must run the following steps: 1. Let |global| be [=this=]'s [=relevant global object=]. 1. Let |promise| be [=a new promise=]. 1. Run the following steps [=in parallel=]: 1. Let |result| be a new instance of {{BluetoothRemoteGATTCharacteristic}} with its {{[[representedCharacteristic]]}} slot initialized to |characteristic|. 1. Initialize |result|.{{BluetoothRemoteGATTCharacteristic/service}}< from the {{BluetoothRemoteGATTService}} instance representing the Service in which |characteristic| appears. 1. Initialize |result|.{{BluetoothRemoteGATTCharacteristic/uuid}} from the UUID of |characteristic|. 1. Create a BluetoothCharacteristicProperties instance from the Characteristic |characteristic|, and let |properties| be the result. 1. If the previous step returned an error, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with that error and abort these steps. 1. Initialize |result|.{{BluetoothRemoteGATTCharacteristic/properties}} to |properties|. 1. Initialize |result|.{{BluetoothRemoteGATTCharacteristic/value}} to null. The UA MAY initialize |result|.{{BluetoothRemoteGATTCharacteristic/value}} to a [=new=] {{DataView}} wrapping a [=new=] {{ArrayBuffer}} containing the most recently read value from |characteristic| if this value is available. 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with |result|. 1. Return |promise|.
The getDescriptor(descriptor) method retrieves a Descriptor inside this Characteristic. When invoked, it MUST return > GetGATTChildren(attribute=this,
> single=true,
> uuidCanonicalizer={{BluetoothUUID/getDescriptor()|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()|BluetoothUUID.getDescriptor}},
> uuid=descriptor,
> allowedUuids=undefined,
> child type="GATT Descriptor")
The readValue() method, when invoked, MUST run the following steps: 1. Let |global| be [=this=]'s [=relevant global object=]. 1. Let |gatt| be [=this=].{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}. 1. If [=this=].{{BluetoothRemoteGATTCharacteristic/uuid}} is [=blocklisted for reads=], return [=a promise rejected with=] a "{{SecurityError}}" {{DOMException}} and abort these steps. 1. If |gatt|.{{BluetoothRemoteGATTServer/connected}} is `false`, return [=a promise rejected with=] a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. Let |characteristic| be [=this=].{{[[representedCharacteristic]]}}. 1. If |characteristic| is `null`, return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=] |promise| and run the following steps [=in parallel=]: 1. If the Read bit is not set in |characteristic|'s properties, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NotSupportedError}}" {{DOMException}} and abort these steps. 1. If |global|'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s simulated Bluetooth adapter is not empty, run the following steps: 1. If [=this=].{{[[automatedCharacteristicReadResponse]]}} is not `"not-expected"`, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. [=Trigger a simulated characteristic event=] given |global|'s [=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}}, |characteristic|, and `read`. 1. Set [=this=].{{[[automatedCharacteristicReadResponse]]}} to `"expected"`, and wait for it to change. 1. Let |response| be [=this=].{{[[automatedCharacteristicReadResponse]]}}. 1. Set [=this=].{{[[automatedCharacteristicReadResponse]]}} to `"not-expected"`. 1. If |response| is not `0`, do the following sub-steps: 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. Otherwise, let |buffer| be a [=new=] {{ArrayBuffer}} containing [=this=].{{[[automatedCharacteristicReadResponseData]]}}. 1. Otherwise, run the following steps: 1. If the UA is currently using the Bluetooth system, it MAY [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. Issue(188): Implementations may be able to avoid this {{NetworkError}}, but for now sites need to serialize their use of this API and/or give the user a way to retry failed operations. 1. Use any combination of the sub-procedures in the [=Characteristic Value Read=] procedure to retrieve the value of |characteristic| and let |buffer| be a [=new=] {{ArrayBuffer}} holding the retrieved value. Handle errors as described in . 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following steps: 1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. If the sub-procedures above returned an error, [=reject=] |promise| with that error and abort these steps. 1. Assign a [=new=] {{DataView}} created with |buffer| to [=this=].{{BluetoothRemoteGATTCharacteristic/value}}. 1. [=Fire an event=] named "{{characteristicvaluechanged}}" with its {{Event/bubbles}} attribute initialized to `true` at [=this=]. 1. [=Resolve=] |promise| with [=this=].{{BluetoothRemoteGATTCharacteristic/value}}.
To WriteCharacteristicValue( this: BluetoothRemoteGATTCharacteristic,
value: BufferSource,
response: string),

the UA MUST perform the following steps: 1. Let |global| be |this|'s [=relevant global object=]. 1. Let |gatt| be |this|.{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}. 1. If |this|.{{BluetoothRemoteGATTCharacteristic/uuid}} is [=blocklisted for writes=], return [=a promise rejected with=] a "{{SecurityError}}" {{DOMException}} and abort these steps. 1. Let |bytes| be [=a copy of the bytes held=] by |value|. 1. 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 "{{InvalidModificationError}}" {{DOMException}} and abort these steps. 1. If |gatt|.{{BluetoothRemoteGATTServer/connected}} is `false`, return [=a promise rejected with=] a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. Let |characteristic| be |this|.{{[[representedCharacteristic]]}}. 1. If |characteristic| is `null`, return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=] |promise| and run the following steps [=in parallel=]. 1. Assert: |response| is one of "required", "never", or "optional". 1. If |global|'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s simulated Bluetooth adapter is not empty, run the following steps: 1. If [=this=].{{[[automatedCharacteristicWriteResponse]]}} is not `"not-expected"`, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. [=Trigger a simulated characteristic event=] given |global|'s [=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}}, |characteristic|, `write`, and |bytes|. 1. Set [=this=].{{[[automatedCharacteristicWriteResponse]]}} to `"expected"`, and wait for it to change. 1. Let |response| be [=this=].{{[[automatedCharacteristicWriteResponse]]}}. 1. Set [=this=].{{[[automatedCharacteristicWriteResponse]]}} to `"not-expected"`. 1. If |response| is not `0`, do the following sub-steps: 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. Otherwise, run the following steps: 1. If the UA is currently using the Bluetooth system, it MAY [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. Issue(188): Implementations may be able to avoid this {{NetworkError}}, but for now sites need to serialize their use of this API and/or give the user a way to retry failed operations. 1. Write |bytes| to |characteristic| by performing the following steps:
If |response| is "required"
Use the [=Write Characteristic Value=] procedure.
If |response| is "never"
Use the [=Write Without Response=] procedure.
Otherwise
Use any combination of the sub-procedures in the [=Characteristic Value Write=] procedure.
Handle errors as described in . 1. Queue a global task on |global| using the [=Bluetooth task source=] to perform the following steps: 1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. If the procedure above returned an error, [=reject=] |promise| with that error and abort these steps. 1. Set |this|.{{BluetoothRemoteGATTCharacteristic/value}} to a [=new=] {{DataView}} wrapping a [=new=] {{ArrayBuffer}} containing |bytes|. 1. [=Resolve=] |promise| with undefined.
Deprecated. Use {{writeValueWithResponse()}} and {{writeValueWithoutResponse()}} instead. The writeValue(value) method, when invoked, MUST return > WriteCharacteristicValue( > this=this,
> value=value,
> response="optional")
Issue(238): This method is for backwards compatibility only. New implementations should not implement this method.
The writeValueWithResponse(value) method, when invoked, MUST return > WriteCharacteristicValue( > this=this,
> value=value,
> response="required")
The writeValueWithoutResponse(value) method, when invoked, MUST return > WriteCharacteristicValue( > this=this,
> value=value,
> response="never")
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.
Note: The set for a given characteristic holds the {{Navigator/bluetooth|navigator.bluetooth}} objects for each Realm that has registered for notifications. All notifications become inactive when a device is disconnected. A site that wants to keep getting notifications after reconnecting needs to call {{startNotifications()}} again, and there is an unavoidable risk that some notifications will be missed in the gap before {{startNotifications()}} takes effect.
The startNotifications() method, when invoked, MUST run the following steps. See for details of receiving notifications. 1. Let |global| be [=this=]'s [=relevant global object=]. 1. Let |gatt| be [=this=].{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}. 1. If [=this=].{{BluetoothRemoteGATTCharacteristic/uuid}} is [=blocklisted for reads=], return [=a promise rejected with=] a "{{SecurityError}}" {{DOMException}}. 1. If |gatt|.{{BluetoothRemoteGATTServer/connected}} is `false`, return [=a promise rejected with=] with a "{{NetworkError}}" {{DOMException}}. 1. Let |characteristic| be [=this=].{{[[representedCharacteristic]]}}. 1. If |characteristic| is `null`, return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. 1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=] |promise| and run the following steps [=in parallel=]. 1. If neither of the Notify or Indicate bits are set in |characteristic|'s properties, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a {{NotSupportedError}} and abort these steps. 1. If |characteristic|'s [=active notification context set=] contains {{Navigator/bluetooth|navigator.bluetooth}}, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=resolve=] |promise| with [=this=] and abort these steps. 1. If |global|'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s simulated Bluetooth adapter is not empty, run the following steps: 1. If [=this=].{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}} is not `"not-expected"`, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. [=Trigger a simulated characteristic event=] given |global|'s [=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}}, |characteristic|, and `subscribe-to-notifications`. 1. Set [=this=].{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}} to `"expected"`, and wait for it to change. 1. Let |response| be [=this=].{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}}. 1. Set [=this=].{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}} to `"not-expected"`. 1. If |response| is not `0`, do the following sub-steps: 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. Otherwise, let |success| to be `true`. 1. Otherwise, run the following steps: 1. If the UA is currently using the Bluetooth system, it MAY [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. Issue(188): Implementations may be able to avoid this {{NetworkError}}, but for now sites need to serialize their use of this API and/or give the user a way to retry failed operations. 1. If the characteristic has a [=Client Characteristic Configuration=] descriptor, use any of the [=Characteristic Descriptors=] procedures to ensure that one of the Notification or Indication bits 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 .
Note: Some devices have characteristics whose properties include the Notify or Indicate bit but that don't have a Client Characteristic Configuration descriptor. These non-standard-compliant characteristics tend to send notifications or indications unconditionally, so this specification allows applications to simply subscribe to their messages.
1. If the procedures were successful, let |success| to be `true`. 1. If |success| is `true`, add {{Navigator/bluetooth|navigator.bluetooth}} to|characteristic|'s [=active notification context set=]. 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following steps: 1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. If the procedures above returned an error, reject promise with that error and abort these steps. 1. [=Resolve=] |promise| with [=this=].
Note: 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: 1. Let |characteristic| be this.{{[[representedCharacteristic]]}}. 1. If |characteristic| is `null`, return a promise rejected with an {{InvalidStateError}} and abort these steps. 1. If characteristic's active notification context set contains {{Navigator/bluetooth|navigator.bluetooth}}, remove it. 1. If |global|'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s simulated Bluetooth adapter is not empty, run the following steps: 1. If [=this=].{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}} is not `"not-expected"`, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. [=Trigger a simulated characteristic event=] given |global|'s [=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}}, |characteristic|, and `unsubscribe-from-notifications`. 1. Set [=this=].{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}} to `"expected"`, and wait for it to change. 1. Let |response| be [=this=].{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}}. 1. Set [=this=].{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}} to `"not-expected"`. 1. If |response| is not `0`, do the following sub-steps: 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps.. 1. Otherwise, run the following steps: 1. If characteristic's active notification context set became empty and the characteristic has a Client Characteristic Configuration descriptor, the UA SHOULD use any of the Characteristic Descriptors procedures to clear the Notification and Indication bits in characteristic's Client Characteristic Configuration descriptor. 1. [=Queue a global task=] on the [=Bluetooth task source=] given [=this=]'s [=relevant global object=] to [=resolve=] |promise| with [=this=].
Note: Queuing a task to resolve the promise ensures that no value change events due to notifications arrive after the promise resolves.
### BluetoothCharacteristicProperties ### {#characteristicproperties-interface} Each {{BluetoothRemoteGATTCharacteristic}} exposes its characteristic properties through a {{BluetoothCharacteristicProperties}} object. These properties express what operations are valid on the characteristic. [Exposed=Window, SecureContext] 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 run the following steps: 1. Let |propertiesObj| be a new instance of {{BluetoothCharacteristicProperties}}. 1. Let |properties| be the [=characteristic properties=] of |characteristic|. 1. Initialize the attributes of |propertiesObj| from the corresponding bits in |properties|:
Attribute Bit
broadcast Broadcast
read Read
writeWithoutResponse Write Without Response
write Write
notify Notify
indicate Indicate
authenticatedSignedWrites Authenticated Signed Writes
1. If the Extended Properties bit of the [=characteristic properties=] is not set, initialize |propertiesObj|.{{BluetoothCharacteristicProperties/reliableWrite}} and |propertiesObj|.{{BluetoothCharacteristicProperties/writableAuxiliaries}} to false. Otherwise, run the following steps: 1. Discover the [=Characteristic Extended Properties=] descriptor for |characteristic| and read its value into |extendedProperties|. Handle errors as described in . Issue: [=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. 1. If the previous step returned an error, return that error. 1. Initialize |propertiesObj|.{{BluetoothCharacteristicProperties/reliableWrite}} from the Reliable Write bit of |extendedProperties|. 1. Initialize |propertiesObj|.{{BluetoothCharacteristicProperties/writableAuxiliaries}} from the Writable Auxiliaries bit of |extendedProperties|. 1. Return |propertiesObj|.
## BluetoothRemoteGATTDescriptor ## {#bluetoothgattdescriptor-interface} {{BluetoothRemoteGATTDescriptor}} represents a GATT Descriptor, which provides further information about a Characteristic's value. [Exposed=Window, SecureContext] interface BluetoothRemoteGATTDescriptor { [SameObject] readonly attribute BluetoothRemoteGATTCharacteristic characteristic; readonly attribute UUID uuid; readonly attribute DataView? value; Promise<DataView> readValue(); Promise<undefined> writeValue(BufferSource value); };
characteristic 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.
Instances of {{BluetoothRemoteGATTDescriptor}} are created with the internal slots described in the following table:
Internal Slot Initial Value Description (non-normative)
\[[representedDescriptor]] <always set in prose> The Descriptor this object represents, or `null` if the Descriptor has been removed or otherwise invalidated.
\[[automatedDescriptorReadResponse]] "not-expected" The simulated GATT descriptor response code for a GATT descriptor read attempt.
\[[automatedDescriptorReadResponseData]] Empty byte sequence The simulated GATT descriptor response data for a GATT descriptor read attempt.
\[[automatedDescriptorWriteResponse]] "not-expected" The simulated GATT descriptor response code for a GATT descriptor write attempt.
To create a BluetoothRemoteGATTDescriptor representing a Descriptor descriptor, the UA MUST run the following steps. 1. Let |promise| be [=a new promise=]. 1. Run the following steps [=in parallel=]: 1. Let |result| be a new instance of {{BluetoothRemoteGATTDescriptor}} with its {{[[representedDescriptor]]}} slot initialized to |descriptor|. 1. Initialize |result|.{{BluetoothRemoteGATTDescriptor/characteristic}} from the {{BluetoothRemoteGATTCharacteristic}} instance representing the Characteristic in which |descriptor| appears. 1. Initialize |result|.{{BluetoothRemoteGATTDescriptor/uuid}} from the UUID of |descriptor|. 1. Initialize |result|.{{BluetoothRemoteGATTDescriptor/value}} to `null`. The UA MAY initialize |result|.{{BluetoothRemoteGATTDescriptor/value}} a [=new=] {{DataView}} wrapping a [=new=] {{ArrayBuffer}} containing the most recently read value from |descriptor| if this value is available. 1. [=Queue a global task=] on the [=Bluetooth task source=] given [=this=]'s [=relevant global object=] to [=resolve=] |promise| with |result|. 1. Return |promise|.
The readValue() method, when invoked, MUST run the following steps: 1. Let |global| be [=this=]'s [=relevant global object=]. 1. Let |gatt| be [=this=].{{BluetoothRemoteGATTDescriptor/characteristic}}.{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}. 1. If [=this=].{{BluetoothRemoteGATTDescriptor/uuid}} is [=blocklisted for reads=], return [=a promise rejected with=] a "{{SecurityError}}" {{DOMException}}. 1. If |gatt|.{{BluetoothRemoteGATTServer/connected}} is `false`, return [=a promise rejected with=] a "{{NetworkError}}" {{DOMException}}. 1. Let |descriptor| be [=this=].{{[[representedDescriptor]]}}. 1. If |descriptor| is `null`, return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. 1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=] |promise| and run the following steps [=in parallel=]: 1. If |global|'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s simulated Bluetooth adapter is not empty, run the following steps: 1. If [=this=].{{[[automatedDescriptorReadResponse]]}} is not `"not-expected"`, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. [=Trigger a simulated descriptor event=] given |global|'s [=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}}, |descriptor|, and `read`. 1. Set [=this=].{{[[automatedDescriptorReadResponse]]}} to `"expected"`, and wait for it to change. 1. Let |response| be [=this=].{{[[automatedDescriptorReadResponse]]}}. 1. Set [=this=].{{[[automatedDescriptorReadResponse]]}} to `"not-expected"`. 1. If |response| is not `0`, do the following sub-steps: 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. Otherwise, let |buffer| be a [=new=] {{ArrayBuffer}} containing [=this=].{{[[automatedDescriptorReadResponseData]]}}. 1. Otherwise, run the following steps: 1. If the UA is currently using the Bluetooth system, it MAY [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. Issue(188): Implementations may be able to avoid this {{NetworkError}}, but for now sites need to serialize their use of this API and/or give the user a way to retry failed operations. 1. Use either the [=Read Characteristic Descriptors=] or the [=Read Long Characteristic Descriptors=] sub-procedure to retrieve the value of |descriptor| and let |buffer| be a [=new=] {{ArrayBuffer}} holding the retrieved value. Handle errors as described in . 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following steps: 1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. If the sub-procedure above returned an error, [=reject=] |promise| with that error and abort these steps. 1. Assign a [=new=] {{DataView}} created with |buffer| to [=this=].{{BluetoothRemoteGATTDescriptor/value}}. 1. [=Resolve=] |promise| with [=this=].{{BluetoothRemoteGATTDescriptor/value}}.
The writeValue(value) method, when invoked, MUST run the following steps: 1. Let |global| be [=this=]'s [=relevant global object=]. 1. Let |gatt| be [=this=].{{BluetoothRemoteGATTDescriptor/characteristic}}.{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}. 1. If [=this=].{{BluetoothRemoteGATTDescriptor/uuid}} is [=blocklisted for writes=], return [=a promise rejected with=] a "{{SecurityError}}" {{DOMException}}. 1. Let |bytes| be [=a copy of the bytes held=] by |value|. 1. 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 "{{InvalidModificationError}}" {{DOMException}}. 1. If |gatt|.{{BluetoothRemoteGATTServer/connected}} is `false`, return [=a promise rejected with=] a "{{NetworkError}}" {{DOMException}}. 1. Let |descriptor| be [=this=].{{[[representedDescriptor]]}}. 1. If |descriptor| is `null`, return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. 1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=] |promise| and run the following steps [=in parallel=]. 1. If |global|'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s simulated Bluetooth adapter is not empty, run the following steps: 1. If [=this=].{{[[automatedDescriptorWriteResponse]]}} is not `"not-expected"`, [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. [=Trigger a simulated descriptor event=] given |global|'s [=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}}, |descriptor|, `write`, and |bytes|. 1. Set [=this=].{{[[automatedDescriptorWriteResponse]]}} to `"expected"`, and wait for it to change. 1. Let |response| be [=this=].{{[[automatedDescriptorWriteResponse]]}}. 1. Set [=this=].{{[[automatedDescriptorWriteResponse]]}} to `"not-expected"`. 1. If |response| is not `0`, do the following sub-steps: 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. Otherwise, run the following steps: 1. If the UA is currently using the Bluetooth system, it MAY [=queue a global task=] on the [=Bluetooth task source=] given |global| to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. Issue(188): Implementations may be able to avoid this {{NetworkError}}, but for now sites need to serialize their use of this API and/or give the user a way to retry failed operations. 1. Use either the [=Write Characteristic Descriptors=] or the [=Write Long Characteristic Descriptors=] sub-procedure to write |bytes| to |descriptor|. Handle errors as described in . 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following steps: 1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and abort these steps. 1. If the sub-procedure above returned an error, [=reject=] |promise| with that error and abort these steps. 1. Set [=this=].{{BluetoothRemoteGATTDescriptor/value}} to a [=new=] {{DataView}} wrapping a [=new=] {{ArrayBuffer}} containing |bytes|. 1. [=Resolve=] |promise| with `undefined`.
## Events ## {#events}
### Bluetooth Tree ### {#bluetooth-tree} The Bluetooth tree is the name given to {{Navigator/bluetooth|navigator.bluetooth}} and objects implementing the {{BluetoothDevice}}, {{BluetoothRemoteGATTService}}, {{BluetoothRemoteGATTCharacteristic}}, or {{BluetoothRemoteGATTDescriptor}} interface participate in a tree. * The children of {{Navigator/bluetooth|navigator.bluetooth}} are the {{BluetoothDevice}} objects representing devices in the {{BluetoothPermissionStorage/allowedDevices}} list in "bluetooth"'s extra permission data for {{Navigator/bluetooth|navigator.bluetooth}}'s relevant settings object, in an unspecified order. * The children of a {{BluetoothDevice}} are the {{BluetoothRemoteGATTService}} objects representing Primary and Secondary Services on its GATT Server whose UUIDs are on the origin and device's {{AllowedBluetoothDevice/allowedServices}} list. 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 {{BluetoothRemoteGATTService}} are the {{BluetoothRemoteGATTCharacteristic}} objects 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 {{BluetoothRemoteGATTCharacteristic}} are the {{BluetoothRemoteGATTDescriptor}} objects representing its Descriptors in the order returned by the Discover All Characteristic Descriptors procedure.
### Event types ### {#event-types}
advertisementreceived
Fired on a {{BluetoothDevice}} when an advertising event is received from that device.
availabilitychanged
Fired on {{Bluetooth|navigator.bluetooth}} when the Bluetooth system as a whole becomes available or unavailable to the UA.
characteristicvaluechanged
Fired on a {{BluetoothRemoteGATTCharacteristic}} when its value changes, either as a result of a read request , or a value change notification/indication.
gattserverdisconnected
Fired on a {{BluetoothDevice}} when an active GATT connection is lost.
serviceadded
Fired on a new {{BluetoothRemoteGATTService}} when it has been discovered on a remote device, just after it is added to the Bluetooth tree.
servicechanged
Fired on a {{BluetoothRemoteGATTService}} when 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 {{BluetoothRemoteGATTService}} when it has been removed from its device, just before it is removed from the Bluetooth tree.
### Responding to Disconnection ### {#disconnection-events}
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 global task=] on the [=Bluetooth task source=] given |deviceObj|'s [=relevant global object=] to perform the following steps: 1. If deviceObj.{{BluetoothDevice/[[representedDevice]]}} is not the same device as device, abort these steps. 1. If !deviceObj.gatt.{{BluetoothRemoteGATTServer/connected}}, abort these steps. 1. Clean up the disconnected device |deviceObj|.
To clean up the disconnected device |deviceObj|, the UA must: 1. Set deviceObj.gatt.{{BluetoothRemoteGATTServer/connected}} to `false`. 1. Clear deviceObj.gatt.{{[[activeAlgorithms]]}}. 1. Set deviceObj.gatt.{{[[automatedGATTConnectionResponse]]}} to `"not-expected"`. 1. Let |context| be |deviceObj|.{{[[context]]}}. 1. Remove all entries from |context|.{{[[attributeInstanceMap]]}} whose keys are inside |deviceObj|.{{[[representedDevice]]}}. 1. For each {{BluetoothRemoteGATTService}} |service| in |deviceObj|'s realm, set |service|.{{[[representedService]]}} to `null`. 1. For each {{BluetoothRemoteGATTCharacteristic}} |characteristic| in |deviceObj|'s realm, do the following sub-steps: 1. Let |notificationContexts| be |characteristic|.{{[[representedCharacteristic]]}}'s active notification context set. 1. Remove |context| from |notificationContexts|. 1. If |notificationContexts| became empty and there is still an ATT Bearer to |deviceObj|.{{[[representedDevice]]}} and characteristic has a Client Characteristic Configuration descriptor, the UA SHOULD use any of the Characteristic Descriptors procedures to clear the Notification and Indication bits in characteristic's Client Characteristic Configuration descriptor. 1. Set |characteristic|.{{[[representedCharacteristic]]}} to `null`. 1. For each {{BluetoothRemoteGATTDescriptor}} |descriptor| in |deviceObj|'s realm, set |descriptor|.{{[[representedDescriptor]]}} to `null`. 1. Fire an event named {{gattserverdisconnected}} with its {{Event/bubbles}} attribute initialized to `true` at deviceObj.
Note: This event is not fired at the {{BluetoothRemoteGATTServer}}.
### Responding to Notifications and Indications ### {#notification-events}
When the UA receives a Bluetooth Characteristic Value Notification or Indication, it must perform the following steps: 1. For each |bluetoothGlobal| in the Characteristic's [=active notification context set=], [=queue a global task=] on the [=Bluetooth task source=] given |bluetoothGlobal|'s [=relevant global object=] to do the following sub-steps: 1. Let characteristicObject be the {{BluetoothRemoteGATTCharacteristic}} in the Bluetooth tree rooted at bluetoothGlobal that represents the Characteristic. 1. If characteristicObject .service.device.gatt.{{BluetoothRemoteGATTServer/connected}} is `false`, abort these sub-steps. 1. Set characteristicObject.value to a new {{DataView}} wrapping a new {{ArrayBuffer}} holding the new value of the Characteristic. 1. Fire an event named {{characteristicvaluechanged}} with its bubbles attribute initialized to true at characteristicObject.
### Responding to Service Changes ### {#service-change-events}
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. 1. Let removedAttributes be the list of attributes in the range indicated by the Service Changed characteristic that the UA had discovered before the Indication. 1. 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. 1. Let addedAttributes be the list of attributes discovered in the previous step. 1. If an attribute with the same definition (see the Service Interoperability Requirements), ignoring Characteristic and Descriptor values, appears in both removedAttributes and addedAttributes, remove it from both.
Given the following device states:
State 1
  • Service A
    • Characteristic C: value `[1, 2, 3]`
  • Service B
State 2
  • Service A
    • Characteristic C: value `[3, 2, 1]`
  • Service B
State 3
  • Service A
    • Characteristic D: value `[3, 2, 1]`
  • Service B
State 4
  • Service A
    • Characteristic C: value `[1, 2, 3]`
  • Service B
    • Include Service A
A transition from state 1 to 2 leaves service A with "the same definition, ignoring Characteristic and Descriptor values", which means it's removed from both |removedAttributes| and |addedAttributes|, and it wouldn't appear in any {{servicechanged}} events. A transition from state 1 to 3 leaves service A with a different definition, because a service definition includes its characteristic definitions, so it's left in both |removedAttributes| and |addedAttributes|. Then in step 8, the service is moved to |changedServices|, which makes it cause a {{servicechanged}} event instead of both a {{serviceadded}} and {{serviceremoved}}. Step 9 also adds service A to |changedServices| because characteristic C was removed and characteristic D was added. A transition from state 1 to 4 is similar to the 1->3 transition. Service B is moved to |changedServices| in step 8, but no characteristics or descriptors have changed, so it's not redundantly added in step 9.
1. Let |invalidatedAttributes| be the attributes in |removedAttributes| but not |addedAttributes|. 1. For each environment settings object |settings| in the UA, queue a task on its responsible event loop to do the following sub-steps: 1. For each {{BluetoothRemoteGATTService}} |service| whose relevant settings object is |settings|, if |service|.{{[[representedService]]}} is in |invalidatedAttributes|, set |service|.{{[[representedService]]}} to `null`. 1. For each {{BluetoothRemoteGATTCharacteristic}} |characteristic| whose relevant settings object is |settings|, if |characteristic|.{{[[representedCharacteristic]]}} is in |invalidatedAttributes|, set |characteristic|.{{[[representedCharacteristic]]}} to `null`. 1. For each {{BluetoothRemoteGATTDescriptor}} |descriptor| whose relevant settings object is |settings|, if |descriptor|.{{[[representedDescriptor]]}} is in |invalidatedAttributes|, set |descriptor|.{{[[representedDescriptor]]}} to `null`. 1. Let |global| be |settings|' global object. 1. Remove every entry from |global|.navigator.bluetooth.{{[[attributeInstanceMap]]}} that represents an attribute that is in |invalidatedAttributes|. 1. Let changedServices be a set of Services, initially empty. 1.

If the same Service appears in both removedAttributes and addedAttributes, remove it from both, and add it to changedServices.

1.

For each Characteristic and Descriptor in removedAttributes or addedAttributes, remove it from its original list, and add its parent Service to changedServices.

Note: After this point, removedAttributes and addedAttributes contain only Services.
1.

If a Service in addedAttributes would not have been returned from any previous call to getPrimaryService, getPrimaryServices, getIncludedService, or getIncludedServices if it had existed at the time of the call, the UA MAY remove the Service from addedAttributes.

1. Let changedDevices be the set of Bluetooth devices that contain any Service in removedAttributes, addedAttributes, and changedServices. 1. For each {{BluetoothDevice}} |deviceObj| that is connected to a device in |changedDevices|, [=queue a global task=] on the [=Bluetooth task source=] given |deviceObj|'s [=relevant global object=] to do the following steps: 1. For each Service service in removedAttributes: 1. If deviceObj.{{BluetoothDevice/[[allowedServices]]}} is `"all"` or contains the Service's UUID, fire an event named {{serviceremoved}} with its bubbles attribute initialized to true at the {{BluetoothRemoteGATTService}} representing the Service. 1. Remove this {{BluetoothRemoteGATTService}} from the Bluetooth tree. 1. For each Service in addedAttributes, if deviceObj.{{BluetoothDevice/[[allowedServices]]}} is `"all"` or contains the Service's UUID, add the {{BluetoothRemoteGATTService}} representing this Service to the Bluetooth tree and then fire an event named {{serviceadded}} with its bubbles attribute initialized to true at the {{BluetoothRemoteGATTService}}. 1. For each Service in changedServices, if deviceObj.{{BluetoothDevice/[[allowedServices]]}} is `"all"` or contains the Service's UUID, fire an event named {{servicechanged}} with its bubbles attribute initialized to true at the {{BluetoothRemoteGATTService}} representing the Service.
### IDL event handlers ### {#idl-event-handlers} [SecureContext] interface mixin CharacteristicEventHandlers { attribute EventHandler oncharacteristicvaluechanged; }; oncharacteristicvaluechanged is an Event handler IDL attribute for the {{characteristicvaluechanged}} event type. [SecureContext] interface mixin BluetoothDeviceEventHandlers { attribute EventHandler onadvertisementreceived; attribute EventHandler ongattserverdisconnected; }; onadvertisementreceived is an Event handler IDL attribute for the {{advertisementreceived}} event type. ongattserverdisconnected is an Event handler IDL attribute for the {{gattserverdisconnected}} event type. [SecureContext] interface mixin 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. ## Error handling ## {#error-handling}
Note: 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: 1. If the procedure times out or the ATT Bearer (described in Profile Fundamentals) is absent or terminated for any reason, return a {{NetworkError}} from the step and abort these steps. 1. Take the following actions depending on the Error Code:
Invalid PDU
Invalid Offset
Attribute Not Found
Unsupported 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 {{NotSupportedError}} from the step.
Invalid Handle
Return an {{InvalidStateError}} from the step.
Invalid Attribute Value Length
Return an {{InvalidModificationError}} from 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 {{NotSupportedError}} from 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 {{InvalidModificationError}} from the step.

Insufficient Authentication
Insufficient Encryption
Insufficient 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 {{SecurityError}} from the step. Otherwise, retry the step at the new higher security level.
Insufficient Authorization
Return a {{SecurityError}} from the step.
Application Error
If the GATT procedure was a Write, return an {{InvalidModificationError}} from the step. Otherwise, return a {{NotSupportedError}} from the step.
Read Not Permitted
Write Not Permitted
Request Not Supported
Prepare Queue Full
Insufficient Resources
Unlikely Error
Anything else
Return a {{NotSupportedError}} from the step.
# UUIDs # {#uuids} typedef DOMString UUID; A UUID string represents a 128-bit [[!RFC4122 obsolete]] 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.
Note: This standard provides the BluetoothUUID.canonicalUUID(alias) function to map a 16- or 32-bit Bluetooth UUID alias to its 128-bit form.
Note: 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.
## Standardized UUIDs ## {#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. A valid name is a string that matches the [[!ECMAScript]] regexp /^[a-z0-9_-.]+$/. [Exposed=Window] 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.
Note: 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 valid names from GATT assigned services keys, or, equivalently, the values for which {{BluetoothUUID/getService()|BluetoothUUID.getService()}} does not throw an exception. BluetoothCharacteristicUUID represents 16- and 32-bit UUID aliases, valid UUIDs, and valid names from GATT assigned characteristics keys, or, equivalently, the values for which {{BluetoothUUID/getCharacteristic()|BluetoothUUID.getCharacteristic()}} does not throw an exception. BluetoothDescriptorUUID represents 16- and 32-bit UUID aliases, valid UUIDs, and valid names from GATT assigned descriptors keys, or, equivalently, the values for which {{BluetoothUUID/getDescriptor()|BluetoothUUID.getDescriptor()}} does not throw an exception.
To ResolveUUIDName(name, GATT assigned numbers), the UA MUST perform the following steps: 1. If name is an unsigned long, return {{BluetoothUUID/canonicalUUID()|BluetoothUUID.canonicalUUID}}(name) and abort these steps. 1. If name is a valid UUID, return name and abort these steps. 1. If name is a valid name and maps to a valid UUID in GATT assigned numbers, let alias be its assigned number, and return {{BluetoothUUID/canonicalUUID()|BluetoothUUID.canonicalUUID}}(alias). 1. Otherwise, throw a {{TypeError}}.
The static BluetoothUUID. getService(name) method, when invoked, MUST return ResolveUUIDName(name, GATT assigned services). The static BluetoothUUID. getCharacteristic(name) method, when invoked, MUST return ResolveUUIDName(name, GATT assigned characteristics). The static BluetoothUUID. getDescriptor(name) method, when invoked, MUST return ResolveUUIDName(name, GATT assigned descriptors).
{{BluetoothUUID/getService()|BluetoothUUID.getService}}(" cycling_power") returns "00001818-0000-1000-8000-00805f9b34fb". {{BluetoothUUID/getService()|BluetoothUUID.getService}}("00001801-0000-1000-8000-00805f9b34fb") returns "00001801-0000-1000-8000-00805f9b34fb". {{BluetoothUUID/getService()|BluetoothUUID.getService}}("unknown-service") throws a {{TypeError}}. {{BluetoothUUID/getCharacteristic()|BluetoothUUID.getCharacteristic}}("{{ieee_11073-20601_regulatory_certification_data_list|ieee_11073-20601_regulatory_certification_data_list}}") returns "00002a2a-0000-1000-8000-00805f9b34fb". {{BluetoothUUID/getDescriptor()|BluetoothUUID.getDescriptor}}("{{gatt.characteristic_presentation_format|gatt.characteristic_presentation_format}}") returns "00002904-0000-1000-8000-00805f9b34fb".
## GATT assigned numbers ## {#gatt-assigned-numbers} This specification provides human-readable names for GATT assigned numbers to increase readability for developers using standardized GATT services, characteristics, and descriptors. The GATT assigned numbers files live in the https://github.com/WebBluetoothCG/registries repository.
This specification previously used a mapping table, provided by the Bluetooth SIG, for defining these human-readable names. When discovered it was no longer maintained, a mapping table was specified directly in the specification.
The result of parsing the GATT assigned numbers at a URL url is a map from valid names to valid UUIDs, or an error, produced by the following algorithm: 1. Fetch url, and let contents be its body, decoded as UTF-8. 1. Let lines be contents split on '\n'. 1. Let result be an empty map. 1. For each line in lines, do the following sub-steps: 1. If line is empty or its first character is '#', continue to the next line. 1. If line consists of a valid name, a space (U+0020), and a valid UUID, let name be that name and let uuid be that UUID. 1. Otherwise, return an error and abort these steps. 1. If name is already in result, return an error and abort these steps. 1. Add a mapping in result from name to uuid. 1. Return result.
The GATT assigned services are the result of parsing the GATT assigned numbers at https://github.com/WebBluetoothCG/registries/blob/master/gatt_assigned_services.txt. The UA should re-fetch this file periodically, but it's unspecified how often. The GATT assigned characteristics are the result of parsing the GATT assigned numbers at https://github.com/WebBluetoothCG/registries/blob/master/gatt_assigned_characteristics.txt. The UA should re-fetch this file periodically, but it's unspecified how often. The GATT assigned descriptors are the result of parsing the GATT assigned numbers at https://github.com/WebBluetoothCG/registries/blob/master/gatt_assigned_descriptors.txt. The UA should re-fetch this file periodically, but it's unspecified how often. # Advertising Data Filter # {#advertising-data-filter} An advertising data filter represents a way to match against manufacturer or service data.
To parse an advertising data filter from a string |input|, perform the following steps: 1. Let |words| be |input| strictly split on `/`. 1. If the length of |words| is not equal to `2`, return an error and abort these steps. 1. If the length of |words|[0] is not equal to the length of |words|[1], return an error and abort these steps. 1. Let |prefixData| be |words|[0]. 1. Let |prefixMask| be |words|[1]. 1. If |prefixData| or |prefixMask| is not a sequence of ascii lower hex digit, return an error. 1. Let |prefixIndex| be `0`. 1. let |dataList| be an empty list. 1. let |maskList| be an empty list. 1. While |prefixIndex| is less than the length of |prefixData|, do the following sub-steps: 1. Let |data| be the result of interpreting the characters at index |prefixIndex| and |prefixIndex| + 1 of |prefixData| as a hexadecimal number. 1. Let |mask| be the result of interpreting the characters at index |prefixIndex| and |prefixIndex| + 1 of |prefixMask| as a hexadecimal number. 1. Append |data| to |dataList|. 1. Append |mask| to |maskList|. 1. Set |prefixIndex| to `|prefixIndex| + 2`. 1. Let |result| be a new {{BluetoothDataFilterInit}} dictionary. 1. Set |result|[{{BluetoothDataFilterInit/dataPrefix}}] to an {{Uint8Array}} constructed with |dataList|. 1. Set |result|[{{BluetoothDataFilterInit/mask}}] to an {{Uint8Array}} constructed with |maskList|. 1. Return |result|.
# The Blocklist # {#the-blocklist} This specification relies on blocklist files in the https://github.com/WebBluetoothCG/registries repository to restrict the set of GATT attributes and manufacturer data a website can access. A valid company identifier string is a seqeunce of ascii lower hex digit that its length is bigger than `0` and less than `5`. The official list of company identifies can be found on Bluetooth Assigned Numbers website.
The result of parsing the manufacturer data blocklist at a URL url is a map from Company Identifier Code to a list of {{BluetoothDataFilterInit}}, or an error, produced by the following algorithm: 1. Fetch |url|, and let |contents| be its body, decoded as UTF-8. 1. Let |lines| be the result of invoking {{String/split(separator, limit)}} on |contents| with separator `'\n'`. 1. Let |result| be an empty map. 1. For each |line| in |lines|, do the following sub-steps: 1. If |line| is empty or its first character is `'#'`, continue to the next line. 1. Let |regExp| be a {{RegExp}} constructed with 'manufacturer\ ([0-9a-f]+)\ ([0-9a-f]+\/[0-9a-f]+)'. 1. Let |matchResult| be the result of invoking |regExp|.{{RegExp/exec(string)}} on |line|, or return an error if |matchResult| is `null` or the length of |matchResult| is not equal to `3`. 1. Let |companyIdentifierStr| be |matchResult|[1] if |matchResult|[1] is a valid company identifier string, otherwise return an error. 1. Let |companyIdentifier| be the result of interpreting |companyIdentifierStr| as a hexadecimal number. 1. Let |dataPrefixStr| be |matchResult|[2]. 1. If |companyIdentifier| is not in |result|, set |result|[|companyIdentifier|] to be an empty list. 1. Let |dataFilter| be the result of parsing an advertising data filter at |dataPrefixStr| if the result is not an error, otherwise return an error. 1. Append |dataFilter| to |result|[|companyIdentifier|]. 1. Return |result|.
The result of parsing the gatt blocklist at a URL url is a map from valid UUIDs to tokens, or an error, produced by the following algorithm: 1. Fetch url, and let contents be its body, decoded as UTF-8. 1. Let lines be contents split on '\n'. 1. Let result be an empty map. 1. For each line in lines, do the following sub-steps: 1. If line is empty or its first character is '#', continue to the next line. 1. If line consists of just a valid UUID, let uuid be that UUID and let token be "exclude". 1. 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. 1. Otherwise, return an error and abort these steps. 1. If uuid is already in result, return an error and abort these steps. 1. Add a mapping in result from uuid to token. 1. Return result.
The GATT blocklist is the result of parsing the gatt blocklist at https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt. The Manufacturer Data blocklist is the result of parsing the manufacturer data blocklist at https://github.com/WebBluetoothCG/registries/blob/master/manufacturer_data_blocklist.txt. The UA should re-fetch the blocklist periodically, but it's unspecified how often. A UUID is blocklisted if either the GATT blocklist's value is an error, or the UUID maps to "exclude" in the GATT blocklist. A UUID is blocklisted for reads if either the GATT blocklist's value is an error, or the UUID maps to either "exclude" or "exclude-reads" in the GATT blocklist. A UUID is blocklisted for writes if either the GATT blocklist's value is an error, or the UUID maps to either "exclude" or "exclude-writes" in the GATT blocklist.
A manufacturer data |manufacturerData| is a blocklisted manufacturer data if the following steps return `blocked`: 1. If the Manufacturer Data blocklist's value is an error, return `blocked`. 1. Let |manufacturerBlocklist| be the Manufacturer Data blocklist's value. 1. Let |companyIdentifier| be the company identifier of |manufacturerData|. 1. If |companyIdentifier| is not in |manufacturerBlocklist|, return `unblocked`. 1. For each |dataFilter| in |manufacturerBlocklist|[|companyIdentifier|], do the following sub-steps: 1. If the advertising data of |manufacturerData| matches |dataFilter|, return `blocked`. 1. Return `unblocked`.
A manufacturer data filter |manufacturerDataFilter| is a blocklisted manufacturer data filter if the following steps return `blocked`: 1. If the Manufacturer Data blocklist's value is an error, return `blocked`. 1. Let |manufacturerBlocklist| be the Manufacturer Data blocklist's value. 1. Let |companyIdentifier| be |manufacturerDataFilter|["{{BluetoothManufacturerDataFilterInit/companyIdentifier}}"]. 1. If |companyIdentifier| is not in |manufacturerBlocklist|, return `unblocked`. 1. For each |dataFilter| in |manufacturerBlocklist|[|companyIdentifier|], do the following sub-steps: 1. If |manufacturerDataFilter| is a strict subset of |dataFilter|, return `blocked`. 1. Return `unblocked`.
# Extensions to the Navigator Interface # {#navigator-extensions} [SecureContext] partial interface Navigator { [SameObject] readonly attribute Bluetooth bluetooth; }; Each {{Navigator}} has an associated `Bluetooth`, which is a {{Bluetooth}} object. Upon creation of the {{Navigator}} object, its associated `Bluetooth` must be set to a new {{Bluetooth}} object created in the {{Navigator}} object's [=relevant realm=]. {{Navigator}}'s bluetooth getter steps are to return [=this=]'s associated `Bluetooth`. # Integrations # {#integrations} ## Permissions Policy ## {#permissions-policy} This specification defines a policy-controlled feature, identified by the token "bluetooth", that controls whether the methods exposed by the {{Navigator/bluetooth}} attribute on the {{Navigator}} object may be used. The default allowlist for this feature is ["self"]. # Automated testing # {#automated-testing} For the purposes of user-agent automation and application testing, this document defines extensions to the [[WebDriver-BiDi]] specification. The Web Bluetooth API and its extension specifications pose a challenge to test authors, as fully exercising those interfaces requires physical hardware devices that respond in predictable ways. To address this challenge this document defines a number of WebDriver-BiDi extension commands that allow defining and controlling simulated peripherals and advertisements that behave like physical device peripherals and their advertisements. These simulated peripherals and advertisements represent devices with particular properties and whose readings can be entirely defined by users. Each [=navigable/top-level traversable=] may have a simulated Bluetooth adapter, that is a software defined Bluetooth adapter that has a set of discovered simulated Bluetooth devices and can assume roles like Central. Each simulated Bluetooth adapter has a simulated Bluetooth device mapping, which is an ordered map of Bluetooth address {{strings}} to simulated Bluetooth devices. Each simulated Bluetooth adapter has an adapter state that is a string enumeration describing the current state of the adapter. The possible enumeration values are: * "powered-on" * "powered-off" * "absent" Each simulated Bluetooth adapter has a low-energy supported state that is a boolean describing if the adapter supports Bluetooth Low Energy. A simulated Bluetooth device is a software defined [=Bluetooth device=] that behaves like a physical device, may be attached to a simulated Bluetooth adapter, may have associated properties like Manufacturer Specific Data and Service UUIDs, and has a simulated GATT service mapping, which is an ordered map of Bluetooth UUID strings to simulated GATT services. A simulated GATT service is a software defined [=Service=] that belongs to a simulated Bluetooth device, has a property of UUID, is known-present in the Bluetooth cache. and has a simulated GATT characteristic mapping, which is an ordered map of Bluetooth UUID strings to simulated GATT characteristics. A simulated GATT characteristic is a software defined [=Characteristic=] that belongs to a simulated GATT service, has a property of UUID, a property of Characteristic Properties, is known-present in the Bluetooth cache, and has a simulated GATT descriptor mapping, which is an ordered map of Bluetooth UUID strings to simulated GATT descriptors. Simulated GATT characteristic properties are software defined [=Characteristic Properties=] that belong to a simulated GATT characteristic and are known-present in the Bluetooth cache. A simulated GATT descriptor is a software defined [=Descriptor=] that belongs to a simulated GATT characteristic, has a property of UUID, and is known-present in the Bluetooth cache. Issue: CDDL snippetes use the "text" type instead of "browsingContext.BrowsingContext" to allow indepedent programmatic processing of CDDL snippets. Currently, other modules cannot be referenced. ## Definitions ## {#bluetooth-bidi-definitions}
bluetooth.BluetoothUuid = text;
bluetooth.BluetoothManufacturerData = { key: uint, data: tstr };
bluetooth.CharacteristicProperties = {
  ? broadcast: bool,
  ? read: bool,
  ? writeWithoutResponse: bool,
  ? write: bool,
  ? notify: bool,
  ? indicate: bool,
  ? authenticatedSignedWrites: bool,
  ? extendedProperties: bool
}
{^bluetooth.BluetoothManufacturerData/key^}
is the Company Identifier Code.
{^bluetooth.BluetoothManufacturerData/data^}
is the manufacturer data [=byte sequence=], base64 encoded.
## The bluetooth module ## {#bluetooth-module} The bluetooth module contains commands for managing the remote end Bluetooth behavior. ### Types ### {#bidi-types} #### The bluetooth.RequestDevice Type #### {#bluetooth-requestdevice-type}
bluetooth.RequestDevice = text
A bluetooth.RequestDevice is an identifier for a single device in a request device prompt. A device prompt is a [=tuple=] consisting of a device prompt id (a string) and a set of devices which is a [=/set=] of {{BluetoothDevice}} objects. It represents a prompt which allows a user to [=prompt the user to choose|choose=] a [=Bluetooth device=]. #### The bluetooth.RequestDeviceInfo Type #### {#bluetooth-requestdeviceinfo-type}
bluetooth.RequestDeviceInfo = {
   id: bluetooth.RequestDevice,
   name: text / null,
}
A bluetooth.RequestDeviceInfo represents a single device in a request device prompt.
To serialize a device given a {{BluetoothDevice}} |device|: 1. Let |id| be |device|.{{BluetoothDevice/id}}. 1. Let |name| be |device|.{{BluetoothDevice/name}}. 1. Return a [=map=] matching the {^bluetooth.RequestDeviceInfo^} production, with {^bluetooth.RequestDeviceInfo/"id"^} set to |id| and {^bluetooth.RequestDeviceInfo/"name"^} set to |name|.
#### The bluetooth.RequestDevicePrompt Type #### {#bluetooth-requestdeviceprompt-type}
bluetooth.RequestDevicePrompt = text
A bluetooth.RequestDevicePrompt is an identifier for a single prompt. A remote end has a map of navigables to device prompts which is a [=/map=] whose keys are [=navigable ids=] and values are [=device prompts=].
To get a prompt given |navigableId| and |promptId|: 1. Let |promptMap| be the [=map of navigables to device prompts=]. 1. If |promptMap|[|navigableId|] does not [=map/exist=]: 1. Return [=error=] with [=error code=] [=no such prompt=]. 1. Let |prompt| be [=map of navigables to device prompts=][|navigableId|]. 1. If |prompt|'s [=device prompt id=] is not |promptId|: 1. Return [=error=] with [=error code=] [=no such prompt=]. 1. Return [=success=] with data |prompt|.
To match a device in prompt given [=device prompt=] |prompt| and |deviceId|: 1. For each |device| in |prompt|'s [=set of devices=]: 1. If |device|.{{BluetoothDevice/id}} is |deviceId|, return [=success=] with data |device|. 1. Otherwise: 1. Return [=error=] with [=error code=] [=no such device=].
To serialize prompt devices given [=device prompt=] |prompt|: 1. Let |devices| be an empty [=/list=]. 1. For each |device| in |prompt|'s [=set of devices=]. 1. [=list/Append=] the result of [=serialize a device|serializing=] |device| to |devices|. 1. Return |devices|.
#### The bluetooth.ScanRecord Type #### {#bluetooth-scanrecord-type}

bluetooth.ScanRecord = {
  ? name: text,
  ? uuids: [ * bluetooth.BluetoothUuid ],
  ? appearance: number,
  ? manufacturerData: [ * bluetooth.BluetoothManufacturerData ],
}
A `bluetooth.ScanRecord` represents data of the advertisement packet sent by a [=Bluetooth device=].
{^bluetooth.ScanRecord/name^}
is the [=Bluetooth device=]'s local name, or a prefix of it.
{^bluetooth.ScanRecord/uuids^}
lists the Service UUIDs that this scan record says the [=Bluetooth device=]'s GATT server supports.
{^bluetooth.ScanRecord/appearance^}
is an Appearance, one of the values defined by the {{gap.appearance}} characteristic.
{^bluetooth.ScanRecord/manufacturerData^}
list of BluetoothManufacturerData that maps {{unsigned short}} Company Identifier Codes to base64 encoded manufacturer data [=byte sequences=].
### Errors ### {#bidi-errors} This specification extends the set of [=error codes=] from [[WEBDRIVER-BIDI|WebDriver BiDi]] with the following additional codes:
no such device
Tried to reference an unknown {{BluetoothDevice}}.
no such prompt
Tried to reference an unknown [=device prompt=].
### Commands ### {#bidi-commands}
BluetoothCommand = (
  bluetooth.HandleRequestDevicePrompt //
  bluetooth.SimulateAdapter //
  bluetooth.DisableSimulation //
  bluetooth.SimulatePreconnectedPeripheral //
  bluetooth.SimulateAdvertisement //
  bluetooth.SimulateGattConnectionResponse //
  bluetooth.SimulateGattDisconnection //
  bluetooth.SimulateService //
  bluetooth.SimulateCharacteristic //
  bluetooth.SimulateCharacteristicResponse //
  bluetooth.SimulateDescriptor //
  bluetooth.SimulateDescriptorResponse
)
#### The {^bluetooth.handleRequestDevicePrompt^} Command #### {#bluetooth-handlerequestdeviceprompt-command}
bluetooth.HandleRequestDevicePrompt = (
   method: "bluetooth.handleRequestDevicePrompt",
   params: bluetooth.HandleRequestDevicePromptParameters,
)

bluetooth.HandleRequestDevicePromptParameters = {
   context: text,
   prompt: bluetooth.RequestDevicePrompt,
   (
       bluetooth.HandleRequestDevicePromptAcceptParameters //
       bluetooth.HandleRequestDevicePromptCancelParameters
   )
}

bluetooth.HandleRequestDevicePromptAcceptParameters = (
   accept: true,
   device: bluetooth.RequestDevice,
)

bluetooth.HandleRequestDevicePromptCancelParameters = (
   accept: false,
)
The [=remote end steps=] with |params| are: 1. Let |contextId| be |params|[{^bluetooth.HandleRequestDevicePromptParameters/"context"^}]. 1. Let |promptId| be |params|[{^bluetooth.HandleRequestDevicePromptParameters/"prompt"^}]. 1. Let |prompt| be the result of [=trying=] to [=get a prompt=] with |contextId| and |promptId|. 1. Let |accept| be the value of |params|[{^bluetooth.HandleRequestDevicePromptAcceptParameters/"accept"^}]. 1. If |accept| is true: 1. Let |deviceId| be the value of |params|[{^bluetooth.HandleRequestDevicePromptAcceptParameters/"device"^}]. 1. Let |device| be the result of [=trying=] to [=match a device in prompt=] given |prompt| and |deviceId|. 1. Acknowledge |prompt| with |device|. 1. Otherwise: 1. Dismiss |prompt|. 1. Return [=success=] with data `null`.
A [=local end=] could dismiss a prompt by sending the following message:
{
  "method": "bluetooth.handleRequestDevicePrompt",
  "params": {
    "context": "cxt-d03fdd81",
    "prompt": "pmt-e0a234b",
    "accept": true,
    "device": "dvc-9b3b872"
  }
}
#### The {^bluetooth.simulateAdapter^} Command #### {#bluetooth-simulateAdapter-command}
bluetooth.SimulateAdapter = (
   method: "bluetooth.simulateAdapter",
   params: bluetooth.SimulateAdapterParameters,
)

bluetooth.SimulateAdapterParameters = {
   context: text,
   ? leSupported: bool,
   state: "absent" / "powered-off" / "powered-on"
}
The [=remote end steps=] with command parameters |params| are: 1. Let |contextId| be |params|[{^bluetooth.SimulateAdapterParameters/"context"^}]. 1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. If |navigable| is not a [=navigable/top-level traversable=], return [=error=] with [=error code=] [=invalid argument=]. 1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is empty, run the following steps: 1. If |params|[{^bluetooth.SimulateAdapterParameters/"leSupported"^}] does not [=map/exist=], set |params|[{^bluetooth.SimulateAdapterParameters/"leSupported"^}] to `true`. 1. Let |simulatedBluetoothAdapter| be a new [=simulated Bluetooth adapter=]. 1. Set |simulatedBluetoothAdapter|'s LE supported state to |params|[{^bluetooth.SimulateAdapterParameters/"leSupported"^}]. 1. Set |simulatedBluetoothAdapter|'s adapter state to |params|[{^bluetooth.SimulateAdapterParameters/"state"^}]. 1. Set |navigable|'s simulated Bluetooth adapter to |simulatedBluetoothAdapter|. 1. Return [=success=] with data `null`. 1. If |simulatedBluetoothAdapter| is not empty, run the following steps: 1. If |params|[{^bluetooth.SimulateAdapterParameters/"leSupported"^}] [=map/exists=], return [=error=] with [=error code=] [=invalid argument=]. 1. Set |simulatedBluetoothAdapter|'s adapter state to |params|[{^bluetooth.SimulateAdapterParameters/"state"^}]. 1. Return [=success=] with data `null`.
A [=local end=] could simulate an adapter that supports Bluetooth Low Energy by sending the following message:
{
  "method": "bluetooth.simulateAdapter",
  "params": {
    "context": "cxt-d03fdd81",
    "leSupported": true,
    "state": "powered-on",
  }
}
A [=local end=] could update the adapter state of an existing adapter by sending the following message:
{
  "method": "bluetooth.simulateAdapter",
  "params": {
    "context": "cxt-d03fdd81",
    "state": "powered-off",
  }
}
#### The {^bluetooth.disableSimulation^} Command #### {#bluetooth-disableSimulation-command}
bluetooth.DisableSimulation = (
   method: "bluetooth.disableSimulation",
   params: bluetooth.DisableSimulationParameters,
)

bluetooth.DisableSimulationParameters = {
   context: text
}
The [=remote end steps=] with command parameters |params| are: 1. Let |contextId| be |params|[{^bluetooth.DisableSimulationParameters/"context"^}]. 1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. If |navigable| is not a [=navigable/top-level traversable=], return [=error=] with [=error code=] [=invalid argument=]. 1. Set |navigable|'s simulated Bluetooth adapter to empty. 1. Return [=success=] with data `null`.
A [=local end=] could disable the existing simulation by sending the following message:
{
  "method": "bluetooth.disableSimulation",
  "params": {
    "context": "cxt-d03fdd81"
  }
}
#### The {^bluetooth.simulatePreconnectedPeripheral^} Command #### {#bluetooth-simulateconnectedperipheral-command}
bluetooth.SimulatePreconnectedPeripheral = (
   method: "bluetooth.simulatePreconnectedPeripheral",
   params: bluetooth.SimulatePreconnectedPeripheralParameters,
)

bluetooth.SimulatePreconnectedPeripheralParameters = {
   context: text,
   address: text,
   name: text,
   manufacturerData: [ * bluetooth.BluetoothManufacturerData ],
   knownServiceUuids: [ * bluetooth.BluetoothUuid ]
}
The [=remote end steps=] with command parameters |params| are: 1. Let |contextId| be |params|[{^bluetooth.SimulatePreconnectedPeripheralParameters/"context"^}]. 1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. If |navigable| is not a [=navigable/top-level traversable=], return [=error=] with [=error code=] [=invalid argument=]. 1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |deviceAddress| be |params|[{^bluetooth.SimulatePreconnectedPeripheralParameters/"address"^}]. 1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. 1. If |deviceMapping|[|deviceAddress|] [=map/exists=], return [=error=] with [=error code=] [=invalid argument=]. 1. Let |simulatedBluetoothDevice| be a new [=simulated Bluetooth device=]. 1. Set |simulatedBluetoothDevice|'s name to |params|[{^bluetooth.SimulatePreconnectedPeripheralParameters/"name"^}]. 1. Set |simulatedBluetoothDevice|'s address to |params|[{^bluetooth.SimulatePreconnectedPeripheralParameters/"address"^}]. 1. Set |simulatedBluetoothDevice|'s manufacturer specific data to the output of [=forgiving-base64 decode=] performed on |params|[{^bluetooth.SimulatePreconnectedPeripheralParameters/"manufacturerData"^}]. 1. Set |simulatedBluetoothDevice|'s service UUIDs to |params|[{^bluetooth.SimulatePreconnectedPeripheralParameters/"knownServiceUuids"^}]. 1. Set |deviceMapping|[|deviceAddress|] to |simulatedBluetoothDevice|. 1. Return [=success=] with data `null`.
A [=local end=] could simulate a preconnected peripheral by sending the following message:
{
  "method": "bluetooth.simulatePreconnectedPeripheral",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "name": "Some Device",
    "manufacturerData": [ { key: 17, data: "AP8BAX8=" } ],
    "knownServiceUuids": [
      "12345678-1234-5678-9abc-def123456789",
    ],
  }
}
#### The {^bluetooth.simulateAdvertisement^} Command #### {#bluetooth-simulateadvertisement-command}
bluetooth.SimulateAdvertisement = (
   method: "bluetooth.simulateAdvertisement",
   params: bluetooth.SimulateAdvertisementParameters,
)

bluetooth.SimulateAdvertisementParameters = {
   context: text,
   scanEntry: bluetooth.SimulateAdvertisementScanEntryParameters
}

bluetooth.SimulateAdvertisementScanEntryParameters = {
   deviceAddress: text,
   rssi: number,
   scanRecord: bluetooth.ScanRecord
}

The [=remote end steps=] with command parameters |params| are: 1. Let |contextId| be |params|[{^bluetooth.SimulateAdvertisementParameters/"context"^}]. 1. Let |topLevelNavigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. If |topLevelNavigable| is not a [=navigable/top-level traversable=], return [=error=] with [=error code=] [=invalid argument=]. 1. Let |scanEntry| be |params|[{^bluetooth.SimulateAdvertisementParameters/"scanEntry"^}]. 1. Let |deviceAddress| be |scanEntry|[{^bluetooth.SimulateAdvertisementScanEntryParameters/"deviceAddress"^}]. 1. Let |simulatedBluetoothAdapter| be |topLevelNavigable|'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. 1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. Otherwise, let |simulatedDevice| be a new simulated Bluetooth device with |deviceAddress| and set |deviceMapping|[|deviceAddress|] to |simulatedDevice|. 1. If |topLevelNavigable| is currently executing the [=scan for devices=] algorithm, insert simulatedDevice into the simulatedBluetoothDevices variable within that algorithm. Issue: Inserting data into variables from another algorithm is not well defined. The scan for devices algorithm needs to define asynchronous device discovery in order to match implementations. 1. Let |navigables| be the inclusive descendant navigables of |topLevelNavigable|'s active document. 1. For each |navigable| of |navigables|: 1. Let |document| be |navigable|'s active document. 1. Queue a task on |document|'s relevant settings object's responsible event loop to do the following sub-steps: 1. Let |simulatedDeviceInstance| be the result of get the BluetoothDevice representing |simulatedDevice| inside |navigable|'s active window's associated Navigator's [=associated Bluetooth=]. 1. If |simulatedDeviceInstance|.{{[[watchAdvertisementsState]]}} is `not-watching`, abort these sub-steps. 1. Fire an `advertisementreceived` event for the advertising event represented by |scanEntry|[{^bluetooth.SimulateAdvertisementScanEntryParameters/"scanRecord"^}], at |simulatedDeviceInstance|. 1. Return [=success=] with data `null`.
A [=local end=] could simulate a device advertisement by sending the following message:
{
  "method": "bluetooth.simulateAdvertisement",
  "params": {
    "context": "cxt-d03fdd81",
    "scanEntry": {
      "deviceAddress": "08:08:08:08:08:08",
      "rssi": -10,
      "scanRecord": {
        "name": "Heart Rate",
        "uuids": ["0000180d-0000-1000-8000-00805f9b34fb"],
        "manufacturerData": [ { key: 17, data: "AP8BAX8=" } ],
        "appearance": 1,
        "txPower": 1
      }
    }
  }
}
#### The {^bluetooth.simulateGattConnectionResponse^} Command #### {#bluetooth-simulategattconnectionresponse-command}
bluetooth.SimulateGattConnectionResponse = (
   method: "bluetooth.simulateGattConnectionResponse",
   params: bluetooth.SimulateGattConnectionResponseParameters,
)

bluetooth.SimulateGattConnectionResponseParameters = {
   context: text,
   address: text,
   code: uint
}

The [=remote end steps=] with command parameters |params| are: 1. Let |contextId| be |params|[{^bluetooth.SimulateGattConnectionResponseParameters/"context"^}]. 1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. Let |deviceAddress| be |params|[{^bluetooth.SimulateGattConnectionResponseParameters/"address"^}]. 1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. 1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |simulatedDeviceInstance| be the result of get the BluetoothDevice representing |simulatedDevice| inside |navigable|'s active window's associated Navigator's [=associated Bluetooth=]. 1. If |simulatedDeviceInstance|.{{[[gatt]]}}.{{[[automatedGATTConnectionResponse]]}} is `"expected"`, set |simulatedDeviceInstance|.{{[[gatt]]}}.{{[[automatedGATTConnectionResponse]]}} to |params|[{^bluetooth.SimulateGattConnectionResponseParameters/"code"^}]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
A [=local end=] could simulate a device gatt connection response of success (error code `0x00` according to List of Error Codes) by sending the following message:
{
  "method": "bluetooth.simulateGattConnectionResponse",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "code": 0
  }
}
#### The {^bluetooth.simulateGattDisconnection^} Command #### {#bluetooth-simulategattdisconnection-command}
bluetooth.SimulateGattDisconnection = (
   method: "bluetooth.simulateGattDisconnection",
   params: bluetooth.SimulateGattDisconnectionParameters,
)

bluetooth.SimulateGattDisconnectionParameters = {
   context: text,
   address: text,
}

The [=remote end steps=] with command parameters |params| are: 1. Let |contextId| be |params|[{^bluetooth.SimulateGattDisconnectionParameters/"context"^}]. 1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. Let |deviceAddress| be |params|[{^bluetooth.SimulateGattDisconnectionParameters/"address"^}]. 1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. 1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |simulatedDeviceInstance| be the result of get the BluetoothDevice representing |simulatedDevice| inside |navigable|'s active window's associated Navigator's [=associated Bluetooth=]. 1. If |simulatedDeviceInstance|.{{[[gatt]]}}.{{[[automatedGATTConnectionResponse]]}} is `"expected"`, set |simulatedDeviceInstance|.{{[[gatt]]}}.{{[[automatedGATTConnectionResponse]]}} to `0x15`.
`0x15` represents `"Remote Device Terminated Connection due to Power Off"` according to the List of Error Codes. This simulates a scenario where the Bluetooth device is not able to respond to a GATT connection attempt.
1. Otherwise, clean up the disconnected device |simulatedDeviceInstance|.
A [=local end=] could simulate device GATT disconnection by sending the following message:
{
  "method": "bluetooth.simulateGattDisconnection",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
  }
}
#### The {^bluetooth.simulateService^} Command #### {#bluetooth-simulateservice-command}
bluetooth.SimulateService = (
   method: "bluetooth.simulateService",
   params: bluetooth.SimulateServiceParameters,
)

bluetooth.SimulateServiceParameters = {
   context: text,
   address: text,
   uuid: bluetooth.BluetoothUuid,
   type: "add" / "remove",
}
The [=remote end steps=] with command parameters |params| are: 1. Let |contextId| be |params|[{^bluetooth.SimulateServiceParameters/"context"^}]. 1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. Let |deviceAddress| be |params|[{^bluetooth.SimulateServiceParameters/"address"^}]. 1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. 1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |simulatedDeviceInstance| be the result of get the BluetoothDevice representing |simulatedDevice| inside |navigable|'s active window's associated Navigator's [=associated Bluetooth=]. 1. Let |serviceMapping| be |simulatedDevice|'s simulated GATT service mapping. 1. Let |uuid| be |params|[{^bluetooth.SimulateServiceParameters/"uuid"^}]. 1. If |params|[{^bluetooth.SimulateServiceParameters/"type"^}] is {^bluetooth.SimulateServiceParameters/type/"add"^}: 1. If |serviceMapping|[|uuid|] [=map/exists=], return [=error=] with [=error code=] [=invalid element state=]. 1. Let |simulatedGattService| be a new simulated GATT service. 1. Set |simulatedGattService|'s UUID to |uuid|. 1. Set |serviceMapping|[|uuid|] to |simulatedGattService|. 1. Create a BluetoothRemoteGATTService representing |simulatedGattService| and add a mapping from |simulatedGattService| to the resulting {{Promise}} in |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. 1. Return [=success=] with data `null`. 1. If |params|[{^bluetooth.SimulateServiceParameters/"type"^}] is {^bluetooth.SimulateServiceParameters/type/"remove"^}: 1. If |serviceMapping|[|uuid|] [=map/exists=], let |simulatedGattService| be |serviceMapping|[|uuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. Remove |simulatedGattService| from |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. 1. Remove |uuid| from |serviceMapping|. 1. Return [=success=] with data `null`. 1. Return [=error=] with [=error code=] [=invalid argument=].
A [=local end=] could simulate adding a GATT service by sending the following message:
{
  "method": "bluetooth.simulateService",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "uuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "type": "add"
  }
}
A [=local end=] could simulate removing a GATT service by sending the following message:
{
  "method": "bluetooth.simulateService",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "uuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "type": "remove"
  }
}
#### The {^bluetooth.simulateCharacteristic^} Command #### {#bluetooth-simulatecharacteristic-command}
bluetooth.SimulateCharacteristic = (
   method: "bluetooth.simulateCharacteristic",
   params: bluetooth.SimulateCharacteristicParameters,
)

bluetooth.SimulateCharacteristicParameters = {
   context: text,
   address: text,
   serviceUuid: bluetooth.BluetoothUuid,
   characteristicUuid: bluetooth.BluetoothUuid,
   ? characteristicProperties: bluetooth.CharacteristicProperties,
   type: "add" / "remove"
}
The [=remote end steps=] with command parameters |params| are: 1. Let |contextId| be |params|[{^bluetooth.SimulateCharacteristicParameters/"context"^}]. 1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. Let |deviceAddress| be |params|[{^bluetooth.SimulateCharacteristicParameters/"address"^}]. 1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. 1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |simulatedDeviceInstance| be the result of get the BluetoothDevice representing |simulatedDevice| inside |navigable|'s active window's associated Navigator's [=associated Bluetooth=]. 1. Let |serviceMapping| be |simulatedDevice|'s simulated GATT service mapping. 1. Let |serviceUuid| be |params|[{^bluetooth.SimulateCharacteristicParameters/"serviceUuid"^}]. 1. If |serviceMapping|[|serviceUuid|] [=map/exists=], let |simulatedService| be |serviceMapping|[|serviceUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |characteristicMapping| be |simulatedService|'s simulated GATT characteristic mapping. 1. Let |characteristicUuid| be |params|[{^bluetooth.SimulateCharacteristicParameters/"characteristicUuid"^}]. 1. If |params|[{^bluetooth.SimulateCharacteristicParameters/"type"^}] is {^bluetooth.SimulateCharacteristicParameters/type/"add"^}: 1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], return [=error=] with [=error code=] [=invalid element state=]. 1. If |params|[{^bluetooth.SimulateCharacteristicParameters/"characteristicProperties"^}] does not [=map/exist=], return [=error=] with [=error code=] [=invalid argument=]. 1. Let |simulatedGattCharacteristicProperties| be new simulated GATT characteristic properties and run the following steps: 1. Let |properties| be |params|[{^bluetooth.SimulateCharacteristicParameters/"characteristicProperties"^}]. 1. If |properties|[{^bluetooth.CharacteristicProperties/"broadcast"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Broadcast` bit if |properties|[{^bluetooth.CharacteristicProperties/"broadcast"^}] is `true`. 1. If |properties|[{^bluetooth.CharacteristicProperties/"read"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Read` bit if |properties|[{^bluetooth.CharacteristicProperties/"read"^}] is `true`. 1. If |properties|[{^bluetooth.CharacteristicProperties/"writeWithoutResponse"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Write Without Response` bit if |properties|[{^bluetooth.CharacteristicProperties/"writeWithoutResponse"^}] is `true`. 1. If |properties|[{^bluetooth.CharacteristicProperties/"write"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Write` bit if |properties|[{^bluetooth.CharacteristicProperties/"write"^}] is `true`. 1. If |properties|[{^bluetooth.CharacteristicProperties/"notify"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Notify` bit if |properties|[{^bluetooth.CharacteristicProperties/"notify"^}] is `true`. 1. If |properties|[{^bluetooth.CharacteristicProperties/"indicate"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Indicate` bit if |properties|[{^bluetooth.CharacteristicProperties/"indicate"^}] is `true`. 1. If |properties|[{^bluetooth.CharacteristicProperties/"authenticatedSignedWrites"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Authenticated Signed Writes` bit if |properties|[{^bluetooth.CharacteristicProperties/"authenticatedSignedWrites"^}] is `true`. 1. If |properties|[{^bluetooth.CharacteristicProperties/"extendedProperties"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Extended Properties` bit if |properties|[{^bluetooth.CharacteristicProperties/"extendedProperties"^}] is `true`. 1. Let |simulatedGattCharacteristic| be a new simulated GATT characteristic. 1. Set |simulatedGattCharacteristic|'s UUID to |characteristicUuid|. 1. Set |simulatedGattCharacteristic|'s Characteristic Properties to |simulatedGattCharacteristicProperties|. 1. Set |characteristicMapping|[|characteristicUuid|] to |simulatedGattCharacteristic|. 1. Create a BluetoothRemoteGATTCharacteristic representing |simulatedGattCharacteristic| and add a mapping from |simulatedGattCharacteristic| to the resulting {{Promise}} in |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. 1. Return [=success=] with data `null`. 1. If |params|[{^bluetooth.SimulateCharacteristicParameters/"type"^}] is {^bluetooth.SimulateCharacteristicParameters/type/"remove"^}: 1. If |params|[{^bluetooth.SimulateCharacteristicParameters/"characteristicProperties"^}] [=map/exists=], return [=error=] with [=error code=] [=invalid argument=]. 1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], let |simulatedGattCharacteristic| be |characteristicMapping|[|characteristicUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. Remove |simulatedGattCharacteristic| from |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. 1. Remove |characteristicUuid| from |characteristicMapping|. 1. Return [=success=] with data `null`. 1. Return [=error=] with [=error code=] [=invalid argument=].
A [=local end=] could simulate adding a GATT characteristic with read, write, and notify properties by sending the following message:
{
  "method": "bluetooth.simulateCharacteristic",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "characteristicProperties": {
      "read": true,
      "write": true,
      "notify": true
    },
    "type": "add"
  }
}
A [=local end=] could simulate removing a GATT characteristic by sending the following message:
{
  "method": "bluetooth.simulateCharacteristic",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "type": "remove"
  }
}
#### The {^bluetooth.simulateCharacteristicResponse^} Command #### {#bluetooth-simulatecharacteristicresponse-command}
bluetooth.SimulateCharacteristicResponse = (
   method: "bluetooth.simulateCharacteristicResponse",
   params: bluetooth.SimulateCharacteristicResponseParameters,
)

bluetooth.SimulateCharacteristicResponseParameters = {
   context: text,
   address: text,
   serviceUuid: bluetooth.BluetoothUuid,
   characteristicUuid: bluetooth.BluetoothUuid,
   type: "read" / "write" / "subscribe-to-notifications" / "unsubscribe-from-notifications",
   code: uint,
   ? data: [ * uint ]
}
The [=remote end steps=] with command parameters |params| are: 1. Let |contextId| be |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"context"^}]. 1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. Let |deviceAddress| be |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"address"^}]. 1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. 1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |serviceMapping| be |simulatedDevice|'s simulated GATT service mapping. 1. Let |serviceUuid| be |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"serviceUuid"^}]. 1. If |serviceMapping|[|serviceUuid|] [=map/exists=], let |simulatedService| be |serviceMapping|[|serviceUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |characteristicMapping| be |simulatedService|'s simulated GATT characteristic mapping. 1. Let |characteristicUuid| be |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"characteristicUuid"^}]. 1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], let |simulatedGattCharacteristic| be |characteristicMapping|[|characteristicUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. Let |simulatedDeviceInstance| be the result of get the BluetoothDevice representing |simulatedDevice| inside |navigable|'s active window's associated Navigator's [=associated Bluetooth=]. 1. Let |promise| be |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}[|simulatedGattCharacteristic|]. 1. Upon fulfillment of |promise| with |characteristic|, run the following steps: 1. If |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"type"^}] is {^bluetooth.SimulateCharacteristicResponseParameters/type/"read"^}, run the following steps: 1. If |characteristic|.{{[[automatedCharacteristicReadResponse]]}} is `expected`, set |characteristic|.{{[[automatedCharacteristicReadResponse]]}} to |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"code"^}] and |characteristic|.{{[[automatedCharacteristicReadResponseData]]}} to [=a copy of the bytes held=] by |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"data"^}]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. If |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"type"^}] is {^bluetooth.SimulateCharacteristicResponseParameters/type/"write"^}, run the following steps: 1. If |characteristic|.{{[[automatedCharacteristicWriteResponse]]}} is `expected`, set |characteristic|.{{[[automatedCharacteristicWriteResponse]]}} to |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"code"^}]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. If |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"type"^}] is {^bluetooth.SimulateCharacteristicResponseParameters/type/"subscribe-to-notifications"^}, run the following steps: 1. If |characteristic|.{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}} is `expected`, set |characteristic|.{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}} to |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"code"^}]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. If |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"type"^}] is {^bluetooth.SimulateCharacteristicResponseParameters/type/"unsubscribe-from-notifications"^}, run the following steps: 1. If |characteristic|.{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}} is `expected`, set |characteristic|.{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}} to |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"code"^}]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=].
A [=local end=] could simulate a response of success (error code `0x00` according to Error Response) with data for a characteristic read operation by sending the following message:
{
  "method": "bluetooth.simulateCharacteristicResponse",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "type": "read",
    "code": 0,
    "data": [1, 2]
  }
}
#### The {^bluetooth.simulateDescriptor^} Command #### {#bluetooth-simulatedescriptor-command}
bluetooth.SimulateDescriptor = (
   method: "bluetooth.simulateDescriptor",
   params: bluetooth.SimulateDescriptorParameters,
)

bluetooth.SimulateDescriptorParameters = {
   context: text,
   address: text,
   serviceUuid: bluetooth.BluetoothUuid,
   characteristicUuid: bluetooth.BluetoothUuid,
   descriptorUuid: bluetooth.BluetoothUuid,
   type: "add" / "remove"
}
1. Let |contextId| be |params|[{^bluetooth.SimulateDescriptorParameters/"context"^}]. 1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. Let |deviceAddress| be |params|[{^bluetooth.SimulateDescriptorParameters/"address"^}]. 1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. 1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |simulatedDeviceInstance| be the result of get the BluetoothDevice representing |simulatedDevice| inside |navigable|'s active window's associated Navigator's [=associated Bluetooth=]. 1. Let |serviceMapping| be |simulatedDevice|'s simulated GATT service mapping. 1. Let |serviceUuid| be |params|[{^bluetooth.SimulateDescriptorParameters/"serviceUuid"^}]. 1. If |serviceMapping|[|serviceUuid|] [=map/exists=], let |simulatedService| be |serviceMapping|[|serviceUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |characteristicMapping| be |simulatedService|'s simulated GATT characteristic mapping. 1. Let |characteristicUuid| be |params|[{^bluetooth.SimulateDescriptorParameters/"characteristicUuid"^}]. 1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], let |simulatedCharacteristic| be |characteristicMapping|[|characteristicUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |descriptorMapping| be |simulatedCharacteristic|'s simulated GATT descriptor mapping. 1. Let |descriptorUuid| be |params|[{^bluetooth.SimulateDescriptorParameters/"descriptorUuid"^}]. 1. If |params|[{^bluetooth.SimulateDescriptorParameters/"type"^}] is {^bluetooth.SimulateDescriptorParameters/type/"add"^}: 1. If |descriptorMapping|[|descriptorUuid|] [=map/exists=], return [=error=] with [=error code=] [=invalid element state=]. 1. Let |simulatedGattDescriptor| be a new simulated GATT descriptor. 1. Set |simulatedGattDescriptor|'s UUID to |descriptorUuid|. 1. Set |descriptorMapping|[|descriptorUuid|] to |simulatedGattDescriptor|. 1. Create a BluetoothRemoteGATTDescriptor representing |simulatedGattDescriptor| and add a mapping from |simulatedGattDescriptor| to the resulting {{Promise}} in |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. 1. Return [=success=] with data `null`. 1. If |params|[{^bluetooth.SimulateDescriptorParameters/"type"^}] is {^bluetooth.SimulateDescriptorParameters/type/"remove"^}: 1. If |descriptorMapping|[|descriptorUuid|] [=map/exists=], let |simulatedGattDescriptor| be |descriptorMapping|[|descriptorUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. Remove |simulatedGattDescriptor| from |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. 1. Remove |descriptorUuid| from |descriptorMapping|. 1. Return [=success=] with data `null`. 1. Return [=error=] with [=error code=] [=invalid argument=].
A [=local end=] could simulate adding a GATT descriptor by sending the following message:
{
  "method": "bluetooth.simulateDescriptor",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "descriptorUuid": "00002901-0000-1000-8000-00805f9b34fb",
    "type": "add"
  }
}
A [=local end=] could simulate removing a GATT descriptor by sending the following message:
{
  "method": "bluetooth.simulateDescriptor",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "descriptorUuid": "00002901-0000-1000-8000-00805f9b34fb",
    "type": "remove"
  }
}
#### The {^bluetooth.simulateDescriptorResponse^} Command #### {#bluetooth-simulatedescriptorresponse-command}
bluetooth.SimulateDescriptorResponse = (
   method: "bluetooth.simulateDescriptorResponse",
   params: bluetooth.SimulateDescriptorResponseParameters,
)

bluetooth.SimulateDescriptorResponseParameters = {
   context: text,
   address: text,
   serviceUuid: bluetooth.BluetoothUuid,
   characteristicUuid: bluetooth.BluetoothUuid,
   descriptorUuid: bluetooth.BluetoothUuid,
   type: "read" / "write",
   code: uint,
   ? data: [ * uint ]
}
1. Let |contextId| be |params|[{^bluetooth.SimulateDescriptorResponseParameters/"context"^}]. 1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. 1. Let |deviceAddress| be |params|[{^bluetooth.SimulateDescriptorResponseParameters/"address"^}]. 1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. 1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. 1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |serviceMapping| be |simulatedDevice|'s simulated GATT service mapping. 1. Let |serviceUuid| be |params|[{^bluetooth.SimulateDescriptorResponseParameters/"serviceUuid"^}]. 1. If |serviceMapping|[|serviceUuid|] [=map/exists=], let |simulatedService| be |serviceMapping|[|serviceUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. 1. Let |characteristicMapping| be |simulatedService|'s simulated GATT characteristic mapping. 1. Let |characteristicUuid| be |params|[{^bluetooth.SimulateDescriptorResponseParameters/"characteristicUuid"^}]. 1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], let |simulatedCharacteristic| be |characteristicMapping|[|characteristicUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. Let |descriptorMapping| be |simulatedCharacteristic|'s simulated GATT descriptor mapping. 1. Let |descriptorUuid| be |params|[{^bluetooth.SimulateDescriptorResponseParameters/"descriptorUuid"^}]. 1. If |descriptorMapping|[|descriptorUuid|] [=map/exists=], let |simulatedDescriptor| be |descriptorMapping|[|descriptorUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. Let |simulatedDeviceInstance| be the result of get the BluetoothDevice representing |simulatedDevice| inside |navigable|'s active window's associated Navigator's [=associated Bluetooth=]. 1. Let |promise| be |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}[|simulatedDescriptor|]. 1. Upon fulfillment of |promise| with |descriptor|, run the following steps: 1. If |params|[{^bluetooth.SimulateDescriptorResponseParameters/"type"^}] is {^bluetooth.SimulateDescriptorResponseParameters/type/"read"^}, run the following steps: 1. If |descriptor|.{{[[automatedDescriptorReadResponse]]}} is `expected`, set |descriptor|.{{[[automatedDescriptorReadResponse]]}} to |params|[{^bluetooth.SimulateDescriptorResponseParameters/"code"^}] and |descriptor|.{{[[automatedDescriptorReadResponseData]]}} to [=a copy of the bytes held=] by |params|[{^bluetooth.SimulateDescriptorResponseParameters/"data"^}]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. If |params|[{^bluetooth.SimulateDescriptorResponseParameters/"type"^}] is {^bluetooth.SimulateDescriptorResponseParameters/type/"write"^}, run the following steps: 1. If |characteristic|.{{[[automatedDescriptorWriteResponse]]}} is `expected`, set |characteristic|.{{[[automatedDescriptorWriteResponse]]}} to |params|[{^bluetooth.SimulateDescriptorResponseParameters/"code"^}]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=].
A [=local end=] could simulate a response of success (error code `0x00` according to Error Response) with data for a descriptor read operation by sending the following message:
{
  "method": "bluetooth.simulateDescriptorResponse",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "descriptorUuid": "00002901-0000-1000-8000-00805f9b34fb",
    "type": "read",
    "code": 0,
    "data": [1, 2]
  }
}
### Events ### {#bidi-events}
BluetoothEvent = (
  bluetooth.RequestDevicePromptUpdated //
  bluetooth.GattConnectionAttempted
)
#### The bluetooth.requestDevicePromptUpdated Event #### {#bluetooth-requestdevicepromptupdated-event}
bluetooth.RequestDevicePromptUpdated = (
   method: "bluetooth.requestDevicePromptUpdated",
   params: bluetooth.RequestDevicePromptUpdatedParameters
)

bluetooth.RequestDevicePromptUpdatedParameters = {
   context: text,
   prompt: bluetooth.RequestDevicePrompt,
   devices: [* bluetooth.RequestDeviceInfo],
}
To trigger a prompt updated event given a [=navigable=] |navigable|, a string |promptId|, and a [=/set=] of [=Bluetooth devices=] |devices|: 1. Let |navigableId| be |navigable|'s [=navigable id=]. 1. Let |prompt| be the [=device prompt=] (|promptId|, |devices|). 1. Let |serialized devices| be the result of [=serialize prompt devices=] with |prompt|. 1. Set [=map of navigables to device prompts=][|navigableId|] to |prompt|. 1. Let |params| be a [=map=] matching the {^bluetooth.RequestDevicePromptUpdatedParameters^} production with the {^bluetooth.RequestDevicePromptUpdatedParameters/"context"^} field set to |navigableId|, the {^bluetooth.RequestDevicePromptUpdatedParameters/"prompt"^} field set to |promptId|, and the {^bluetooth.RequestDevicePromptUpdatedParameters/"devices"^} field set to |serialized devices|. 1. Let |body| be a [=map=] matching the {^bluetooth.RequestDevicePromptUpdated^} production, with the {^bluetooth.RequestDevicePromptUpdated/"params"^} field set to |params|. 1. Let |relatedNavigables| be a [=/set=] containing |navigable|. 1. For each |session| in the [=set of sessions for which an event is enabled=] given {^bluetooth.RequestDevicePromptUpdated/method/"bluetooth.requestDevicePromptUpdated"^} and |relatedNavigables|: 1. [=Emit an event=] with |session| and |body|.
#### The bluetooth.gattConnectionAttempted Event #### {#bluetooth-gattConnectionAttempted-event}
bluetooth.GattConnectionAttempted = (
   method: "bluetooth.gattConnectionAttempted",
   params: bluetooth.GattConnectionAttemptedParameters
)

bluetooth.GattConnectionAttemptedParameters = {
   context: text,
   address: text
}
To trigger a gatt connection attempted event given a [=navigable=] |navigable| and a {{BluetoothDevice}} |device|: 1. Let |navigableId| be |navigable|'s [=navigable id=]. 1. Let |params| be a [=map=] matching the {^bluetooth.GattConnectionAttemptedParameters^} production with the {^bluetooth.GattConnectionAttemptedParameters/"context"^} field set to |navigableId| and the {^bluetooth.GattConnectionAttemptedParameters/"address"^} field set to |device|.{{[[representedDevice]]}}'s address. 1. Let |body| be a [=map=] matching the {^bluetooth.GattConnectionAttempted^} production, with the {^bluetooth.GattConnectionAttempted/"params"^} field set to |params|. 1. Let |relatedNavigables| be a [=/set=] containing |navigable|. 1. For each |session| in the [=set of sessions for which an event is enabled=] given {^bluetooth.GattConnectionAttempted/method/"bluetooth.gattConnectionAttempted"^} and |relatedNavigables|: 1. [=Emit an event=] with |session| and |body|.
#### The bluetooth.characteristicEventGenerated Event #### {#bluetooth-characteristiceventgenerated-event}
bluetooth.CharacteristicEventGenerated = (
   method: "bluetooth.characteristicEventGenerated",
   params: bluetooth.CharacteristicEventGeneratedParameters
)

bluetooth.CharacteristicEventGeneratedParameters = {
  context: text,
  address: text,
  serviceUuid: bluetooth.BluetoothUuid,
  characteristicUuid: bluetooth.BluetoothUuid,
  type: "read" / "write-with-response" / "write-without-response" / "subscribe-to-notifications" / "unsubscribe-from-notifications",
  ? data: [ * uint ]
}
To trigger a simulated characteristic event given a [=navigable=] |navigable|, a {{BluetoothDevice}} |device|, a simulated GATT characteristic |characteristic|, string |type|, and an optional byte sequence |bytes|: 1. Let |navigableId| be |navigable|'s [=navigable id=]. 1. Let |params| be a [=map=] matching the {^bluetooth.CharacteristicEventGeneratedParameters^} production and run the following steps: 1. Set |params|[{^bluetooth.CharacteristicEventGeneratedParameters/"context"^}] to |navigableId|. 1. Set |params|[{^bluetooth.CharacteristicEventGeneratedParameters/"address"^}] to |device|.{{[[representedDevice]]}}'s address. 1. Let |service| be the simulated GATT service containing |characteristic|. 1. Set |params|[{^bluetooth.CharacteristicEventGeneratedParameters/"serviceUuid"^}] to |service|'s UUID. 1. Set |params|[{^bluetooth.CharacteristicEventGeneratedParameters/"characteristicUuid"^}] to |characteristic|'s UUID. 1. Set |params|[{^bluetooth.CharacteristicEventGeneratedParameters/"type"^}] to |type|. 1. If |type| is {^bluetooth.CharacteristicEventGeneratedParameters/type/"write-with-response"^} or {^bluetooth.CharacteristicEventGeneratedParameters/type/"write-without-response"^}, run the following steps: 1. Let |data| be an empty list. 1. For each |byte| in |bytes|: 1. Append |byte|'s [=byte/value=] to |data|. 1. Set |params|[{^bluetooth.CharacteristicEventGeneratedParameters/"data"^}] to |data|. 1. Let |body| be a [=map=] matching the {^bluetooth.CharacteristicEventGenerated^} production, with the {^bluetooth.CharacteristicEventGenerated/"params"^} field set to |params|. 1. Let |relatedNavigables| be a [=/set=] containing |navigable|. 1. For each |session| in the [=set of sessions for which an event is enabled=] given {^bluetooth.CharacteristicEventGenerated/method/"bluetooth.characteristicEventGenerated"^} and |relatedNavigables|: 1. [=Emit an event=] with |session| and |body|.
#### The bluetooth.descriptorEventGenerated Event #### {#bluetooth-descriptoreventgenerated-event}
bluetooth.DescriptorEventGenerated = (
   method: "bluetooth.descriptorEventGenerated",
   params: bluetooth.DescriptorEventGeneratedParameters
)

bluetooth.DescriptorEventGeneratedParameters = {
  context: text,
  address: text,
  serviceUuid: bluetooth.BluetoothUuid,
  characteristicUuid: bluetooth.BluetoothUuid,
  descriptorUuid: bluetooth.BluetoothUuid,
  type: "read" / "write",
  ? data: [ * uint ]
}
To trigger a simulated descriptor event given a [=navigable=] |navigable|, a {{BluetoothDevice}} |device|, a simulated GATT descriptor |descriptor|, string |type|, and an optional byte sequence |bytes|: 1. Let |navigableId| be |navigable|'s [=navigable id=]. 1. Let |params| be a [=map=] matching the {^bluetooth.DescriptorEventGeneratedParameters^} production and run the following steps: 1. Set |params|[{^bluetooth.DescriptorEventGeneratedParameters/"context"^}] to |navigableId|. 1. Set |params|[{^bluetooth.DescriptorEventGeneratedParameters/"address"^}] to |device|.{{[[representedDevice]]}}'s address. 1. Let |characteristic| be the simulated GATT characteristic containing |descriptor|. 1. Let |service| be the simulated GATT service containing |characteristic|. 1. Set |params|[{^bluetooth.DescriptorEventGeneratedParameters/"serviceUuid"^}] to |service|'s UUID. 1. Set |params|[{^bluetooth.DescriptorEventGeneratedParameters/"characteristicUuid"^}] to |characteristic|'s UUID. 1. Set |params|[{^bluetooth.DescriptorEventGeneratedParameters/"descriptorUuid"^}] to |descriptor|'s UUID. 1. Set |params|[{^bluetooth.DescriptorEventGeneratedParameters/"type"^}] to |type|. 1. If |type| is {^bluetooth.DescriptorEventGeneratedParameters/type/"write"^}, run the following steps: 1. Let |data| be an empty list. 1. For each |byte| in |bytes|: 1. Append |byte|'s [=byte/value=] to |data|. 1. Set |params|[{^bluetooth.DescriptorEventGeneratedParameters/"data"^}] to |data|. 1. Let |body| be a [=map=] matching the {^bluetooth.DescriptorEventGenerated^} production, with the {^bluetooth.DescriptorEventGenerated/"params"^} field set to |params|. 1. Let |relatedNavigables| be a [=/set=] containing |navigable|. 1. For each |session| in the [=set of sessions for which an event is enabled=] given {^bluetooth.DescriptorEventGenerated/method/"bluetooth.descriptorEventGenerated"^} and |relatedNavigables|: 1. [=Emit an event=] with |session| and |body|.
# Terminology and Conventions # {#terminology} This specification uses a few conventions and several terms from other specifications. This section lists those and links to their primary definitions. 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 {{Bluetooth/requestDevice()}} algorithm says to call {{Array.prototype.map}}.call(filter.services, {{BluetoothUUID/getService()|BluetoothUUID.getService}}), this MUST apply the {{Array.prototype.map}} algorithm defined in [[ECMAScript]] with filter.services as its this parameter and the algorithm defined in for {{BluetoothUUID/getService()|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 a {{FrozenArray}}'s contents. This applies to {{TypedArray}}s and {{DataView}}s wrapped around the {{ArrayBuffer}} too.
[[!BLUETOOTH42]]
  1. Architecture & Terminology Overview
    1. General Description
      1. Overview of Bluetooth Low Energy Operation (defines advertising events)
    2. Communication Topology and Operation
      1. Operational Procedures and Modes
        1. BR/EDR Procedures
          1. Inquiry (Discovering) Procedure
            1. Extended Inquiry Response
  2. Core System Package [BR/EDR Controller volume]
    1. Error Codes
      1. Overview of Error Codes
        1. List of Error Codes
    2. Host Controller Interface Functional Specification
      1. HCI Commands and Events
        1. Informational Parameters
          1. Read BD_ADDR Command
        2. Status Parameters
          1. Read RSSI Command
  3. Core System Package [Host volume]
    1. Service Discovery Protocol (SDP) Specification
      1. Overview
        1. Searching for Services
          1. UUID (defines UUID aliases and the algorithm to compute the 128-bit UUID represented by a UUID alias)
    2. Generic Access Profile
      1. Profile Overview
        1. Profile Roles
          1. Roles when Operating over an LE Physical Transport
            1. Broadcaster Role
            2. Observer Role
            3. Peripheral Role
            4. Central Role
      2. User Interface Aspects
        1. Representation of Bluetooth Parameters
          1. Bluetooth Device Name (the user-friendly name)
      3. Idle Mode Procedures — BR/EDR Physical Transport
        1. Device Discovery Procedure
        2. BR/EDR Bonding Procedure
      4. Operational Modes and Procedures — LE Physical Transport
        1. Broadcast Mode and Observation Procedure
          1. Observation Procedure
        2. Discovery Modes and Procedures
          1. General Discovery Procedure
          2. Name Discovery Procedure
        3. Connection Modes and Procedures
        4. Bonding Modes and Procedures
          1. LE Bonding Procedure
      5. Security Aspects — LE Physical Transport
        1. Privacy Feature
        2. Random Device Address
          1. Static Address
          2. Private address
            1. Resolvable Private Address Resolution Procedure
      6. Advertising Data and Scan Response Data Format (defines AD structure)
      7. Bluetooth Device Requirements
        1. Bluetooth Device Address (defines BD_ADDR)
          1. Bluetooth Device Address Types
            1. Public Bluetooth Address
      8. Definitions (defines bond)
    3. Attribute Protocol (ATT)
      1. Protocol Requirements
        1. Basic Concepts
          1. Attribute Type
          2. Attribute Handle
          3. Long Attribute Values
        2. Attribute Protocol Pdus
          1. Error Handling
            1. Error Response
    4. Generic Attribute Profile (GATT)
      1. Profile Overview
        1. Configurations and Roles (defines GATT Client and GATT Server)
        2. Profile Fundamentals, defines the ATT Bearer
        3. Attribute Protocol
          1. Attribute Caching
        4. GATT Profile Hierarchy
          1. Service
          2. Included Services
          3. Characteristic
      2. Service Interoperability Requirements
        1. Service Definition
        2. Characteristic Definition
          1. Characteristic Declaration
            1. Characteristic Properties
          2. Characteristic Descriptor Declarations
            1. Characteristic Extended Properties
            2. Client Characteristic Configuration
      3. GATT Feature Requirements — defines the GATT procedures.
        1. Server Configuration
          1. Exchange MTU
        2. Primary Service Discovery
          1. Discover All Primary Services
          2. Discover Primary Service by Service UUID
        3. Relationship Discovery
          1. Find Included Services
        4. Characteristic Discovery
          1. Discover All Characteristics of a Service
          2. Discover Characteristics by UUID
        5. Characteristic Descriptor Discovery
          1. Discover All Characteristic Descriptors
        6. Characteristic Value Read
        7. Characteristic Value Write
          1. Write Without Response
          2. Write Characteristic Value
        8. Characteristic Value Notification
        9. Characteristic Value Indications
        10. Characteristic Descriptors
          1. Read Characteristic Descriptors
          2. Read Long Characteristic Descriptors
          3. Write Characteristic Descriptors
          4. Write Long Characteristic Descriptors
        11. Procedure Timeouts
      4. GAP Interoperability Requirements
        1. BR/EDR GAP Interoperability Requirements
          1. Connection Establishment
        2. LE GAP Interoperability Requirements
          1. Connection Establishment
      5. Defined Generic Attribute Profile Service
        1. Service Changed
    5. Security Manager Specification
      1. Security Manager
        1. Security in Bluetooth Low Energy
          1. Definition of Keys and Values, defines the Identity Resolving Key (IRK)
  4. Core System Package [Low Energy Controller volume]
    1. Link Layer Specification
      1. General Description
        1. Device Address
          1. Public Device Address
          2. Random Device Address
            1. Static Device Address
      2. Air Interface Protocol
        1. Non-Connected States
          1. Scanning State
            1. Passive Scanning
[[!BLUETOOTH-SUPPLEMENT6]]
  1. Data Types Specification
    1. Data Types Definitions and Formats
      1. Service UUID Data Type
      2. Local Name Data Type
      3. Flags Data Type (defines the Discoverable Mode flags)
      4. Manufacturer Specific Data
      5. TX Power Level
      6. Service Data
      7. Appearance