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 run the following steps:
requestLEScan(options)
- Let global be this’s relevant global object.
- Let document be global’s associated Document.
-
If document is not allowed to use the policy-controlled feature named
"bluetooth", return a promise rejected with a "SecurityError"DOMException. -
If options.
acceptAllAdvertisementsistrue, and options.filtersis present, return a promise rejected with a "TypeError"DOMException.Note: There’s no need to include filters if all advertisements are being accepted.
-
If options.
acceptAllAdvertisementsisfalse, and options.filtersis either absent or empty, return a promise rejected with aTypeError.Note: An empty set of filters wouldn’t return any advertisements.
-
Let filters be
if options.Array.prototype.map.call(options.filters, filter=>newBluetoothLEScanFilter(filter))filtersis present, or an emptyFrozenArrayotherwise. If this throws an exception, return a promise rejected with that exception. -
Let scan be a new
BluetoothLEScaninstance whose fields are initialized as in the following table:Field Initial value filtersfilters keepRepeatedDevicesoptions. keepRepeatedDevicesacceptAllAdvertisementsoptions. acceptAllAdvertisementsactivetrue - Let promise be a new promise.
-
Run the following steps in parallel.
-
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", queue a global task on the Bluetooth task source given global to reject promise with a "NotAllowedError"DOMExceptionand abort these steps. -
Ensure the UA is scanning for BLE advertisements
in a mode that will receive at least
all advertisements matching scan (in
addition to 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.
-
Queue a global task on the Bluetooth task source given
global to run the following steps.
-
If the UA failed to start scanning, reject promise with
one of the following errors, and abort these steps:
- The UA doesn’t support scanning for advertisements
- "
NotSupportedError"DOMException - Bluetooth is turned off
- "
InvalidStateError"DOMException - Other reasons
- "
UnknownError"DOMException
- Let bluetooth be document’s associated Bluetooth.
-
Add scan to bluetooth.
[[activeScans]]. - Resolve promise with scan.
-
If the UA failed to start scanning, reject promise with
one of the following errors, and abort these steps:
-
Request permission to use
- Return promise.
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
acceptAllAdvertisementsto explicitly do it. -
Initialize
this.asmanufacturerDatanew BluetoothManufacturerDataFilter(init..manufacturerData) -
Initialize
this.asserviceDatanew BluetoothServiceDataFilter(init..serviceData) -
Initialize
this.asservicesifArray.prototype.map.call(init.services, service=>BluetoothUUID.getService(service))init.is present, or an emptyservicesFrozenArrayotherwise. -
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.toactivefalse. - Remove
thisfromnavigator.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]]()thishas no map entries. Abort these steps. -
Let canonicalInit be the
manufacturerDatafield 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]]()thishas no map entries. Abort these steps. -
Let canonicalInit be the
serviceDatafield 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 bydataPrefixcanonicalInit..dataPrefix -
Initialize
this.as a read only ArrayBuffer whose contents are a copy of the bytes held bymaskcanonicalInit..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
BluetoothLEScanPermissionDescriptordescriptor and aBluetoothLEScanPermissionResultresult:-
Update
result.to descriptor’s permission state.state -
If
result.is "statedenied", setresult.scansto an emptyFrozenArrayand abort these steps. -
Update
result.to a newscansFrozenArraycontaining 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
Bluetoothinstance 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
BluetoothLEScans 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
mayUseGATTset tofalse. -
Get the
BluetoothDevicerepresenting device inside bluetooth, and let deviceObj be the result. -
Add each
BluetoothLEScanin scans todeviceObj..[[returnedFromScans]] -
Fire an
advertisementreceivedevent 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.acceptAllAdvertisementsisfalseand event doesn’t match any filter inscan., returnfiltersno match. -
If
scan.iskeepRepeatedDevicesfalse, there is aBluetoothDevicedevice 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.nameis non-null, event has a Local Name equal tofilter.name.Note: A Shortened Local Name can match a
namefilter. -
If
filter.namePrefixis non-null, event has a Local Name, andfilter.namePrefixis 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