diff --git a/index.bs b/index.bs index 0fc9eec..cf349c8 100644 --- a/index.bs +++ b/index.bs @@ -1578,7 +1578,7 @@ spec: permissions interface BluetoothDevice { readonly attribute DOMString id; readonly attribute DOMString? name; - readonly attribute BluetoothRemoteGATTServer gatt; + readonly attribute BluetoothRemoteGATTServer? gatt; readonly attribute FrozenArray<UUID> uuids; Promise<void> watchAdvertisements(); @@ -1605,7 +1605,8 @@ spec: permissions

- gatt provides a way to interact with this device's GATT server. + {{BluetoothDevice/gatt}} provides a way to interact with this device's GATT server + if the site has permission to use any of its services.

@@ -1645,6 +1646,17 @@ spec: permissions The Bluetooth device this object represents. + + \[[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]] `undefined` @@ -1735,12 +1747,6 @@ spec: permissions

  • Initialize result.watchingAdvertisements to `false`.
  • -
  • - Set result.gatt.{{BluetoothRemoteGATTServer/device}} to result. -
  • -
  • - Set result.gatt.{{BluetoothRemoteGATTServer/connected}} to `false`. -
  • Add device's advertised Service UUIDs @@ -1764,6 +1770,22 @@ spec: permissions
  • +
    +

    + Getting the gatt attribute + MUST perform the following steps: +

    +
      +
    1. + If this.{{[[allowedServices]]}} is an empty list, + return `null`. +
    2. +
    3. + Return this.{{[[gatt]]}}. +
    4. +
    +
    +

    Getting the uuids attribute MUST perform the following steps: diff --git a/index.html b/index.html index e965eb3..010c1a9 100644 --- a/index.html +++ b/index.html @@ -2483,7 +2483,7 @@

    interface BluetoothDevice { readonly attribute DOMString id; readonly attribute DOMString? name; - readonly attribute BluetoothRemoteGATTServer gatt; + readonly attribute BluetoothRemoteGATTServer? gatt; readonly attribute FrozenArray<UUID> uuids; Promise<void> watchAdvertisements(); @@ -2502,7 +2502,8 @@

    wants to expose that fact to script.

    name is the human-readable name of the device.

    -

    gatt provides a way to interact with this device’s GATT server.

    +

    gatt provides a way to interact with this device’s GATT server + if the site has permission to use any of its services.

    uuids lists the UUIDs of GATT services known to be on the device, that the current origin is allowed to access.

    @@ -2527,6 +2528,11 @@

    [[representedDevice]] undefined The Bluetooth device this object represents. + + [[gatt]] + a new BluetoothRemoteGATTServer instance with + its device attribute initialized to this and its connected attribute initialized to false. + Does not change. [[allowedServices]] undefined @@ -2574,8 +2580,6 @@

    If device has a partial or complete Bluetooth Device Name, set result.name to that string.
  • Initialize result.watchingAdvertisements to false. -
  • Set result.gatt.device to result. -
  • Set result.gatt.connected to false.
  • Add device’s advertised Service UUIDs to result@[[unfilteredUuids]].
  • If the Bluetooth cache contains @@ -2585,22 +2589,31 @@

  • Return the value in context@[[deviceInstanceMap]] whose key is the same device as device. +
    +

    Getting the gatt attribute + MUST perform the following steps:

    +
      +
    1. If this.[[allowedServices]] is an empty list, + return null. +
    2. Return this.[[gatt]]. +
    +

    Getting the uuids attribute MUST perform the following steps:

    1. - If [[cachedAllowedServices]] is not equal to [[allowedServices]] or [[cachedUnfilteredUuids]] is not equal to [[unfilteredUuids]], + If [[cachedAllowedServices]] is not equal to [[allowedServices]] or [[cachedUnfilteredUuids]] is not equal to [[unfilteredUuids]], perform the following sub-steps:
        -
      1. Set [[cachedAllowedServices]] to a copy of [[allowedServices]]. +
      2. Set [[cachedAllowedServices]] to a copy of [[allowedServices]].
      3. Set [[cachedUnfilteredUuids]] to a copy of [[unfilteredUuids]].
      4. Set [[filteredUuids]] to a new FrozenArray consisting of:
        -
        If [[allowedServices]] is "all" +
        If [[allowedServices]] is "all"
        the elements of [[unfilteredUuids]]
        Otherwise, -
        the intersection of [[unfilteredUuids]] and [[allowedServices]]. +
        the intersection of [[unfilteredUuids]] and [[allowedServices]].
    2. Return [[filteredUuids]]. @@ -2876,7 +2889,7 @@

      5.1. GATT Information Model

      -

      The GATT Profile Hierarchy describes how a GATT Server contains a hierarchy of Profiles, Primary Services, Included Services, Characteristics, and Descriptors.

      +

      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, @@ -2897,7 +2910,7 @@

      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, + 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, @@ -3035,11 +3048,11 @@

      5.2. BluetoothRemoteGATTServer

      -

      BluetoothRemoteGATTServer represents a GATT Server on a remote device.

      -
      interface BluetoothRemoteGATTServer {
      +     

      BluetoothRemoteGATTServer represents a GATT Server on a remote device.

      +
      interface BluetoothRemoteGATTServer {
         readonly attribute BluetoothDevice device;
         readonly attribute boolean connected;
      -  Promise<BluetoothRemoteGATTServer> connect();
      +  Promise<BluetoothRemoteGATTServer> connect();
         void disconnect();
         Promise<BluetoothRemoteGATTService> getPrimaryService(BluetoothServiceUUID service);
         Promise<sequence<BluetoothRemoteGATTService>>
      @@ -3047,21 +3060,21 @@ 

      -
      NOTE: BluetoothRemoteGATTServer attributes
      +
      NOTE: BluetoothRemoteGATTServer attributes

      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 example when there are other connected BluetoothRemoteGATTServer instances for other global objects.

      When no ECMAScript code can - observe an instance of BluetoothRemoteGATTServer server anymore, + observe an instance of BluetoothRemoteGATTServer server anymore, the UA SHOULD run server.disconnect(). Because BluetoothDevice instances are stored in navigator.bluetooth.[[deviceInstanceMap]], this can’t happen at least until navigation releases the global object or closing the tab or window destroys the browsing context. Disconnecting on garbage collection ensures that the UA doesn’t keep consuming resources on the remote device unnecessarily.

      -

      Instances of BluetoothRemoteGATTServer are created with the internal slots +

      Instances of BluetoothRemoteGATTServer are created with the internal slots described in the following table:

      @@ -3101,7 +3114,7 @@

      Set this.connected to false.
    3. Fire an event named gattserverdisconnected with its bubbles attribute initialized to true at this.device. -

      This event is not fired at the BluetoothRemoteGATTServer.

      +

      This event is not fired at the BluetoothRemoteGATTServer.

    4. Let device be this.device@[[representedDevice]].
    5. In parallel: if, for all BluetoothDevices deviceObj in the whole UA @@ -3109,9 +3122,9 @@

      device’s ATT Bearer.

      Algorithms need to fail if - their BluetoothRemoteGATTServer was disconnected while they were running, + 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. + 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:

        @@ -3141,18 +3154,18 @@

        The getPrimaryService(service) method, when invoked, MUST perform the following steps:

          -
        1. If this.device@[[allowedServices]] is not "all" and service is not in this.device@[[allowedServices]], +
        2. If this.device@[[allowedServices]] is not "all" and service is not in this.device@[[allowedServices]], return a promise rejected with a SecurityError and abort these steps. -
        3. Return GetGATTChildren(attribute=this.device,
          single=true,
          uuidCanonicalizer=BluetoothUUID.getService,
          uuid=service,
          allowedUuids=this.device@[[allowedServices]],
          child type="GATT Primary Service")
          +
        4. Return GetGATTChildren(attribute=this.device,
          single=true,
          uuidCanonicalizer=BluetoothUUID.getService,
          uuid=service,
          allowedUuids=this.device@[[allowedServices]],
          child type="GATT Primary Service")

        The getPrimaryServices(service) method, when invoked, MUST perform the following steps:

          -
        1. If this.device@[[allowedServices]] is not "all", +
        2. If this.device@[[allowedServices]] is not "all", and service is present - and not in this.device@[[allowedServices]], + and not in this.device@[[allowedServices]], return a promise rejected with a SecurityError and abort these steps. -
        3. Return GetGATTChildren(attribute=this.device@[[representedDevice]],
          single=false,
          uuidCanonicalizer=BluetoothUUID.getService,
          uuid=service,
          allowedUuids=this.device@[[allowedServices]],
          child type="GATT Primary Service")
          +
        4. Return GetGATTChildren(attribute=this.device@[[representedDevice]],
          single=false,
          uuidCanonicalizer=BluetoothUUID.getService,
          uuid=service,
          allowedUuids=this.device@[[allowedServices]],
          child type="GATT Primary Service")
        @@ -3616,7 +3629,7 @@

        Clear deviceObj.gatt@[[activeAlgorithms]].
      1. Fire an event named gattserverdisconnected with its bubbles attribute initialized to true at deviceObj. -

        This event is not fired at the BluetoothRemoteGATTServer.

        +

        This event is not fired at the BluetoothRemoteGATTServer.

      @@ -3680,15 +3693,15 @@

    6. If no remaining Service in deviceObj@[[representedDevice]] has the same UUID as service, remove the UUID from deviceObj@[[unfilteredUuids]]. -
    7. If deviceObj@[[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. +
    8. If deviceObj@[[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.
    9. Remove this BluetoothRemoteGATTService from the Bluetooth tree.
    10. For each Service in addedEntities, add the Service’s UUID to deviceObj@[[unfilteredUuids]]. - If deviceObj@[[allowedServices]] is "all" or contains the Service’s UUID, + If deviceObj@[[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.
    11. For each Service in changedServices, - if deviceObj@[[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. + if deviceObj@[[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.
    12. @@ -4567,6 +4580,7 @@

      Flags Data Type, in §9
    13. GAP Interoperability Requirements, in §9
    14. GATT, in §9 +
    15. [[gatt]], in §4.2
    16. gatt, in §4.2
    17. GATT blacklist, in §7
    18. GATT Client, in §9 @@ -4974,7 +4988,7 @@

      I interface BluetoothDevice { readonly attribute DOMString id; readonly attribute DOMString? name; - readonly attribute BluetoothRemoteGATTServer gatt; + readonly attribute BluetoothRemoteGATTServer? gatt; readonly attribute FrozenArray<UUID> uuids; Promise<void> watchAdvertisements(); @@ -5384,12 +5398,6 @@

      4.2. BluetoothDevice - + +

    19. + + + + + + + +
      FieldInitial value
      {{BluetoothLEScan/filters}}|filters|
      {{BluetoothLEScan/keepRepeatedDevices}}|options|.keepRepeatedDevices
      {{BluetoothLEScan/active}}`true`
      +

    20. +
    21. + Add |scan| to navigator.bluetooth.{{[[activeScans]]}}. +
    22. +
    23. + Ensure the UA is scanning for BLE advertisements + in a mode that will receive at least + all advertisements matching any scan + in any {{[[activeScans]]}} set in the whole UA. +
    24. +
    25. + If the UA fails to start scanning, + remove |scan| from navigator.bluetooth.{{[[activeScans]]}}, + reject |promise| with one of the following errors, + and abort these steps: + +
      +
      The UA doesn't support scanning for advertisements
      +
      {{NotSupportedError}}
      + +
      Bluetooth is turned off
      +
      {{InvalidStateError}}
      + +
      Other reasons
      +
      {{UnknownError}}
      +
      +
    26. +
    27. + Resolve |promise| with |scan|. +
    28. +
    + + +
    +

    Controlling a BLE scan

    + +
    +      [Constructor(BluetoothLEScanFilterInit init)]
    +      interface BluetoothLEScanFilter {
    +        readonly attribute UUID? serviceUUID;
    +        readonly attribute unsigned short? manufacturerData;
    +        readonly attribute UUID? serviceData;
    +        readonly attribute boolean? connectable;
    +      };
    +
    +      interface BluetoothLEScan {
    +        readonly attribute FrozenArray<BluetoothLEScanFilter> filters;
    +        readonly attribute boolean keepRepeatedDevices;
    +
    +        readonly attribute boolean active;
    +
    +        void stop();
    +      };
    +    
    + +
    +

    + {{BluetoothLEScan/stop()|BluetoothLEScan.stop()}} + stops a previously-requested scan. + Sites should do this as soon as possible to avoid wasting power. +

    +
    + +
    +

    + The BluetoothLEScanFilter(|init|) + constructor, when invoked MUST perform the following steps: +

    +
      +
    1. + Initialize all fields to `null`. +
    2. +
    3. + For each present member in |init|, + set `this`'s attribute with a matching identifier to the value of the member. + If the member's type is {{BluetoothServiceUUID}}, + pass its value through {{BluetoothUUID/getService()|BluetoothUUID.getService()}} + to get the value with which to initialize the attribute. + If any of these calls throw an exception, + propagate that exception from this constructor. +
    4. +
    +
    + +
    +

    + The stop() method, when invoked, + MUST perform the following steps: +

    +
      +
    1. Set this.{{BluetoothLEScan/active}} to `false`.
    2. +
    3. Remove `this` from navigator.bluetooth.{{[[activeScans]]}}.
    4. +
    5. + The UA SHOULD reconfigure or stop its BLE scan to save power + while still receiving any advertisements that + match any scan + in any {{[[activeScans]]}} set in the whole UA. +
    6. +
    +
    +
    + +
    +

    Permission to scan

    + +

    + The "bluetooth-le-scan" + powerful feature's + permission-related algorithms and types + are defined as follows: +

    +
    +
    permission descriptor type
    +
    +
    +          dictionary BluetoothLEScanPermissionDescriptor : PermissionDescriptor {
    +            // These match BluetoothLEScanOptions.
    +            sequence<BluetoothLEScanFilterInit> filters;
    +            boolean keepRepeatedDevices;
    +          };
    +        
    +
    + +
    permission result type
    +
    +
    +          interface BluetoothLEScanPermissionResult : PermissionStatus {
    +            attribute FrozenArray<BluetoothLEScan> scans;
    +          };
    +        
    +
    + +
    permission query algorithm
    +
    +

    + Given a {{BluetoothLEScanPermissionDescriptor}} |descriptor| + and a {{BluetoothLEScanPermissionResult}} |result|: +

    +
      +
    1. + Update |result|.{{PermissionStatus/state}} + to |descriptor|'s permission state. +
    2. +
    3. + If |result|.{{PermissionStatus/state}} is {{"denied"}}, + set |result|.scans to an empty {{FrozenArray}} + and abort these steps. +
    4. +
    5. + Update |result|.{{scans}} to + a new {{FrozenArray}} containing + the elements of navigator.bluetooth.{{[[activeScans]]}}. + +

      + Consider filtering the result to active scans that + match the fields of the descriptor. +

      +
    6. +
    +
    + +
    permission request algorithm
    +
    +

    + Given a {{BluetoothLEScanPermissionDescriptor}} |descriptor| + and a {{BluetoothLEScanPermissionResult}} |result|: +

    +
      +
    1. + Let |promise| be the result of + navigator.bluetooth.{{requestLEScan()|requestLEScan}}(|descriptor|). +
    2. +
    3. Wait for |promise| to settle.
    4. +
    5. + If |promise| rejected, throw the reason it rejected with, + and abort these steps. +
    6. +
    7. + Update |result|.status to + |descriptor|'s permission state. +
    8. +
    9. + Update |result|.scans to + a new {{FrozenArray}} whose single element is the value in |promise|. +
    10. +
    +
    + +
    + permission revocation algorithm +
    +
    +
      +
    1. + For each |activeScan| in navigator.bluetooth.{{[[activeScans]]}}: +
        +
      1. + If the permission state of +
        +                  {
        +                    name: "bluetooth-le-scan",
        +                    filters: activeScan.filters,
        +                    keepRepeatedDevices: activeScan.keepRepeatedDevices
        +                  }
        +                
        + is not {{"granted"}}, + call |activeScan|.{{stop()}}. +
      2. +
      +
    2. +
    +
    +
    +
    + + +
    +

    Event handling

    + +
    +

    Responding to advertising events

    + +
    +

    + When the UA receives an advertising event |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. +
    2. +
    3. + For each {{Bluetooth}} instance |bluetooth| in the UA, + queue a task on + |bluetooth|'s relevant settings object's responsible event loop + to do the following sub-steps: +
        +
      1. + Let |scans| be the set of + {{BluetoothLEScan}}s in |bluetooth|.{{[[activeScans]]}} + that match |event|. +
      2. +
      3. If |scans| is empty, abort these sub-steps.
      4. +
      5. + Note: the user's permission to scan + likely indicates that + they intend newly-discovered devices to appear in + {{"bluetooth"}}'s extra permission data, + but possibly without any {{AllowedBluetoothDevice/allowedServices}}. +
      6. +
      7. + Get the `BluetoothDevice` representing |device| inside |bluetooth|, + and let |deviceObj| be the result. +
      8. +
      9. + Add each {{BluetoothLEScan}} in |scans| + to |deviceObj|.{{[[returnedFromScans]]}}. +
      10. +
      11. + Fire an `advertisementreceived` event + for |event| at |deviceObj|. +
      12. +
      +
    4. +
    +
    + +
    +

    + An advertising event |event| + matches a {{BluetoothLEScan}} |scan| + if the following steps return `match`: +

    +
      +
    1. + If |event| doesn't match + any filter in |scan|.{{BluetoothLEScan/filters}}, + return `no match`. +
    2. +
    3. + If |scan|.{{BluetoothLEScan/keepRepeatedDevices}} is `false`, + there is a {{BluetoothDevice}} |device| that + represents the same Bluetooth device as the one that sent |event|, + and |device|.{{[[returnedFromScans]]}} includes |scan|, + the UA MAY return `no match`. +
    4. +
    5. Return `match`.
    6. +
    +
    + +
    +

    + An advertising event |event| + matches + a {{BluetoothLEScanFilter}} |filter| + if all of the following conditions hold: +

    +
      +
    • + If |filter|.serviceUUID is non-`null`, + it is equal to a Service UUID in |event|. +
    • +
    • + If |filter|.manufacturerData is non-`null`, + it is equal to the Company Identifier Code + in some Manufacturer Specific Data in |event|. +
    • +
    • + If |filter|.serviceData is non-`null`, + it is the 128-bit UUID representation of the UUID + in some Service Data in |event|. +
    • +
    • + If |filter|.connectable is `true`, + |event| has either the ADV_IND + or the ADV_DIRECT_IND type. +
    • +
    • + If |filter|.connectable is `false`, + |event| has either the ADV_NONCONN_IND + or the ADV_SCAN_IND type. +
    • +
    +
    +
    +
    + +
    +

    Changes to existing interfaces

    + +

    + Instances of {{Bluetooth}} additionally have the following internal slots: +

    + + + + + + + + + + + +
    Internal SlotInitial ValueDescription (non-normative)
    \[[activeScans]] + An empty set of {{BluetoothLEScan}} instances. + + The contents of this set will have {{BluetoothLEScan/active}} equal to `true`. +
    + +

    + Instances of {{BluetoothDevice}} additionally have the following internal slots: +

    + + + + + + + + + + + +
    Internal SlotInitial ValueDescription (non-normative)
    \[[returnedFromScans]] + An empty set of {{BluetoothLEScan}} objects. + + Used to implement {{BluetoothLEScanOptions/keepRepeatedDevices}}. +
    +
    + +
    +

    Terminology and conventions

    + +

    + This specification uses a few conventions and several terms from other specifications. + This section lists those and links to their primary definitions. +

    + +

    + 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/requestLEScan()}} algorithm says to call + |options|.filters.map(filter=>new + {{BluetoothLEScanFilter/BluetoothLEScanFilter()|BluetoothLEScanFilter}}(filter)), + this MUST apply the + {{Array.prototype.map}} algorithm defined in [[ECMAScript]] + with |options|.services as its this parameter and + the algorithm defined at {{BluetoothLEScanFilter/BluetoothLEScanFilter()}} + as its callbackfn parameter, + regardless of any modifications that have been made to window, + Array, Array.prototype, Array.prototype.map, + Function, Function.prototype, + BluetoothLEScanFilter, or other objects. +

    + +
    +
    [[!BLUETOOTH42]]
    +
    +
      +
    1. Architecture & Terminology Overview +
        +
      1. General Description +
          +
        1. Overview of Bluetooth Low Energy Operation + (defines advertising events) +
        2. +
        +
      2. +
      +
    2. +
    3. Core System Package [Host volume] +
        +
      1. Generic Access Profile +
          +
        1. Profile Overview +
            +
          1. Profile Roles +
              +
            1. Roles when Operating over an LE Physical Transport +
                +
              1. Observer Role
              2. +
              +
            2. +
            +
          2. +
          +
        2. +
        3. Security Aspects — LE Physical Transport +
            +
          1. Privacy Feature +
              +
            1. Privacy Feature in an Observer +
            +
          2. +
          +
        4. +
        +
      2. +
      +
    4. +
    5. Core System Package [Low Energy Controller volume] +
        +
      1. Link Layer Specification +
          +
        1. General Description +
            +
          1. Device Address
          2. +
          +
        2. +
        3. Air Interface Packets +
            +
          1. Advertising Channel PDU +
              +
            1. Advertising PDUs +
                +
              1. ADV_IND
              2. +
              3. ADV_DIRECT_IND
              4. +
              5. ADV_NONCONN_IND
              6. +
              7. ADV_SCAN_IND
              8. +
              +
            2. +
            +
          2. +
          +
        4. +
        5. Air Interface Protocol +
            +
          1. Non-Connected States +
              +
            1. Scanning State +
                +
              1. Passive Scanning
              2. +
              3. Active Scanning
              4. +
              +
            2. +
            +
          2. +
          +
        6. +
        +
      2. +
      +
    6. +
    +
    +
    [[!BLUETOOTH-SUPPLEMENT6]]
    +
    +
      +
    1. Data Types Specification +
        +
      1. Data Types Definitions and Formats +
          +
        1. Service UUID
        2. +
        3. Manufacturer Specific Data
        4. +
        5. Service Data
        6. +
        +
      2. +
      +
    2. +
    +
    +
    +
    diff --git a/scanning.html b/scanning.html new file mode 100644 index 0000000..90c13f7 --- /dev/null +++ b/scanning.html @@ -0,0 +1,2644 @@ + + + + + Web Bluetooth Scanning + + + + + + + + + + +
    +

    +

    Web Bluetooth Scanning

    +

    Draft Community Group Report,

    + +
    + +
    +
    +

    Abstract

    +
    +

    This document describes an API to scan for nearby Bluetooth Low Energy devices in real time.

    +
    +

    Status of this document

    +
    +

    This specification was published by the Web Bluetooth Community Group. + It is not a W3C Standard nor is it on the W3C Standards Track. + + Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. + + Learn more about W3C Community and Business Groups.

    +

    Changes to this document may be tracked at https://github.com/WebBluetoothCG/web-bluetooth/commits/gh-pages.

    +

    If you wish to make comments regarding this document, please send them to public-web-bluetooth@w3.org (subscribe, archives).

    +

    +
    +
    + +
    +
    +

    1. Introduction

    +

    This section is non-normative.

    +

    Bluetooth Low Energy (BLE) allows devices to broadcast advertisements to nearby observers. + These advertisements can contain small amounts of data + of a variety of types defined in [BLUETOOTH-SUPPLEMENT6].

    +

    For example, a beacon might announce that it’s next to a particular museum exhibit + and is advertising with 1mW of power, + which would let nearby observers know their approximate distance to that exhibit.

    +

    This specification extends [web-bluetooth] to allow websites to + receive these advertisements from nearby BLE devices, + with the user’s permission.

    +
    +

    1.1. Examples

    +
    + +

    To discover what iBeacons are nearby and measure their distance, + a website could use code like the following:

    +
    function recordNearbyBeacon(major, minor, pathLossVs1m) { ... }
    +let myBeaconUuid = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
    +
    +navigator.bluetooth.requestLEScan({
    +  filters: [{manufacturerData: 0x004C}],
    +  options: {
    +    keepRepeatedDevices: true,
    +  }
    +}).then(() => {
    +  navigator.bluetooth.addEventListener('advertisementreceived', event => {
    +    let appleData = event.manufacturerData.get(0x004C);
    +    if (appleData.byteLength != 23 ||
    +        appleData.getUint16(0, false) !== 0x0215) {
    +      // Isn’t an iBeacon.
    +      return;
    +    }
    +    let uuidArray = new Uint8Array(appleData.buffer, 2, 16);
    +    for (let i = 0; i < 16; i++) {
    +      if (uuidArray[i] != myBeaconUuid[i]) {
    +        // Not my class of beacon.
    +        return;
    +      }
    +    }
    +    let major = appleData.getUint16(18, false);
    +    let minor = appleData.getUint16(20, false);
    +    let txPowerAt1m = -appleData.getInt8(22);
    +    let pathLossVs1m = txPowerAt1m - event.rssi;
    +
    +    recordNearbyBeacon(major, minor, pathLossVs1m);
    +  });
    +})
    +
    +
    +
    +
    +
    +

    2. Privacy considerations

    +

    This section is non-normative.

    +

    Actively scanning for advertisements + broadcasts a device address of the scanning device. + If the UA’s Bluetooth system supports the privacy feature, + this address rotates periodically, + which prevents remote radios from tracking the user’s device. + However, if the UA’s Bluetooth system does not support the privacy feature, + this address is a stable unique identifier that’s difficult to change. + To mitigate this, UAs should either use passive scanning, + use the privacy feature in an observer, + or warn the user that nearby devices will learn the identity of their device.

    +

    The ambient advertisements in a user’s area are unlikely to directly include GPS coordinates, + but are likely to contain unique identifiers + that could be manually correlated with particular physical locations + or with particular other people. + Given that, the user needs to give permission + before a website gets access to nearby advertisements.

    +

    If a user has already given a site permission to know their location, + it might be ok to implicitly grant access to BLE advertisements. + However, BLE advertisements give away + strictly less location information than full [geolocation-api] access, + so UAs should allow users to grant that intermediate level of access.

    +

    BLE advertisements are usually fully public, + since they’re broadcast unencrypted on 2.4GHz radio waves. + However, it’s possible that a user would + have a device broadcast private information in a radio-shielded room. + This is probably an inappropriate use for BLE advertisements, + but might argue for requiring explicit permission to scan, + rather than inferring it from having permission to get a geolocation.

    +
    +
    +

    3. Security considerations

    +

    This section is non-normative.

    +

    Because this API doesn’t write anything, + there are few if any security implications. + A device in a shielded room might broadcast security-sensitive information, + but we don’t have any actual attack scenarios for that.

    +
    +
    +

    4. Scanning for BLE advertisements

    +
    dictionary BluetoothLEScanFilterInit {
    +  BluetoothServiceUUID serviceUUID;
    +  unsigned short manufacturerData;
    +  BluetoothServiceUUID serviceData;
    +  boolean connectable;
    +};
    +
    +dictionary BluetoothLEScanOptions {
    +  required sequence<BluetoothLEScanFilterInit> filters;
    +  boolean keepRepeatedDevices = false;
    +};
    +
    +partial interface Bluetooth {
    +  [SecureContext]
    +  Promise<BluetoothLEScan> requestLEScan(BluetoothLEScanOptions options);
    +};
    +
    +
    +
    NOTE: requestLEScan() summary
    +

    navigator.bluetooth.requestLEScan(options) starts scanning for BLE advertisements, + asking the user for permission if they haven’t yet granted it.

    +

    Because this could show a prompt, + it requires a secure context, + and UAs are likely to require a user gesture.

    +

    Advertising events that match a BluetoothLEScanFilter in an active BluetoothLEScan cause advertisementreceived events to be dispatched + to the sending BluetoothDevice. + A filter matches if the advertisement includes data equal to each present member. + Usually, you’ll only include one member in each filter.

    +

    Normally scans will discard + the second and subsequent advertisements from a single device + to save power. + If you need to receive them, + set keepRepeatedDevices to true. + Note that setting keepRepeatedDevices to false doesn’t guarantee you won’t get redundant events; + it just allows the UA to save power by omitting them.

    +
    +
    +

    The requestLEScan(options) method, + when invoked, MUST return a new promise promise and run the following steps in parallel:

    +
      +
    1. Let filters be options.filters.map(filter=>new BluetoothLEScanFilter(filter)). + If this throws an exception, reject promise with that exception and abort these steps. +
    2. + Request permission to use +
      {
      +  name: "bluetooth-le-scan",
      +  filters: options.filters,
      +  keepRepeatedDevices: options.keepRepeatedDevices,
      +}
      +
      +

      This may require that this algorithm was triggered by user activation.

      +
    3. If the result is "denied", + reject promise with a NotAllowedError and abort these steps. +
    4. + Let scan be a new BluetoothLEScan instance + whose fields are initialized as in the following table: + + + + + + + +
      Field + Initial value +
      filters + filters +
      keepRepeatedDevices + options.keepRepeatedDevices +
      active + true +
      +
    5. Add scan to navigator.bluetooth.[[activeScans]]. +
    6. Ensure the UA is scanning for BLE advertisements + in a mode that will receive at least + all advertisements matching any scan + in any [[activeScans]] set in the whole UA. +
    7. + If the UA fails to start scanning, + remove scan from navigator.bluetooth.[[activeScans]], reject promise with one of the following errors, + and abort these steps: +
      +
      The UA doesn’t support scanning for advertisements +
      NotSupportedError +
      Bluetooth is turned off +
      InvalidStateError +
      Other reasons +
      UnknownError +
      +
    8. Resolve promise with scan. +
    +
    +
    +

    4.1. Controlling a BLE scan

    +
    [Constructor(BluetoothLEScanFilterInit init)]
    +interface BluetoothLEScanFilter {
    +  readonly attribute UUID? serviceUUID;
    +  readonly attribute unsigned short? manufacturerData;
    +  readonly attribute UUID? serviceData;
    +  readonly attribute boolean? connectable;
    +};
    +
    +interface BluetoothLEScan {
    +  readonly attribute FrozenArray<BluetoothLEScanFilter> filters;
    +  readonly attribute boolean keepRepeatedDevices;
    +
    +  readonly attribute boolean active;
    +
    +  void stop();
    +};
    +
    +
    +
    NOTE: BluetoothLEScan members
    +

    BluetoothLEScan.stop() stops a previously-requested scan. + Sites should do this as soon as possible to avoid wasting power.

    +
    +
    +

    The BluetoothLEScanFilter(init) constructor, when invoked MUST perform the following steps:

    +
      +
    1. Initialize all fields to null. +
    2. For each present member in init, + set this's attribute with a matching identifier to the value of the member. + If the member’s type is BluetoothServiceUUID, + pass its value through BluetoothUUID.getService() to get the value with which to initialize the attribute. + If any of these calls throw an exception, + propagate that exception from this constructor. +
    +
    +
    +

    The stop() method, when invoked, + MUST perform the following steps:

    +
      +
    1. Set this.active to false. +
    2. Remove this from navigator.bluetooth.[[activeScans]]. +
    3. The UA SHOULD reconfigure or stop its BLE scan to save power + while still receiving any advertisements that match any scan + in any [[activeScans]] set in the whole UA. +
    +
    +
    +
    +

    4.2. Permission to scan

    +

    The "bluetooth-le-scan" powerful feature’s + permission-related algorithms and types + are defined as follows:

    +
    +
    permission descriptor type +
    +
    dictionary BluetoothLEScanPermissionDescriptor : PermissionDescriptor {
    +  // These match BluetoothLEScanOptions.
    +  sequence<BluetoothLEScanFilterInit> filters;
    +  boolean keepRepeatedDevices;
    +};
    +
    +
    permission result type +
    +
    interface BluetoothLEScanPermissionResult : PermissionStatus {
    +  attribute FrozenArray<BluetoothLEScan> scans;
    +};
    +
    +
    permission query algorithm +
    +

    Given a BluetoothLEScanPermissionDescriptor descriptor and a BluetoothLEScanPermissionResult result:

    +
      +
    1. Update result.state to descriptor’s permission state. +
    2. If result.state is "denied", + set result.scans to an empty FrozenArray and abort these steps. +
    3. + Update result.scans to + a new FrozenArray containing + the elements of navigator.bluetooth.[[activeScans]]. +

      Consider filtering the result to active scans that + match the fields of the descriptor.

      +
    +
    permission request algorithm +
    +

    Given a BluetoothLEScanPermissionDescriptor descriptor and a BluetoothLEScanPermissionResult result:

    +
      +
    1. Let promise be the result of navigator.bluetooth.requestLEScan(descriptor). +
    2. Wait for promise to settle. +
    3. If promise rejected, throw the reason it rejected with, + and abort these steps. +
    4. Update result.status to descriptor’s permission state. +
    5. Update result.scans to + a new FrozenArray whose single element is the value in promise. +
    +
    permission revocation algorithm +
    +
      +
    1. + For each activeScan in navigator.bluetooth.[[activeScans]]: +
        +
      1. + If the permission state of +
        {
        +  name: "bluetooth-le-scan",
        +  filters: activeScan.filters,
        +  keepRepeatedDevices: activeScan.keepRepeatedDevices
        +}
        +
        +

        is not "granted", + call activeScan.stop().

        +
      +
    +
    +
    +
    +
    +

    5. Event handling

    +
    +

    5.1. Responding to advertising events

    +
    +

    When the UA receives an advertising event 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. +
    2. + For each Bluetooth instance bluetooth in the UA, queue a task on bluetooth’s relevant settings object’s responsible event loop to do the following sub-steps: +
        +
      1. Let scans be the set of BluetoothLEScans in bluetooth.[[activeScans]] that match event. +
      2. If scans is empty, abort these sub-steps. +
      3. Note: the user’s permission to scan likely indicates that + they intend newly-discovered devices to appear in "bluetooth"'s extra permission data, + but possibly without any allowedServices. +
      4. Get the BluetoothDevice representing device inside bluetooth, + and let deviceObj be the result. +
      5. Add each BluetoothLEScan in scans to deviceObj.[[returnedFromScans]]. +
      6. Fire an advertisementreceived event for event at deviceObj. +
      +
    +
    +
    +

    An advertising event event matches a BluetoothLEScan scan if the following steps return match:

    +
      +
    1. If event doesn’t match any filter in scan.filters, + return no match. +
    2. If scan.keepRepeatedDevices is false, + there is a BluetoothDevice device that + represents the same Bluetooth device as the one that sent event, + and device.[[returnedFromScans]] includes scan, + the UA MAY return no match. +
    3. Return match. +
    +
    +
    +

    An advertising event event matches a BluetoothLEScanFilter filter if all of the following conditions hold:

    +
      +
    • If filter.serviceUUID is non-null, + it is equal to a Service UUID in event. +
    • If filter.manufacturerData is non-null, + it is equal to the Company Identifier Code + in some Manufacturer Specific Data in event. +
    • If filter.serviceData is non-null, + it is the 128-bit UUID representation of the UUID + in some Service Data in event. +
    • If filter.connectable is true, event has either the ADV_IND or the ADV_DIRECT_IND type. +
    • If filter.connectable is false, event has either the ADV_NONCONN_IND or the ADV_SCAN_IND type. +
    +
    +
    +
    +
    +

    6. Changes to existing interfaces

    +

    Instances of Bluetooth additionally have the following internal slots:

    + + + + + +
    Internal Slot + Initial Value + Description (non-normative) +
    [[activeScans]] + An empty set of BluetoothLEScan instances. + The contents of this set will have active equal to true. +
    +

    Instances of BluetoothDevice additionally have the following internal slots:

    + + + + + +
    Internal Slot + Initial Value + Description (non-normative) +
    [[returnedFromScans]] + An empty set of BluetoothLEScan objects. + Used to implement keepRepeatedDevices. +
    +
    +
    +

    7. Terminology and conventions

    +

    This specification uses a few conventions and several terms from other specifications. + This section lists those and links to their primary definitions.

    +

    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 requestLEScan() algorithm says to call options.filters.map(filter=>new BluetoothLEScanFilter(filter)), + this MUST apply the Array.prototype.map algorithm defined in [ECMAScript] with options.services as its this parameter and + the algorithm defined at BluetoothLEScanFilter() as its callbackfn parameter, + regardless of any modifications that have been made to window, Array, Array.prototype, Array.prototype.map, Function, Function.prototype, BluetoothLEScanFilter, or other objects.

    +
    +
    [BLUETOOTH42] +
    +
      +
    1. + Architecture & Terminology Overview +
        +
      1. + General Description +
          +
        1. Overview of Bluetooth Low Energy Operation + (defines advertising events) +
        +
      +
    2. + Core System Package [Host volume] +
        +
      1. + Generic Access Profile +
          +
        1. + Profile Overview +
            +
          1. + Profile Roles +
              +
            1. + Roles when Operating over an LE Physical Transport +
                +
              1. Observer Role +
              +
            +
          +
        2. + Security Aspects — LE Physical Transport +
            +
          1. + Privacy Feature +
              +
            1. Privacy Feature in an Observer +
            +
          +
        +
      +
    3. + Core System Package [Low Energy Controller volume] +
        +
      1. + Link Layer Specification +
          +
        1. + General Description +
            +
          1. Device Address +
          +
        2. + Air Interface Packets +
            +
          1. + Advertising Channel PDU +
              +
            1. + Advertising PDUs +
                +
              1. ADV_IND +
              2. ADV_DIRECT_IND +
              3. ADV_NONCONN_IND +
              4. ADV_SCAN_IND +
              +
            +
          +
        3. + Air Interface Protocol +
            +
          1. + Non-Connected States +
              +
            1. + Scanning State +
                +
              1. Passive Scanning +
              2. Active Scanning +
              +
            +
          +
        +
      +
    +
    [BLUETOOTH-SUPPLEMENT6] +
    +
      +
    1. + Data Types Specification +
        +
      1. + Data Types Definitions and Formats +
          +
        1. Service UUID +
        2. Manufacturer Specific Data +
        3. Service Data +
        +
      +
    +
    +
    +
    +
    +

    Conformance

    +

    Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. + The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” + in the normative parts of this document + are to be interpreted as described in RFC 2119. + However, for readability, + these words do not appear in all uppercase letters in this specification.

    +

    All of the text of this specification is normative + except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

    +

    Examples in this specification are introduced with the words “for example” + or are set apart from the normative text with class="example", like this:

    +
    This is an example of an informative example.
    +

    Informative notes begin with the word “Note” + and are set apart from the normative text with class="note", like this:

    +

    Note, this is an informative note.

    +
    + +

    Index

    +

    Terms defined by this specification

    + +

    Terms defined by reference

    + +

    References

    +

    Normative References

    +
    +
    [BLUETOOTH-SUPPLEMENT6] +
    Supplement to the Bluetooth Core Specification Version 6. 14 July 2015. URL: https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=302735 +
    [BLUETOOTH42] +
    BLUETOOTH SPECIFICATION Version 4.2. 2 December 2014. URL: https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439 +
    [ECMAScript] +
    ECMAScript Language Specification. URL: https://tc39.github.io/ecma262/ +
    [HTML] +
    Ian Hickson. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/ +
    [PERMISSIONS] +
    Mounir Lamouri; Marcos Caceres. The Permissions API. 7 April 2015. WD. URL: https://w3c.github.io/permissions/ +
    [PROMISES-GUIDE] +
    Domenic Denicola. Writing Promise-Using Specifications. 16 February 2016. Finding of the W3C TAG. URL: https://www.w3.org/2001/tag/doc/promises-guide +
    [RFC2119] +
    S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119 +
    [WEB-BLUETOOTH] +
    Jeffrey Yasskin. Web Bluetooth. Draft Community Group Report. URL: https://webbluetoothcg.github.io/web-bluetooth/ +
    [WebIDL-1] +
    Cameron McCormack; Boris Zbarsky. WebIDL Level 1. 8 March 2016. CR. URL: https://heycam.github.io/webidl/ +
    +

    Informative References

    +
    +
    [GEOLOCATION-API] +
    Andrei Popescu. Geolocation API Specification. 28 May 2015. PER. URL: http://dev.w3.org/geo/api/spec-source.html +
    [SECURE-CONTEXTS] +
    Mike West. Secure Contexts. 19 July 2016. WD. URL: https://w3c.github.io/webappsec-secure-contexts/ +
    +

    IDL Index

    +
    dictionary BluetoothLEScanFilterInit {
    +  BluetoothServiceUUID serviceUUID;
    +  unsigned short manufacturerData;
    +  BluetoothServiceUUID serviceData;
    +  boolean connectable;
    +};
    +
    +dictionary BluetoothLEScanOptions {
    +  required sequence<BluetoothLEScanFilterInit> filters;
    +  boolean keepRepeatedDevices = false;
    +};
    +
    +partial interface Bluetooth {
    +  [SecureContext]
    +  Promise<BluetoothLEScan> requestLEScan(BluetoothLEScanOptions options);
    +};
    +
    +[Constructor(BluetoothLEScanFilterInit init)]
    +interface BluetoothLEScanFilter {
    +  readonly attribute UUID? serviceUUID;
    +  readonly attribute unsigned short? manufacturerData;
    +  readonly attribute UUID? serviceData;
    +  readonly attribute boolean? connectable;
    +};
    +
    +interface BluetoothLEScan {
    +  readonly attribute FrozenArray<BluetoothLEScanFilter> filters;
    +  readonly attribute boolean keepRepeatedDevices;
    +
    +  readonly attribute boolean active;
    +
    +  void stop();
    +};
    +
    +dictionary BluetoothLEScanPermissionDescriptor : PermissionDescriptor {
    +  // These match BluetoothLEScanOptions.
    +  sequence<BluetoothLEScanFilterInit> filters;
    +  boolean keepRepeatedDevices;
    +};
    +
    +interface BluetoothLEScanPermissionResult : PermissionStatus {
    +  attribute FrozenArray<BluetoothLEScan> scans;
    +};
    +
    +
    +

    Issues Index

    +
    +
    Consider filtering the result to active scans that + match the fields of the descriptor.
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file