forked from WebBluetoothCG/web-bluetooth
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.bs
5251 lines (4646 loc) · 219 KB
/
index.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<pre class='metadata'>
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: <a href="https://www.w3.org/community/web-bluetooth/">Join the W3C Community Group</a>
!Participate: <a href="https://github.com/WebBluetoothCG/web-bluetooth">Fix the text through GitHub</a>
!Participate: <a href="mailto:public-web-bluetooth@w3.org">public-web-bluetooth@w3.org</a> (<a href="https://lists.w3.org/Archives/Public/public-web-bluetooth/" rel="discussion">archives</a>)
!Participate: <a href="irc://irc.w3.org:6665/#web-bluetooth">IRC: #web-bluetooth on W3C's IRC</a>
Markup Shorthands: css no, markdown yes
</pre>
<pre class=biblio>
{
"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"
}
}
</pre>
<pre class="anchors">
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/browsers.html#
type: dfn
text: browsing context; url: browsing-context
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
</pre>
<pre class="link-defaults">
spec:dom
type:dfn
text:children
spec: fingerprinting-guidance
type: dfn
text: fingerprinting surface
spec: html
type: dfn
text: browsing context; for: /
text: global object; for: /
spec: webidl
type: dfn
text: resolve
</pre>
<style>
.argument-list { display: inline-block; vertical-align: top; }
/* Show self-links for various elements. This is incompatible with nearby floats. */
.note, .why, .example, .issue { overflow: inherit; }
.unstable::before {
content: "This section is not stable.";
float: right;
color: red;
}
.unstable {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='300' height='290'><text transform='rotate(-45)' text-anchor='middle' font-family='sans-serif' font-weight='bold' font-size='70' y='210' opacity='.1'>Unstable</text></svg>");
background-repeat: repeat
}
.unstable.example:not(.no-marker)::before {
content: "Example " counter(example) " (Unstable)";
float: none;
}
.note {
background-color: rgba(145, 235, 145, .2);
}
.example {
background-color: rgba(240, 230, 170, .2);
}
.def {
background-color: rgba(85, 170, 255, .2);
}
.issue {
background-color: rgba(235, 145, 145, .2);
}
table {
border-collapse: collapse;
border-left-style: hidden;
border-right-style: hidden;
text-align: left;
}
table caption {
font-weight: bold;
padding: 3px;
text-align: left;
}
table td, table th {
border: 1px solid black;
padding: 3px;
}
</style>
# Introduction # {#introduction}
*This section is non-normative.*
<a href="https://developer.bluetooth.org/">Bluetooth</a> is a standard for
short-range wireless communication between devices. Bluetooth "Classic" (<abbr
title="Basic Rate">BR</abbr>/<abbr title="Enhanced Data Rate">EDR</abbr>)
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",
<abbr title="Bluetooth Low Energy">BLE</abbr>, or just <abbr title="Low
Energy">LE</abbr>
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 <a lt="Generic Attribute Profile">Generic Attribute
Profile (<abbr title="Generic Attribute Profile">GATT</abbr>)</a>.
BLE defines multiple roles that devices can play. The <a>Broadcaster</a> and
<a>Observer</a> roles are for transmitter- and receiver-only applications,
respectively. Devices acting in the <a>Peripheral</a> role can receive
connections, and devices acting in the <a>Central</a> role can connect to
<a>Peripheral</a> devices.
A device acting in either the <a>Peripheral</a> or <a>Central</a> role can host
a <a>GATT Server</a>, which exposes a hierarchy of <a>Service</a>s,
<a>Characteristic</a>s, and <a>Descriptor</a>s. 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
<a>Central</a> role, to connect to <a>GATT Server</a>s 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}
<div class="example" id="example-heart-rate-monitor">
To discover and retrieve data from a standard heart rate monitor,
a website would use code like the following:
<pre highlight="js">
let chosenHeartRateService = null;
navigator.bluetooth.<a idl for="Bluetooth" lt="requestDevice()">requestDevice</a>({
filters: [{
services: ['heart_rate'],
}]
}).then(device => device.gatt.<a for="BluetoothRemoteGATTServer">connect()</a>)
.then(server => server.<a idl for="BluetoothRemoteGATTServer" lt="getPrimaryService()"
>getPrimaryService</a>(<a idl lt="heart_rate">'heart_rate'</a>))
.then(service => {
chosenHeartRateService = service;
return Promise.all([
service.<a idl for="BluetoothRemoteGATTService" lt="getCharacteristic()">getCharacteristic</a>(<a idl lt="body_sensor_location">'body_sensor_location'</a>)
.then(handleBodySensorLocationCharacteristic),
service.<a idl for="BluetoothRemoteGATTService" lt="getCharacteristic()">getCharacteristic</a>(<a idl lt="heart_rate_measurement">'heart_rate_measurement'</a>)
.then(handleHeartRateMeasurementCharacteristic),
]);
});
function handleBodySensorLocationCharacteristic(characteristic) {
if (characteristic === null) {
console.log("Unknown sensor location.");
return Promise.resolve();
}
return characteristic.<a for="BluetoothRemoteGATTCharacteristic">readValue()</a>
.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.<a for="BluetoothRemoteGATTCharacteristic">startNotifications()</a>
.then(char => {
characteristic.addEventListener('<a event>characteristicvaluechanged</a>',
onHeartRateChanged);
});
}
function onHeartRateChanged(event) {
const characteristic = event.target;
console.log(parseHeartRate(characteristic.<a attribute for="BluetoothRemoteGATTCharacteristic">value</a>));
}
</pre>
<code>parseHeartRate()</code> would be defined using the
<a idl lt="heart_rate_measurement">
<code>heart_rate_measurement</code> documentation</a>
to read the {{DataView}} stored in a {{BluetoothRemoteGATTCharacteristic}}'s
{{BluetoothRemoteGATTCharacteristic/value}} field.
<pre highlight="js">
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;
}
</pre>
<code>onHeartRateChanged()</code> might log an object like
<pre highlight="js">
{
heartRate: 70,
contactDetected: true,
energyExpended: 750, // Meaning 750kJ.
rrIntervals: [890, 870] // Meaning .87s and .85s.
}
</pre>
If the heart rate sensor reports the <code>energyExpended</code> field, the web
application can reset its value to <code>0</code> by writing to the
{{heart_rate_control_point}}
characteristic:
<pre highlight="js">
function resetEnergyExpended() {
if (!chosenHeartRateService) {
return Promise.reject(new Error('No heart rate sensor selected yet.'));
}
return chosenHeartRateService.<a idl for="BluetoothRemoteGATTService" lt="getCharacteristic()">getCharacteristic</a>(<a idl lt="heart_rate_control_point">'heart_rate_control_point'</a>)
.then(controlPoint => {
const resetEnergyExpended = new Uint8Array([1]);
return controlPoint.<a idl for="BluetoothRemoteGATTCharacteristic" lt="writeValue()">writeValue</a>(resetEnergyExpended);
});
}
</pre>
</div>
# Security considerations # {#security-and-privacy}
Issue(575): See <a href="#privacy"></a> 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 <a lt="revoke Bluetooth
access">revoke</a> 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 <a>secure contexts</a> 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 <a
href="#permission-api-integration"></a>, 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 <a>GATT blocklist</a> 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 <a>GATT</a>, as opposed to generic byte-stream access,
denies malicious websites access to most parsers on the device.
On the other hand, GATT's <a>Characteristic</a> and <a>Descriptor</a> 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
(<a>Definition of Keys and Values</a>) to websites. This makes it more
difficult for a website to predict the bits that will be sent over the
radio, which blocks <a
href="https://www.usenix.org/legacy/events/woot11/tech/final_files/Goodspeed.pdf">packet-in-packet
injection attacks</a>. 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
<a>BD_ADDR</a>. Each Bluetooth LE device has at least one of a <a>Public Device
Address</a> and a <a>Static Device Address</a>. The <a>Public Device Address</a>
is a MAC address. The <a>Static Device Address</a> may be regenerated on each
restart. A BR/EDR/LE device will use the same value for the <a>BD_ADDR</a> and
the <a>Public Device Address</a> (specified in the <a>Read BD_ADDR Command</a>).
An LE device may also have a unique, 128-bit <a>Identity Resolving Key</a>,
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 <a>Private Address</a> 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 <a>IRK</a>s
matches any given Resolvable Private Address using the <a>Resolvable Private
Address Resolution Procedure</a>.
Each Bluetooth device also has a human-readable <a>Bluetooth Device Name</a>.
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 <a href="#note-device-id-tracking">suggests</a> 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 <a>Privacy
Feature</a>, 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 <a>Privacy
Feature</a>, so despite this spec recommending it, few UAs are likely to use it.
This spec does <a href="#requestDevice-user-gesture">require a user gesture</a>
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 <a>Privacy
Feature</a>.
## Exposing Bluetooth availability ## {#availability-fingerprint}
*This section is non-normative.*
<code>navigator.bluetooth.{{getAvailability()}}</code> 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 <a>fingerprinting surface</a> 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}
<xmp class="idl">
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;
</xmp>
<div class="note" heading="{{Bluetooth}} members">
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.
<p class="unstable">
{{Bluetooth/referringDevice}} gives access to the device from which the user
opened this page, if any. For example, an <a
href="https://developers.google.com/beacons/eddystone">Eddystone</a> beacon
might advertise a URL, which the UA allows the user to open. A
{{BluetoothDevice}} representing the beacon would be available through
<code>navigator.bluetooth.{{referringDevice}}</code>.
</p>
<p class="unstable">
{{Bluetooth/getDevices()}} enables the page to retrieve Bluetooth devices that
the user has granted access to.
</p>
{{Bluetooth/requestDevice(options)}} asks the user to grant this origin access
to a device that <a>matches any filter</a> in <code>options.<dfn dict-member
for="RequestDeviceOptions">filters</dfn></code> but does not <a>match any filter</a>
in <code>options.<dfn dict-member for="RequestDeviceOptions">exclusionFilters</dfn></code>.
To <a>match a filter</a>, the device has to:
* support <em>all</em> the GATT service UUIDs in the <dfn dict-member
for="BluetoothLEScanFilterInit">services</dfn> list if that member is
present,
* have a name equal to <dfn dict-member
for="BluetoothLEScanFilterInit">name</dfn> if that member is present,
* have a name starting with <dfn dict-member
for="BluetoothLEScanFilterInit">namePrefix</dfn> if that member is present,
* advertise <a>manufacturer specific data</a> matching all of the values in
<dfn dict-member for="BluetoothLEScanFilterInit">manufacturerData</dfn> if
that member is present, and
* <div class="unstable"> advertise <a>service data</a> matching all of the
values in <dfn dict-member
for="BluetoothLEScanFilterInit">serviceData</dfn> if that member is
present.</div>
<p link-for-hint="BluetoothDataFilterInit">
Both <a>Manufacturer Specific Data</a> and <a>Service Data</a> map a key to an
array of bytes. {{BluetoothDataFilterInit}} filters these arrays. An array
matches if it has a |prefix| such that <code>|prefix| & {{mask}}</code> is
equal to <code>{{dataPrefix}} & {{mask}}</code>.
</p>
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 <dfn
dict-member for="RequestDeviceOptions">acceptAllDevices</dfn> 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
<code>options.filters</code> or in <code>options.<dfn dict-member
for="RequestDeviceOptions">optionalServices</dfn></code>. The origin is also
allowed to access any manufacturer data from manufacturer codes defined in
<code>options.<dfn dict-member
for="RequestDeviceOptions">optionalManufacturerData</dfn></code> 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.
</div>
<div class="example" id="example-filter-by-services">
Say the UA is close to the following devices:
<table>
<tr>
<th>Device</th>
<th>Advertised Services</th>
</tr>
<tr>
<td>D1</td>
<td>A, B, C, D</td>
</tr>
<tr>
<td>D2</td>
<td>A, B, E</td>
</tr>
<tr>
<td>D3</td>
<td>C, D</td>
</tr>
<tr>
<td>D4</td>
<td>E</td>
</tr>
<tr>
<td>D5</td>
<td><i><none></i></td>
</tr>
</table>
If the website calls
<pre highlight="js">
navigator.bluetooth.requestDevice({
filters: [ {services: [A, B]} ]
});
</pre>
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
<pre highlight="js">
navigator.bluetooth.requestDevice({
filters: [
{services: [A, B]},
{services: [C, D]}
]
});
</pre>
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
<pre highlight="js">
navigator.bluetooth.getDevices();
</pre>
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 <code>optionalServices</code> 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.
<pre highlight="js">
navigator.bluetooth.requestDevice({
filters: [ {services: [A, B]} ],
optionalServices: \[E]
});
</pre>
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
<pre highlight="js">
navigator.bluetooth.getDevices();
</pre>
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
<code>requestDevice()</code> 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.
</div>
<div class="example" id="example-filter-by-name">
Say the devices in the <a href="#example-filter-by-services">previous
example</a> also advertise names as follows:
<table>
<tr>
<th>Device</th>
<th>Advertised Device Name</th>
</tr>
<tr>
<td>D1</td>
<td>First De…</td>
</tr>
<tr>
<td>D2</td>
<td><i><none></i></td>
</tr>
<tr>
<td>D3</td>
<td>Device Third</td>
</tr>
<tr>
<td>D4</td>
<td>Device Fourth</td>
</tr>
<tr>
<td>D5</td>
<td>Unique Name</td>
</tr>
</table>
The following table shows which devices the user can select between
for several values of <var>filters</var> passed to
<code>navigator.bluetooth.requestDevice({filters: <var>filters</var>})</code>.
<table>
<tr>
<th><var>filters</var></th>
<th>Devices</th><th>Notes</th>
</tr>
<tr>
<td>
<pre highlight="js">
[{name: "Unique Name"}]
</pre>
</td>
<td>D5</td>
<td></td>
</tr>
<tr>
<td>
<pre highlight="js">
[{namePrefix: "Device"}]
</pre>
</td>
<td>D3, D4</td>
<td></td>
</tr>
<tr>
<td>
<pre highlight="js">
[{name: "First De"},
{name: "First Device"}]
</pre>
</td>
<td><i><none></i></td>
<td>
D1 only advertises a prefix of its name, so trying to match its whole name
fails.
</td>
</tr>
<tr>
<td>
<pre highlight="js">
[{namePrefix: "First"},
{name: "Unique Name"}]
</pre>
</td>
<td>D1, D5</td>
<td></td>
</tr>
<tr>
<td>
<pre highlight="js">
[{services: \[C],
namePrefix: "Device"},
{name: "Unique Name"}]
</pre>
</td>
<td>D3, D5</td>
<td></td>
</tr>
</table>
The following table shows which devices the user can select between
for several values of <var>filters</var> and <var>exclusionFilters</var>
passed to <code>navigator.bluetooth.requestDevice({filters: <var>filters</var>,
exclusionFilters: <var>exclusionFilters</var>})</code>.
<table>
<tr>
<th><var>filters</var></th>
<th><var>exclusionFilters</var></th>
<th>Devices</th>
</tr>
<tr>
<td>
<pre highlight="js">
[{namePrefix: "Device"}] // D3, D4
</pre>
</td>
<td>
<pre highlight="js">
[{name: "Device Third"}] // D3
</pre>
</td>
<td>D4</td>
</tr>
<tr>
<td>
<pre highlight="js">
[{namePrefix: "Device"}] // D3, D4
</pre>
</td>
<td>
<pre highlight="js">
[{namePrefix: "Device F"}] // D4
</pre>
</td>
<td>D3</td>
</tr>
<tr>
<td>
<pre highlight="js">
[{services: \[C]}, // D1, D3
{namePrefix: "Device"}] // D3, D4
</pre>
</td>
<td>
<pre highlight="js">
[{services: \[A]}, // D1
{name: "Device Fourth"}] // D4
</pre>
</td>
<td>D3</td>
</tr>
</table>
</div>
<div class="example" id="example-filter-by-manufacturer-service-data">
Say the devices in the <a href="#example-filter-by-services">previous
example</a> also advertise manufacturer or service data as follows:
<table>
<tr>
<th>Device</th>
<th>Manufacturer Data</th>
<th>Service Data</th>
</tr>
<tr>
<td>D1</td>
<td>17: 01 02 03</td>
<td></td>
</tr>
<tr>
<td>D2</td>
<td></td>
<td>A: 01 02 03</td>
</tr>
</table>
The following table shows which devices the user can select between for several
values of <var>filters</var> passed to
<code>navigator.bluetooth.requestDevice({filters: <var>filters</var>})</code>.
<table>
<tr>
<th><var>filters</var></th>
<th>Devices</th>
</tr>
<tr>
<td>
<pre highlight="js">
[{ manufacturerData: [{ companyIdentifier: 17 }] }]
</pre>
</td>
<td>D1</td>
</tr>
<tr>
<td>
<pre highlight="js">
[{ serviceData: [{ service: "A" }] }]
</pre>
</td>
<td>D2</td>
</tr>
<tr>
<td>
<pre highlight="js">
[
{ manufacturerData: [{ companyIdentifier: 17 }] },
{ serviceData: [{ service: "A" }] },
]
</pre>
</td>
<td>D1, D2</td>
</tr>
<tr>
<td>
<pre highlight="js">
[
{
manufacturerData: [{ companyIdentifier: 17 }],
serviceData: [{ service: "A" }],
},
]
</pre>
</td>
<td><i><none></i></td>
</tr>
<tr>
<td>
<pre highlight="js">
[
{
manufacturerData: [
{
companyIdentifier: 17,
dataPrefix: new Uint8Array([1, 2, 3])
},
],
},
]
</pre>
</td>
<td>D1</td>
</tr>
<tr>
<td>
<pre highlight="js">
[
{
manufacturerData: [
{
companyIdentifier: 17,
dataPrefix: new Uint8Array([1, 2, 3, 4])
},
],
},
]
</pre>
</td>
<td><i><none></i></td>
</tr>
<tr>
<td>
<pre highlight="js">
[
{
manufacturerData: [
{
companyIdentifier: 17,
dataPrefix: new Uint8Array([1])
},
],
},
]
</pre>
</td>
<td>D1</td>
</tr>
<tr>
<td>
<pre highlight="js">
[
{
manufacturerData: [
{
companyIdentifier: 17,
dataPrefix: new Uint8Array([0x91, 0xAA]),
mask: new Uint8Array([0x0f, 0x57]),