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) { ... } navigator. bluetooth. requestLEScan({ filters: [{ manufacturerData: { 0x004C : { dataPrefix: new Uint8Array([ 0x02 , 0x15 , // iBeacon identifier. 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 // My beacon UUID. ])}}}], keepRepeatedDevices: true }). then(() => { navigator. bluetooth. addEventListener( ' advertisementreceived ' , event=> { let appleData= event. manufacturerData. get( 0x004C ); if ( appleData. byteLength!= 23 ) { // Isn’t an iBeacon. 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] 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 {
BluetoothLEScanOptions sequence <BluetoothLEScanFilterInit >;
filters boolean keepRepeatedDevices =false ;boolean acceptAllAdvertisements =false ; };partial interface Bluetooth { [SecureContext ]Promise <BluetoothLEScan >requestLEScan (optional BluetoothLEScanOptions = {}); };
options
requestLEScan()
summary navigator.bluetooth.
starts scanning for BLE advertisements,
asking the user for permission if they haven’t yet granted it. requestLEScan(options)
Because this could show a prompt, it requires a secure context.
Additionally, UAs are likely to require a transient
user activation on its relevant global object when requestLEScan
is called.
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.
In the rare case that you want to receive every advertisement without filtering them,
use the acceptAllAdvertisements
field.
The
method,
when invoked, MUST return a new promise promise and run the following steps in parallel: requestLEScan(options)
- If this's relevant global object's associated Document is
not allowed to use the policy-controlled feature named
"bluetooth"
, reject promise with aSecurityError
and abort these steps. -
If
options.acceptAllAdvertisements
istrue
, andoptions.filters
is present, reject promise with aTypeError
and abort these steps.Note: There’s no need to include filters if all advertisements are being accepted.
-
If
options.acceptAllAdvertisements
isfalse
, andoptions.filters
is either absent or empty, reject promise with aTypeError
and abort these steps.Note: An empty set of filters wouldn’t return any advertisements.
- Let filters be
ifArray.prototype.map
.call(options.filters, filter=>newBluetoothLEScanFilter
(filter))options.filters
is present, or an emptyFrozenArray
otherwise. If this throws an exception, reject promise with that exception and abort these steps. -
Request permission to use
{ name: "bluetooth-le-scan" , filters: options. filters, keepRepeatedDevices: options. keepRepeatedDevices, acceptAllAdvertisements: options. acceptAllAdvertisements, } Note: This may require that this algorithm has a transient activation on its relevant global object when triggered.
- If the result is "
denied
", reject promise with aNotAllowedError
and abort these steps. -
Let scan be a new
BluetoothLEScan
instance whose fields are initialized as in the following table:Field Initial value filters
filters keepRepeatedDevices
options.keepRepeatedDevices
acceptAllAdvertisements
options.acceptAllAdvertisements
active
true
- Add scan to
navigator.bluetooth.
.[[activeScans]]
-
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.Find wording that allows the UA to limit its scan to only certain periods of time, to save power.
-
If the UA fails to start scanning,
remove scan from
navigator.bluetooth.
, reject promise with one of the following errors, and abort these steps:[[activeScans]]
- The UA doesn’t support scanning for advertisements
NotSupportedError
- Bluetooth is turned off
InvalidStateError
- Other reasons
UnknownError
- Resolve promise with scan.
4.1. Controlling a BLE scan
[Exposed =Window ,SecureContext ]interface {
BluetoothDataFilter constructor (optional BluetoothDataFilterInit = {});
init readonly attribute ArrayBuffer ;
dataPrefix readonly attribute ArrayBuffer ; }; [
mask Exposed =Window ,SecureContext ]interface {
BluetoothManufacturerDataFilter constructor (optional object );
init readonly maplike <unsigned short ,BluetoothDataFilter >; }; [Exposed =Window ,SecureContext ]interface {
BluetoothServiceDataFilter constructor (optional object );
init readonly maplike <UUID ,BluetoothDataFilter >; }; [Exposed =Window ,SecureContext ]interface {
BluetoothLEScanFilter constructor (optional BluetoothLEScanFilterInit = {});
init readonly attribute DOMString ?;
name readonly attribute DOMString ?;
namePrefix readonly attribute FrozenArray <UUID >;
services readonly attribute BluetoothManufacturerDataFilter ;
manufacturerData readonly attribute BluetoothServiceDataFilter ; }; [
serviceData Exposed =Window ,SecureContext ]interface {
BluetoothLEScan readonly attribute FrozenArray <BluetoothLEScanFilter >;
filters readonly attribute boolean ;
keepRepeatedDevices readonly attribute boolean ;
acceptAllAdvertisements readonly attribute boolean ;
active undefined stop (); };
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:
- Initialize all nullable fields to
null
. -
If no member of init is present, throw a
TypeError
.Note: A filter can’t implicitly allow all advertisements. Use
acceptAllAdvertisements
to explicitly do it. - Initialize
this.
asmanufacturerData
new BluetoothManufacturerDataFilter(init.
.manufacturerData
) - Initialize
this.
asserviceData
new BluetoothServiceDataFilter(init.
.serviceData
) - Initialize
this.
asservices
ifArray.prototype.map
.call(init.services
, service=>BluetoothUUID.getService
(service))init.
is present, or an emptyservices
FrozenArray
otherwise. - For each other present member in init,
set
this
’s attribute with a matching identifier to the value of the member. - If any of the above calls threw an exception, propagate that exception from this constructor.
The stop()
method, when invoked,
MUST perform the following steps:
- Set
this.
toactive
false
. - Remove
this
fromnavigator.bluetooth.
.[[activeScans]]
- 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.
The BluetoothManufacturerDataFilter(init)
constructor, when invoked, MUST perform the following steps:
- If init is not present
or
init.
is empty, then[[OwnPropertyKeys]]
()this
has no map entries. Abort these steps. - Let canonicalInit be the
manufacturerData
field of the result of canonicalizing{manufacturerData: init}
. If this throws an exception, propagate that exception from this constructor and abort these steps. -
this
’s map entries map from each key incanonicalInit.
, parsed as a base-10 integer, to its value of[[OwnPropertyKeys]]
()new
.BluetoothDataFilter
(canonicalInit[key])
The BluetoothServiceDataFilter(init)
constructor, when invoked, MUST perform the following steps:
- If init is not present
or
init.
is empty, then[[OwnPropertyKeys]]
()this
has no map entries. Abort these steps. - Let canonicalInit be the
serviceData
field of the result of canonicalizing{serviceData: init}
. If this throws an exception, propagate that exception from this constructor and abort these steps. -
this
’s map entries map from each key incanonicalInit.
, to its value of[[OwnPropertyKeys]]
()new
.BluetoothDataFilter
(canonicalInit[key])
The BluetoothDataFilter(init)
constructor, when invoked, MUST perform the following steps:
- Let canonicalInit be the result of canonicalizing init. If this throws an exception, propagate that exception from this constructor and abort these steps.
- Initialize
this.
as a read only ArrayBuffer whose contents are a copy of the bytes held bydataPrefix
canonicalInit.
.dataPrefix
- Initialize
this.
as a read only ArrayBuffer whose contents are a copy of the bytes held bymask
canonicalInit.
.mask
4.2. Handling Document Loss of Full Activity
Operations that initiate a scan for Bluetooth devices may only run in a fully active document. When full activity is lost, scanning operations for that document need to be aborted.
Document
of
the current settings object's relevant global object is no longer fully active, it must run these steps:
-
For each activeScan in
navigator.bluetooth.
, perform the following steps:[[activeScans]]
-
Call
activeScan.
.stop()
-
4.3. 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 false ;boolean
=acceptAllAdvertisements false ; }; - permission result type
-
[
Exposed =Window ,SecureContext ]interface
:BluetoothLEScanPermissionResult PermissionStatus {attribute FrozenArray <BluetoothLEScan >
; };scans - permission query algorithm
-
Given a
BluetoothLEScanPermissionDescriptor
descriptor and aBluetoothLEScanPermissionResult
result:- Update
result.
to descriptor’s permission state.state
- If
result.
is "state
denied
", setresult.scans
to an emptyFrozenArray
and abort these steps. -
Update
result.
to a newscans
FrozenArray
containing the elements ofnavigator.bluetooth.
.[[activeScans]]
Consider filtering the result to active scans that match the fields of the descriptor.
- Update
- permission revocation algorithm
-
-
For each activeScan in
navigator.bluetooth.
:[[activeScans]]
-
If the permission state of
{ name: "bluetooth-le-scan" , filters: activeScan. filters, keepRepeatedDevices: activeScan. keepRepeatedDevices}
-
If the permission state of
-
For each activeScan in
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:
- Let device be the Bluetooth device that sent the advertising event.
-
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:- Let scans be the set of
BluetoothLEScan
s inbluetooth.
that match event.[[activeScans]]
- If scans is empty, abort these sub-steps.
- 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 with
mayUseGATT
set tofalse
. - Get the
BluetoothDevice
representing device inside bluetooth, and let deviceObj be the result. - Add each
BluetoothLEScan
in scans todeviceObj.
.[[returnedFromScans]]
- Fire an
advertisementreceived
event for event at deviceObj.
- Let scans be the set of
An advertising event event matches a BluetoothLEScan
scan if the following steps return match
:
-
scan.acceptAllAdvertisements
isfalse
and event doesn’t match any filter inscan.
, returnfilters
no match
. - If
scan.
iskeepRepeatedDevices
false
, there is aBluetoothDevice
device that represents the same Bluetooth device as the one that sent event, anddevice.
includes scan, the UA MAY return[[returnedFromScans]]
no match
. - Return
match
.
An advertising event event matches a BluetoothLEScanFilter
filter if all of the following conditions hold:
-
If
filter.name
is non-null
, event has a Local Name equal tofilter.name
.Note: A Shortened Local Name can match a
name
filter. - If
filter.namePrefix
is non-null
, event has a Local Name, andfilter.namePrefix
is a prefix of it. - For each uuid in
filter.services
, some Service UUID in event is equal to uuid. - For each (id, filter) in
filter.manufacturerData
’s map entries, some Manufacturer Specific Data in event has a Company Identifier Code of id, and whose array of bytes matches filter. - For each (uuid, filter) in
filter.serviceData
’s map entries, some Service Data in event has a UUID whose 128-bit representation is uuid, and whose array of bytes matches filter.
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
,
this MUST apply the Array.prototype.map
.call(options.filters,
filter=>new BluetoothLEScanFilter
(filter))Array.prototype.map
algorithm defined in [ECMAScript] with options.filters
as its this
parameter and filter=>new
as its BluetoothLEScanFilter
(filter)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]
-
-
Architecture & Terminology Overview
-
General Description
- Overview of Bluetooth Low Energy Operation (defines advertising events)
-
General Description
-
Core System Package [Host volume]
-
Generic Access Profile
-
Profile Overview
-
Profile Roles
-
Roles when Operating over an LE Physical Transport
- Observer Role
-
Roles when Operating over an LE Physical Transport
-
Profile Roles
-
Security Aspects — LE Physical Transport
-
Privacy Feature
- Privacy Feature in an Observer
-
Privacy Feature
-
Profile Overview
-
Generic Access Profile
-
Core System Package [Low Energy Controller volume]
-
Link Layer Specification
-
General Description
- Device Address
-
Air Interface Packets
-
Advertising Channel PDU
-
Advertising PDUs
- ADV_IND
- ADV_DIRECT_IND
- ADV_NONCONN_IND
- ADV_SCAN_IND
-
Advertising PDUs
-
Advertising Channel PDU
-
Air Interface Protocol
-
Non-Connected States
-
Scanning State
- Passive Scanning
- Active Scanning
-
Scanning State
-
Non-Connected States
-
General Description
-
Link Layer Specification
-
Architecture & Terminology Overview
- [BLUETOOTH-SUPPLEMENT6]
-
-
Data Types Specification
-
Data Types Definitions and Formats
- Service UUID
- Local Name also defines Shortened Local Name and Complete Local Name
- Manufacturer Specific Data
- Service Data
-
Data Types Definitions and Formats
-
Data Types Specification