@capacitor-community/bluetooth-le 8.0.0 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/docs.json +43 -43
- package/dist/esm/bleClient.d.ts +278 -278
- package/dist/esm/bleClient.js +361 -361
- package/dist/esm/bleClient.js.map +1 -1
- package/dist/esm/config.d.ts +53 -53
- package/dist/esm/config.js +2 -2
- package/dist/esm/conversion.d.ts +56 -56
- package/dist/esm/conversion.js +134 -134
- package/dist/esm/conversion.js.map +1 -1
- package/dist/esm/definitions.d.ts +352 -352
- package/dist/esm/definitions.js +42 -42
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +5 -5
- package/dist/esm/index.js +5 -5
- package/dist/esm/plugin.d.ts +2 -2
- package/dist/esm/plugin.js +4 -4
- package/dist/esm/queue.d.ts +3 -3
- package/dist/esm/queue.js +17 -17
- package/dist/esm/queue.js.map +1 -1
- package/dist/esm/timeout.d.ts +1 -1
- package/dist/esm/timeout.js +9 -9
- package/dist/esm/validators.d.ts +1 -1
- package/dist/esm/validators.js +11 -11
- package/dist/esm/validators.js.map +1 -1
- package/dist/esm/web.d.ts +57 -57
- package/dist/esm/web.js +403 -403
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +964 -964
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +964 -964
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/BluetoothLe/DeviceManager.swift +2 -90
- package/ios/Sources/BluetoothLe/Plugin.swift +0 -12
- package/ios/Sources/BluetoothLe/ScanFilters.swift +114 -0
- package/ios/Tests/BluetoothLeTests/ScanFiltersTests.swift +153 -0
- package/package.json +2 -3
package/dist/plugin.js
CHANGED
|
@@ -1,984 +1,984 @@
|
|
|
1
1
|
var capacitorCommunityBluetoothLe = (function (exports, core) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Android scan mode
|
|
6
|
-
*/
|
|
7
|
-
exports.ScanMode = void 0;
|
|
8
|
-
(function (ScanMode) {
|
|
9
|
-
/**
|
|
10
|
-
* Perform Bluetooth LE scan in low power mode. This mode is enforced if the scanning application is not in foreground.
|
|
11
|
-
* https://developer.android.com/reference/android/bluetooth/le/ScanSettings#SCAN_MODE_LOW_POWER
|
|
12
|
-
*/
|
|
13
|
-
ScanMode[ScanMode["SCAN_MODE_LOW_POWER"] = 0] = "SCAN_MODE_LOW_POWER";
|
|
14
|
-
/**
|
|
15
|
-
* Perform Bluetooth LE scan in balanced power mode. (default) Scan results are returned at a rate that provides a good trade-off between scan frequency and power consumption.
|
|
16
|
-
* https://developer.android.com/reference/android/bluetooth/le/ScanSettings#SCAN_MODE_BALANCED
|
|
17
|
-
*/
|
|
18
|
-
ScanMode[ScanMode["SCAN_MODE_BALANCED"] = 1] = "SCAN_MODE_BALANCED";
|
|
19
|
-
/**
|
|
20
|
-
* Scan using highest duty cycle. It's recommended to only use this mode when the application is running in the foreground.
|
|
21
|
-
* https://developer.android.com/reference/android/bluetooth/le/ScanSettings#SCAN_MODE_LOW_LATENCY
|
|
22
|
-
*/
|
|
23
|
-
ScanMode[ScanMode["SCAN_MODE_LOW_LATENCY"] = 2] = "SCAN_MODE_LOW_LATENCY";
|
|
24
|
-
})(exports.ScanMode || (exports.ScanMode = {}));
|
|
25
|
-
/**
|
|
26
|
-
* Android connection priority used in `requestConnectionPriority`
|
|
27
|
-
*/
|
|
28
|
-
exports.ConnectionPriority = void 0;
|
|
29
|
-
(function (ConnectionPriority) {
|
|
30
|
-
/**
|
|
31
|
-
* Use the connection parameters recommended by the Bluetooth SIG. This is the default value if no connection parameter update is requested.
|
|
32
|
-
* https://developer.android.com/reference/android/bluetooth/BluetoothGatt#CONNECTION_PRIORITY_BALANCED
|
|
33
|
-
*/
|
|
34
|
-
ConnectionPriority[ConnectionPriority["CONNECTION_PRIORITY_BALANCED"] = 0] = "CONNECTION_PRIORITY_BALANCED";
|
|
35
|
-
/**
|
|
36
|
-
* Request a high priority, low latency connection. An application should only request high priority connection parameters to transfer large amounts of data over LE quickly. Once the transfer is complete, the application should request CONNECTION_PRIORITY_BALANCED connection parameters to reduce energy use.
|
|
37
|
-
* https://developer.android.com/reference/android/bluetooth/BluetoothGatt#CONNECTION_PRIORITY_HIGH
|
|
38
|
-
*/
|
|
39
|
-
ConnectionPriority[ConnectionPriority["CONNECTION_PRIORITY_HIGH"] = 1] = "CONNECTION_PRIORITY_HIGH";
|
|
40
|
-
/**
|
|
41
|
-
* Request low power, reduced data rate connection parameters.
|
|
42
|
-
* https://developer.android.com/reference/android/bluetooth/BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER
|
|
43
|
-
*/
|
|
44
|
-
ConnectionPriority[ConnectionPriority["CONNECTION_PRIORITY_LOW_POWER"] = 2] = "CONNECTION_PRIORITY_LOW_POWER";
|
|
4
|
+
/**
|
|
5
|
+
* Android scan mode
|
|
6
|
+
*/
|
|
7
|
+
exports.ScanMode = void 0;
|
|
8
|
+
(function (ScanMode) {
|
|
9
|
+
/**
|
|
10
|
+
* Perform Bluetooth LE scan in low power mode. This mode is enforced if the scanning application is not in foreground.
|
|
11
|
+
* https://developer.android.com/reference/android/bluetooth/le/ScanSettings#SCAN_MODE_LOW_POWER
|
|
12
|
+
*/
|
|
13
|
+
ScanMode[ScanMode["SCAN_MODE_LOW_POWER"] = 0] = "SCAN_MODE_LOW_POWER";
|
|
14
|
+
/**
|
|
15
|
+
* Perform Bluetooth LE scan in balanced power mode. (default) Scan results are returned at a rate that provides a good trade-off between scan frequency and power consumption.
|
|
16
|
+
* https://developer.android.com/reference/android/bluetooth/le/ScanSettings#SCAN_MODE_BALANCED
|
|
17
|
+
*/
|
|
18
|
+
ScanMode[ScanMode["SCAN_MODE_BALANCED"] = 1] = "SCAN_MODE_BALANCED";
|
|
19
|
+
/**
|
|
20
|
+
* Scan using highest duty cycle. It's recommended to only use this mode when the application is running in the foreground.
|
|
21
|
+
* https://developer.android.com/reference/android/bluetooth/le/ScanSettings#SCAN_MODE_LOW_LATENCY
|
|
22
|
+
*/
|
|
23
|
+
ScanMode[ScanMode["SCAN_MODE_LOW_LATENCY"] = 2] = "SCAN_MODE_LOW_LATENCY";
|
|
24
|
+
})(exports.ScanMode || (exports.ScanMode = {}));
|
|
25
|
+
/**
|
|
26
|
+
* Android connection priority used in `requestConnectionPriority`
|
|
27
|
+
*/
|
|
28
|
+
exports.ConnectionPriority = void 0;
|
|
29
|
+
(function (ConnectionPriority) {
|
|
30
|
+
/**
|
|
31
|
+
* Use the connection parameters recommended by the Bluetooth SIG. This is the default value if no connection parameter update is requested.
|
|
32
|
+
* https://developer.android.com/reference/android/bluetooth/BluetoothGatt#CONNECTION_PRIORITY_BALANCED
|
|
33
|
+
*/
|
|
34
|
+
ConnectionPriority[ConnectionPriority["CONNECTION_PRIORITY_BALANCED"] = 0] = "CONNECTION_PRIORITY_BALANCED";
|
|
35
|
+
/**
|
|
36
|
+
* Request a high priority, low latency connection. An application should only request high priority connection parameters to transfer large amounts of data over LE quickly. Once the transfer is complete, the application should request CONNECTION_PRIORITY_BALANCED connection parameters to reduce energy use.
|
|
37
|
+
* https://developer.android.com/reference/android/bluetooth/BluetoothGatt#CONNECTION_PRIORITY_HIGH
|
|
38
|
+
*/
|
|
39
|
+
ConnectionPriority[ConnectionPriority["CONNECTION_PRIORITY_HIGH"] = 1] = "CONNECTION_PRIORITY_HIGH";
|
|
40
|
+
/**
|
|
41
|
+
* Request low power, reduced data rate connection parameters.
|
|
42
|
+
* https://developer.android.com/reference/android/bluetooth/BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER
|
|
43
|
+
*/
|
|
44
|
+
ConnectionPriority[ConnectionPriority["CONNECTION_PRIORITY_LOW_POWER"] = 2] = "CONNECTION_PRIORITY_LOW_POWER";
|
|
45
45
|
})(exports.ConnectionPriority || (exports.ConnectionPriority = {}));
|
|
46
46
|
|
|
47
|
-
/**
|
|
48
|
-
* Convert an array of numbers into a DataView.
|
|
49
|
-
*/
|
|
50
|
-
function numbersToDataView(value) {
|
|
51
|
-
return new DataView(Uint8Array.from(value).buffer);
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Convert a DataView into an array of numbers.
|
|
55
|
-
*/
|
|
56
|
-
function dataViewToNumbers(value) {
|
|
57
|
-
return Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Convert a string into a DataView.
|
|
61
|
-
*/
|
|
62
|
-
function textToDataView(value) {
|
|
63
|
-
return numbersToDataView(value.split('').map((s) => s.charCodeAt(0)));
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Convert a DataView into a string.
|
|
67
|
-
*/
|
|
68
|
-
function dataViewToText(value) {
|
|
69
|
-
return String.fromCharCode(...dataViewToNumbers(value));
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Convert a 16 bit UUID into a 128 bit UUID string
|
|
73
|
-
* @param value number, e.g. 0x180d
|
|
74
|
-
* @return string, e.g. '0000180d-0000-1000-8000-00805f9b34fb'
|
|
75
|
-
*/
|
|
76
|
-
function numberToUUID(value) {
|
|
77
|
-
return `0000${value.toString(16).padStart(4, '0')}-0000-1000-8000-00805f9b34fb`;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Convert a string of hex into a DataView of raw bytes.
|
|
81
|
-
* Note: characters other than [0-9a-fA-F] are ignored
|
|
82
|
-
* @param hex string of values, e.g. "00 01 02" or "000102"
|
|
83
|
-
* @return DataView of raw bytes
|
|
84
|
-
*/
|
|
85
|
-
function hexStringToDataView(hex) {
|
|
86
|
-
const bin = [];
|
|
87
|
-
let i, c, isEmpty = 1, buffer = 0;
|
|
88
|
-
for (i = 0; i < hex.length; i++) {
|
|
89
|
-
c = hex.charCodeAt(i);
|
|
90
|
-
if ((c > 47 && c < 58) || (c > 64 && c < 71) || (c > 96 && c < 103)) {
|
|
91
|
-
buffer = (buffer << 4) ^ ((c > 64 ? c + 9 : c) & 15);
|
|
92
|
-
if ((isEmpty ^= 1)) {
|
|
93
|
-
bin.push(buffer & 0xff);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return numbersToDataView(bin);
|
|
98
|
-
}
|
|
99
|
-
function dataViewToHexString(value) {
|
|
100
|
-
return dataViewToNumbers(value)
|
|
101
|
-
.map((n) => {
|
|
102
|
-
let s = n.toString(16);
|
|
103
|
-
if (s.length == 1) {
|
|
104
|
-
s = '0' + s;
|
|
105
|
-
}
|
|
106
|
-
return s;
|
|
107
|
-
})
|
|
108
|
-
.join('');
|
|
109
|
-
}
|
|
110
|
-
function webUUIDToString(uuid) {
|
|
111
|
-
if (typeof uuid === 'string') {
|
|
112
|
-
return uuid;
|
|
113
|
-
}
|
|
114
|
-
else if (typeof uuid === 'number') {
|
|
115
|
-
return numberToUUID(uuid);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
throw new Error('Invalid UUID');
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function mapToObject(map) {
|
|
122
|
-
const obj = {};
|
|
123
|
-
if (!map) {
|
|
124
|
-
return undefined;
|
|
125
|
-
}
|
|
126
|
-
map.forEach((value, key) => {
|
|
127
|
-
obj[key.toString()] = value;
|
|
128
|
-
});
|
|
129
|
-
return obj;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Convert Data or Uint8Array to Uint8Array.
|
|
133
|
-
* @param value DataView, Uint8Array, or undefined
|
|
134
|
-
* @return Uint8Array or undefined
|
|
135
|
-
*/
|
|
136
|
-
function toUint8Array(value) {
|
|
137
|
-
if (value === undefined) {
|
|
138
|
-
return undefined;
|
|
139
|
-
}
|
|
140
|
-
if (typeof value === 'string') {
|
|
141
|
-
const dataView = hexStringToDataView(value);
|
|
142
|
-
return new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength);
|
|
143
|
-
}
|
|
144
|
-
if (value instanceof DataView) {
|
|
145
|
-
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
146
|
-
}
|
|
147
|
-
return value; // Already Uint8Array
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Convert Data, Uint8Array, or string to hex string.
|
|
151
|
-
* @param value DataView, Uint8Array, or undefined
|
|
152
|
-
* @return hex string or undefined
|
|
153
|
-
*/
|
|
154
|
-
function toHexString(value) {
|
|
155
|
-
if (value === undefined) {
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
158
|
-
if (value instanceof DataView) {
|
|
159
|
-
return dataViewToHexString(value);
|
|
160
|
-
}
|
|
161
|
-
// Uint8Array
|
|
162
|
-
return dataViewToHexString(new DataView(value.buffer, value.byteOffset, value.byteLength));
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Convert a DataView to a DataView backed by an ArrayBuffer.
|
|
166
|
-
* If the DataView is backed by a SharedArrayBuffer, this creates a copy.
|
|
167
|
-
* Otherwise, returns the original DataView.
|
|
168
|
-
* @param value DataView to convert
|
|
169
|
-
* @return DataView backed by ArrayBuffer
|
|
170
|
-
*/
|
|
171
|
-
function toArrayBufferDataView(value) {
|
|
172
|
-
if (value.buffer instanceof SharedArrayBuffer) {
|
|
173
|
-
// Need to copy to a regular ArrayBuffer
|
|
174
|
-
const uint8Array = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
175
|
-
const buffer = uint8Array.slice().buffer;
|
|
176
|
-
return new DataView(buffer);
|
|
177
|
-
}
|
|
178
|
-
// Already an ArrayBuffer, use directly
|
|
179
|
-
return value;
|
|
47
|
+
/**
|
|
48
|
+
* Convert an array of numbers into a DataView.
|
|
49
|
+
*/
|
|
50
|
+
function numbersToDataView(value) {
|
|
51
|
+
return new DataView(Uint8Array.from(value).buffer);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Convert a DataView into an array of numbers.
|
|
55
|
+
*/
|
|
56
|
+
function dataViewToNumbers(value) {
|
|
57
|
+
return Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Convert a string into a DataView.
|
|
61
|
+
*/
|
|
62
|
+
function textToDataView(value) {
|
|
63
|
+
return numbersToDataView(value.split('').map((s) => s.charCodeAt(0)));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Convert a DataView into a string.
|
|
67
|
+
*/
|
|
68
|
+
function dataViewToText(value) {
|
|
69
|
+
return String.fromCharCode(...dataViewToNumbers(value));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Convert a 16 bit UUID into a 128 bit UUID string
|
|
73
|
+
* @param value number, e.g. 0x180d
|
|
74
|
+
* @return string, e.g. '0000180d-0000-1000-8000-00805f9b34fb'
|
|
75
|
+
*/
|
|
76
|
+
function numberToUUID(value) {
|
|
77
|
+
return `0000${value.toString(16).padStart(4, '0')}-0000-1000-8000-00805f9b34fb`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Convert a string of hex into a DataView of raw bytes.
|
|
81
|
+
* Note: characters other than [0-9a-fA-F] are ignored
|
|
82
|
+
* @param hex string of values, e.g. "00 01 02" or "000102"
|
|
83
|
+
* @return DataView of raw bytes
|
|
84
|
+
*/
|
|
85
|
+
function hexStringToDataView(hex) {
|
|
86
|
+
const bin = [];
|
|
87
|
+
let i, c, isEmpty = 1, buffer = 0;
|
|
88
|
+
for (i = 0; i < hex.length; i++) {
|
|
89
|
+
c = hex.charCodeAt(i);
|
|
90
|
+
if ((c > 47 && c < 58) || (c > 64 && c < 71) || (c > 96 && c < 103)) {
|
|
91
|
+
buffer = (buffer << 4) ^ ((c > 64 ? c + 9 : c) & 15);
|
|
92
|
+
if ((isEmpty ^= 1)) {
|
|
93
|
+
bin.push(buffer & 0xff);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return numbersToDataView(bin);
|
|
98
|
+
}
|
|
99
|
+
function dataViewToHexString(value) {
|
|
100
|
+
return dataViewToNumbers(value)
|
|
101
|
+
.map((n) => {
|
|
102
|
+
let s = n.toString(16);
|
|
103
|
+
if (s.length == 1) {
|
|
104
|
+
s = '0' + s;
|
|
105
|
+
}
|
|
106
|
+
return s;
|
|
107
|
+
})
|
|
108
|
+
.join('');
|
|
109
|
+
}
|
|
110
|
+
function webUUIDToString(uuid) {
|
|
111
|
+
if (typeof uuid === 'string') {
|
|
112
|
+
return uuid;
|
|
113
|
+
}
|
|
114
|
+
else if (typeof uuid === 'number') {
|
|
115
|
+
return numberToUUID(uuid);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
throw new Error('Invalid UUID');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function mapToObject(map) {
|
|
122
|
+
const obj = {};
|
|
123
|
+
if (!map) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
map.forEach((value, key) => {
|
|
127
|
+
obj[key.toString()] = value;
|
|
128
|
+
});
|
|
129
|
+
return obj;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Convert Data or Uint8Array to Uint8Array.
|
|
133
|
+
* @param value DataView, Uint8Array, or undefined
|
|
134
|
+
* @return Uint8Array or undefined
|
|
135
|
+
*/
|
|
136
|
+
function toUint8Array(value) {
|
|
137
|
+
if (value === undefined) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
if (typeof value === 'string') {
|
|
141
|
+
const dataView = hexStringToDataView(value);
|
|
142
|
+
return new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength);
|
|
143
|
+
}
|
|
144
|
+
if (value instanceof DataView) {
|
|
145
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
146
|
+
}
|
|
147
|
+
return value; // Already Uint8Array
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Convert Data, Uint8Array, or string to hex string.
|
|
151
|
+
* @param value DataView, Uint8Array, or undefined
|
|
152
|
+
* @return hex string or undefined
|
|
153
|
+
*/
|
|
154
|
+
function toHexString(value) {
|
|
155
|
+
if (value === undefined) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
if (value instanceof DataView) {
|
|
159
|
+
return dataViewToHexString(value);
|
|
160
|
+
}
|
|
161
|
+
// Uint8Array
|
|
162
|
+
return dataViewToHexString(new DataView(value.buffer, value.byteOffset, value.byteLength));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Convert a DataView to a DataView backed by an ArrayBuffer.
|
|
166
|
+
* If the DataView is backed by a SharedArrayBuffer, this creates a copy.
|
|
167
|
+
* Otherwise, returns the original DataView.
|
|
168
|
+
* @param value DataView to convert
|
|
169
|
+
* @return DataView backed by ArrayBuffer
|
|
170
|
+
*/
|
|
171
|
+
function toArrayBufferDataView(value) {
|
|
172
|
+
if (value.buffer instanceof SharedArrayBuffer) {
|
|
173
|
+
// Need to copy to a regular ArrayBuffer
|
|
174
|
+
const uint8Array = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
175
|
+
const buffer = uint8Array.slice().buffer;
|
|
176
|
+
return new DataView(buffer);
|
|
177
|
+
}
|
|
178
|
+
// Already an ArrayBuffer, use directly
|
|
179
|
+
return value;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
const BluetoothLe = core.registerPlugin('BluetoothLe', {
|
|
183
|
-
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.BluetoothLeWeb()),
|
|
182
|
+
const BluetoothLe = core.registerPlugin('BluetoothLe', {
|
|
183
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.BluetoothLeWeb()),
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
-
const makeQueue = () => {
|
|
187
|
-
let currentTask = Promise.resolve();
|
|
188
|
-
// create a new promise so that errors can be bubbled
|
|
189
|
-
// up to the caller without being caught by the queue
|
|
190
|
-
return (fn) => new Promise((resolve, reject) => {
|
|
191
|
-
currentTask = currentTask
|
|
192
|
-
.then(() => fn())
|
|
193
|
-
.then(resolve)
|
|
194
|
-
.catch(reject);
|
|
195
|
-
});
|
|
196
|
-
};
|
|
197
|
-
function getQueue(enabled) {
|
|
198
|
-
if (enabled) {
|
|
199
|
-
return makeQueue();
|
|
200
|
-
}
|
|
201
|
-
return (fn) => fn();
|
|
186
|
+
const makeQueue = () => {
|
|
187
|
+
let currentTask = Promise.resolve();
|
|
188
|
+
// create a new promise so that errors can be bubbled
|
|
189
|
+
// up to the caller without being caught by the queue
|
|
190
|
+
return (fn) => new Promise((resolve, reject) => {
|
|
191
|
+
currentTask = currentTask
|
|
192
|
+
.then(() => fn())
|
|
193
|
+
.then(resolve)
|
|
194
|
+
.catch(reject);
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
function getQueue(enabled) {
|
|
198
|
+
if (enabled) {
|
|
199
|
+
return makeQueue();
|
|
200
|
+
}
|
|
201
|
+
return (fn) => fn();
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
function parseUUID(uuid) {
|
|
205
|
-
if (typeof uuid !== 'string') {
|
|
206
|
-
throw new Error(`Invalid UUID type ${typeof uuid}. Expected string.`);
|
|
207
|
-
}
|
|
208
|
-
uuid = uuid.toLowerCase();
|
|
209
|
-
const is128BitUuid = uuid.search(/^[0-9a-f]{8}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{12}$/) >= 0;
|
|
210
|
-
if (!is128BitUuid) {
|
|
211
|
-
throw new Error(`Invalid UUID format ${uuid}. Expected 128 bit string (e.g. "0000180d-0000-1000-8000-00805f9b34fb").`);
|
|
212
|
-
}
|
|
213
|
-
return uuid;
|
|
204
|
+
function parseUUID(uuid) {
|
|
205
|
+
if (typeof uuid !== 'string') {
|
|
206
|
+
throw new Error(`Invalid UUID type ${typeof uuid}. Expected string.`);
|
|
207
|
+
}
|
|
208
|
+
uuid = uuid.toLowerCase();
|
|
209
|
+
const is128BitUuid = uuid.search(/^[0-9a-f]{8}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{12}$/) >= 0;
|
|
210
|
+
if (!is128BitUuid) {
|
|
211
|
+
throw new Error(`Invalid UUID format ${uuid}. Expected 128 bit string (e.g. "0000180d-0000-1000-8000-00805f9b34fb").`);
|
|
212
|
+
}
|
|
213
|
+
return uuid;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
class BleClientClass {
|
|
217
|
-
constructor() {
|
|
218
|
-
this.scanListener = null;
|
|
219
|
-
this.eventListeners = new Map();
|
|
220
|
-
this.queue = getQueue(true);
|
|
221
|
-
}
|
|
222
|
-
enableQueue() {
|
|
223
|
-
this.queue = getQueue(true);
|
|
224
|
-
}
|
|
225
|
-
disableQueue() {
|
|
226
|
-
this.queue = getQueue(false);
|
|
227
|
-
}
|
|
228
|
-
async initialize(options) {
|
|
229
|
-
await this.queue(async () => {
|
|
230
|
-
await BluetoothLe.initialize(options);
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
async isEnabled() {
|
|
234
|
-
const enabled = await this.queue(async () => {
|
|
235
|
-
const result = await BluetoothLe.isEnabled();
|
|
236
|
-
return result.value;
|
|
237
|
-
});
|
|
238
|
-
return enabled;
|
|
239
|
-
}
|
|
240
|
-
async requestEnable() {
|
|
241
|
-
await this.queue(async () => {
|
|
242
|
-
await BluetoothLe.requestEnable();
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
async enable() {
|
|
246
|
-
await this.queue(async () => {
|
|
247
|
-
await BluetoothLe.enable();
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
async disable() {
|
|
251
|
-
await this.queue(async () => {
|
|
252
|
-
await BluetoothLe.disable();
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
async startEnabledNotifications(callback) {
|
|
256
|
-
await this.queue(async () => {
|
|
257
|
-
var _a;
|
|
258
|
-
const key = `onEnabledChanged`;
|
|
259
|
-
await ((_a = this.eventListeners.get(key)) === null || _a ===
|
|
260
|
-
const listener = await BluetoothLe.addListener(key, (result) => {
|
|
261
|
-
callback(result.value);
|
|
262
|
-
});
|
|
263
|
-
this.eventListeners.set(key, listener);
|
|
264
|
-
await BluetoothLe.startEnabledNotifications();
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
async stopEnabledNotifications() {
|
|
268
|
-
await this.queue(async () => {
|
|
269
|
-
var _a;
|
|
270
|
-
const key = `onEnabledChanged`;
|
|
271
|
-
await ((_a = this.eventListeners.get(key)) === null || _a ===
|
|
272
|
-
this.eventListeners.delete(key);
|
|
273
|
-
await BluetoothLe.stopEnabledNotifications();
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
async isLocationEnabled() {
|
|
277
|
-
const enabled = await this.queue(async () => {
|
|
278
|
-
const result = await BluetoothLe.isLocationEnabled();
|
|
279
|
-
return result.value;
|
|
280
|
-
});
|
|
281
|
-
return enabled;
|
|
282
|
-
}
|
|
283
|
-
async openLocationSettings() {
|
|
284
|
-
await this.queue(async () => {
|
|
285
|
-
await BluetoothLe.openLocationSettings();
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
async openBluetoothSettings() {
|
|
289
|
-
await this.queue(async () => {
|
|
290
|
-
await BluetoothLe.openBluetoothSettings();
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
async openAppSettings() {
|
|
294
|
-
await this.queue(async () => {
|
|
295
|
-
await BluetoothLe.openAppSettings();
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
async setDisplayStrings(displayStrings) {
|
|
299
|
-
await this.queue(async () => {
|
|
300
|
-
await BluetoothLe.setDisplayStrings(displayStrings);
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
async requestDevice(options) {
|
|
304
|
-
options = options ? this.validateRequestBleDeviceOptions(options) : undefined;
|
|
305
|
-
const result = await this.queue(async () => {
|
|
306
|
-
const device = await BluetoothLe.requestDevice(options);
|
|
307
|
-
return device;
|
|
308
|
-
});
|
|
309
|
-
return result;
|
|
310
|
-
}
|
|
311
|
-
async requestLEScan(options, callback) {
|
|
312
|
-
options = this.validateRequestBleDeviceOptions(options);
|
|
313
|
-
await this.queue(async () => {
|
|
314
|
-
var _a;
|
|
315
|
-
await ((_a = this.scanListener) === null || _a ===
|
|
316
|
-
this.scanListener = await BluetoothLe.addListener('onScanResult', (resultInternal) => {
|
|
317
|
-
const result = Object.assign(Object.assign({}, resultInternal), { manufacturerData: this.convertObject(resultInternal.manufacturerData), serviceData: this.convertObject(resultInternal.serviceData), rawAdvertisement: resultInternal.rawAdvertisement
|
|
318
|
-
? this.convertValue(resultInternal.rawAdvertisement)
|
|
319
|
-
: undefined });
|
|
320
|
-
callback(result);
|
|
321
|
-
});
|
|
322
|
-
await BluetoothLe.requestLEScan(options);
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
async stopLEScan() {
|
|
326
|
-
await this.queue(async () => {
|
|
327
|
-
var _a;
|
|
328
|
-
await ((_a = this.scanListener) === null || _a ===
|
|
329
|
-
this.scanListener = null;
|
|
330
|
-
await BluetoothLe.stopLEScan();
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
async getDevices(deviceIds) {
|
|
334
|
-
if (!Array.isArray(deviceIds)) {
|
|
335
|
-
throw new Error('deviceIds must be an array');
|
|
336
|
-
}
|
|
337
|
-
return this.queue(async () => {
|
|
338
|
-
const result = await BluetoothLe.getDevices({ deviceIds });
|
|
339
|
-
return result.devices;
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
async getConnectedDevices(services) {
|
|
343
|
-
if (!Array.isArray(services)) {
|
|
344
|
-
throw new Error('services must be an array');
|
|
345
|
-
}
|
|
346
|
-
services = services.map(parseUUID);
|
|
347
|
-
return this.queue(async () => {
|
|
348
|
-
const result = await BluetoothLe.getConnectedDevices({ services });
|
|
349
|
-
return result.devices;
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
async getBondedDevices() {
|
|
353
|
-
return this.queue(async () => {
|
|
354
|
-
const result = await BluetoothLe.getBondedDevices();
|
|
355
|
-
return result.devices;
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
async connect(deviceId, onDisconnect, options) {
|
|
359
|
-
await this.queue(async () => {
|
|
360
|
-
var _a;
|
|
361
|
-
if (onDisconnect) {
|
|
362
|
-
const key = `disconnected|${deviceId}`;
|
|
363
|
-
await ((_a = this.eventListeners.get(key)) === null || _a ===
|
|
364
|
-
const listener = await BluetoothLe.addListener(key, () => {
|
|
365
|
-
onDisconnect(deviceId);
|
|
366
|
-
});
|
|
367
|
-
this.eventListeners.set(key, listener);
|
|
368
|
-
}
|
|
369
|
-
await BluetoothLe.connect(Object.assign({ deviceId }, options));
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
async createBond(deviceId, options) {
|
|
373
|
-
await this.queue(async () => {
|
|
374
|
-
await BluetoothLe.createBond(Object.assign({ deviceId }, options));
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
async isBonded(deviceId) {
|
|
378
|
-
const isBonded = await this.queue(async () => {
|
|
379
|
-
const result = await BluetoothLe.isBonded({ deviceId });
|
|
380
|
-
return result.value;
|
|
381
|
-
});
|
|
382
|
-
return isBonded;
|
|
383
|
-
}
|
|
384
|
-
async disconnect(deviceId) {
|
|
385
|
-
await this.queue(async () => {
|
|
386
|
-
await BluetoothLe.disconnect({ deviceId });
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
async getServices(deviceId) {
|
|
390
|
-
const services = await this.queue(async () => {
|
|
391
|
-
const result = await BluetoothLe.getServices({ deviceId });
|
|
392
|
-
return result.services;
|
|
393
|
-
});
|
|
394
|
-
return services;
|
|
395
|
-
}
|
|
396
|
-
async discoverServices(deviceId) {
|
|
397
|
-
await this.queue(async () => {
|
|
398
|
-
await BluetoothLe.discoverServices({ deviceId });
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
async getMtu(deviceId) {
|
|
402
|
-
const value = await this.queue(async () => {
|
|
403
|
-
const result = await BluetoothLe.getMtu({ deviceId });
|
|
404
|
-
return result.value;
|
|
405
|
-
});
|
|
406
|
-
return value;
|
|
407
|
-
}
|
|
408
|
-
async requestConnectionPriority(deviceId, connectionPriority) {
|
|
409
|
-
await this.queue(async () => {
|
|
410
|
-
await BluetoothLe.requestConnectionPriority({ deviceId, connectionPriority });
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
async readRssi(deviceId) {
|
|
414
|
-
const value = await this.queue(async () => {
|
|
415
|
-
const result = await BluetoothLe.readRssi({ deviceId });
|
|
416
|
-
return parseFloat(result.value);
|
|
417
|
-
});
|
|
418
|
-
return value;
|
|
419
|
-
}
|
|
420
|
-
async read(deviceId, service, characteristic, options) {
|
|
421
|
-
service = parseUUID(service);
|
|
422
|
-
characteristic = parseUUID(characteristic);
|
|
423
|
-
const value = await this.queue(async () => {
|
|
424
|
-
const result = await BluetoothLe.read(Object.assign({ deviceId,
|
|
425
|
-
service,
|
|
426
|
-
characteristic }, options));
|
|
427
|
-
return this.convertValue(result.value);
|
|
428
|
-
});
|
|
429
|
-
return value;
|
|
430
|
-
}
|
|
431
|
-
async write(deviceId, service, characteristic, value, options) {
|
|
432
|
-
service = parseUUID(service);
|
|
433
|
-
characteristic = parseUUID(characteristic);
|
|
434
|
-
return this.queue(async () => {
|
|
435
|
-
if (!(value === null || value ===
|
|
436
|
-
throw new Error('Invalid data.');
|
|
437
|
-
}
|
|
438
|
-
let writeValue = value;
|
|
439
|
-
if (core.Capacitor.getPlatform() !== 'web') {
|
|
440
|
-
// on native we can only write strings
|
|
441
|
-
writeValue = dataViewToHexString(value);
|
|
442
|
-
}
|
|
443
|
-
await BluetoothLe.write(Object.assign({ deviceId,
|
|
444
|
-
service,
|
|
445
|
-
characteristic, value: writeValue }, options));
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
async writeWithoutResponse(deviceId, service, characteristic, value, options) {
|
|
449
|
-
service = parseUUID(service);
|
|
450
|
-
characteristic = parseUUID(characteristic);
|
|
451
|
-
await this.queue(async () => {
|
|
452
|
-
if (!(value === null || value ===
|
|
453
|
-
throw new Error('Invalid data.');
|
|
454
|
-
}
|
|
455
|
-
let writeValue = value;
|
|
456
|
-
if (core.Capacitor.getPlatform() !== 'web') {
|
|
457
|
-
// on native we can only write strings
|
|
458
|
-
writeValue = dataViewToHexString(value);
|
|
459
|
-
}
|
|
460
|
-
await BluetoothLe.writeWithoutResponse(Object.assign({ deviceId,
|
|
461
|
-
service,
|
|
462
|
-
characteristic, value: writeValue }, options));
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
async readDescriptor(deviceId, service, characteristic, descriptor, options) {
|
|
466
|
-
service = parseUUID(service);
|
|
467
|
-
characteristic = parseUUID(characteristic);
|
|
468
|
-
descriptor = parseUUID(descriptor);
|
|
469
|
-
const value = await this.queue(async () => {
|
|
470
|
-
const result = await BluetoothLe.readDescriptor(Object.assign({ deviceId,
|
|
471
|
-
service,
|
|
472
|
-
characteristic,
|
|
473
|
-
descriptor }, options));
|
|
474
|
-
return this.convertValue(result.value);
|
|
475
|
-
});
|
|
476
|
-
return value;
|
|
477
|
-
}
|
|
478
|
-
async writeDescriptor(deviceId, service, characteristic, descriptor, value, options) {
|
|
479
|
-
service = parseUUID(service);
|
|
480
|
-
characteristic = parseUUID(characteristic);
|
|
481
|
-
descriptor = parseUUID(descriptor);
|
|
482
|
-
return this.queue(async () => {
|
|
483
|
-
if (!(value === null || value ===
|
|
484
|
-
throw new Error('Invalid data.');
|
|
485
|
-
}
|
|
486
|
-
let writeValue = value;
|
|
487
|
-
if (core.Capacitor.getPlatform() !== 'web') {
|
|
488
|
-
// on native we can only write strings
|
|
489
|
-
writeValue = dataViewToHexString(value);
|
|
490
|
-
}
|
|
491
|
-
await BluetoothLe.writeDescriptor(Object.assign({ deviceId,
|
|
492
|
-
service,
|
|
493
|
-
characteristic,
|
|
494
|
-
descriptor, value: writeValue }, options));
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
async startNotifications(deviceId, service, characteristic, callback, options) {
|
|
498
|
-
service = parseUUID(service);
|
|
499
|
-
characteristic = parseUUID(characteristic);
|
|
500
|
-
await this.queue(async () => {
|
|
501
|
-
var _a;
|
|
502
|
-
const key = `notification|${deviceId}|${service}|${characteristic}`;
|
|
503
|
-
await ((_a = this.eventListeners.get(key)) === null || _a ===
|
|
504
|
-
const listener = await BluetoothLe.addListener(key, (event) => {
|
|
505
|
-
callback(this.convertValue(event === null || event ===
|
|
506
|
-
});
|
|
507
|
-
this.eventListeners.set(key, listener);
|
|
508
|
-
await BluetoothLe.startNotifications(Object.assign({ deviceId,
|
|
509
|
-
service,
|
|
510
|
-
characteristic }, options));
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
async stopNotifications(deviceId, service, characteristic) {
|
|
514
|
-
service = parseUUID(service);
|
|
515
|
-
characteristic = parseUUID(characteristic);
|
|
516
|
-
await this.queue(async () => {
|
|
517
|
-
var _a;
|
|
518
|
-
const key = `notification|${deviceId}|${service}|${characteristic}`;
|
|
519
|
-
await ((_a = this.eventListeners.get(key)) === null || _a ===
|
|
520
|
-
this.eventListeners.delete(key);
|
|
521
|
-
await BluetoothLe.stopNotifications({
|
|
522
|
-
deviceId,
|
|
523
|
-
service,
|
|
524
|
-
characteristic,
|
|
525
|
-
});
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
validateRequestBleDeviceOptions(options) {
|
|
529
|
-
if (options.services) {
|
|
530
|
-
options.services = options.services.map(parseUUID);
|
|
531
|
-
}
|
|
532
|
-
if (options.optionalServices) {
|
|
533
|
-
options.optionalServices = options.optionalServices.map(parseUUID);
|
|
534
|
-
}
|
|
535
|
-
if (options.serviceData && core.Capacitor.getPlatform() !== 'web') {
|
|
536
|
-
// Native platforms: convert to hex strings
|
|
537
|
-
options.serviceData = options.serviceData.map((filter) => (Object.assign(Object.assign({}, filter), { serviceUuid: parseUUID(filter.serviceUuid), dataPrefix: toHexString(filter.dataPrefix), mask: toHexString(filter.mask) })));
|
|
538
|
-
}
|
|
539
|
-
if (options.manufacturerData) {
|
|
540
|
-
if (core.Capacitor.getPlatform() !== 'web') {
|
|
541
|
-
// Native platforms: convert to hex strings
|
|
542
|
-
options.manufacturerData = options.manufacturerData.map((filter) => (Object.assign(Object.assign({}, filter), { dataPrefix: toHexString(filter.dataPrefix), mask: toHexString(filter.mask) })));
|
|
543
|
-
}
|
|
544
|
-
else {
|
|
545
|
-
// Web platform: convert to Uint8Array for Web Bluetooth API
|
|
546
|
-
options.manufacturerData = options.manufacturerData.map((filter) => (Object.assign(Object.assign({}, filter), { dataPrefix: toUint8Array(filter.dataPrefix), mask: toUint8Array(filter.mask) })));
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
return options;
|
|
550
|
-
}
|
|
551
|
-
convertValue(value) {
|
|
552
|
-
if (typeof value === 'string') {
|
|
553
|
-
return hexStringToDataView(value);
|
|
554
|
-
}
|
|
555
|
-
else if (value === undefined) {
|
|
556
|
-
return new DataView(new ArrayBuffer(0));
|
|
557
|
-
}
|
|
558
|
-
return value;
|
|
559
|
-
}
|
|
560
|
-
convertObject(obj) {
|
|
561
|
-
if (obj === undefined) {
|
|
562
|
-
return undefined;
|
|
563
|
-
}
|
|
564
|
-
const result = {};
|
|
565
|
-
for (const key of Object.keys(obj)) {
|
|
566
|
-
result[key] = this.convertValue(obj[key]);
|
|
567
|
-
}
|
|
568
|
-
return result;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
216
|
+
class BleClientClass {
|
|
217
|
+
constructor() {
|
|
218
|
+
this.scanListener = null;
|
|
219
|
+
this.eventListeners = new Map();
|
|
220
|
+
this.queue = getQueue(true);
|
|
221
|
+
}
|
|
222
|
+
enableQueue() {
|
|
223
|
+
this.queue = getQueue(true);
|
|
224
|
+
}
|
|
225
|
+
disableQueue() {
|
|
226
|
+
this.queue = getQueue(false);
|
|
227
|
+
}
|
|
228
|
+
async initialize(options) {
|
|
229
|
+
await this.queue(async () => {
|
|
230
|
+
await BluetoothLe.initialize(options);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async isEnabled() {
|
|
234
|
+
const enabled = await this.queue(async () => {
|
|
235
|
+
const result = await BluetoothLe.isEnabled();
|
|
236
|
+
return result.value;
|
|
237
|
+
});
|
|
238
|
+
return enabled;
|
|
239
|
+
}
|
|
240
|
+
async requestEnable() {
|
|
241
|
+
await this.queue(async () => {
|
|
242
|
+
await BluetoothLe.requestEnable();
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
async enable() {
|
|
246
|
+
await this.queue(async () => {
|
|
247
|
+
await BluetoothLe.enable();
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
async disable() {
|
|
251
|
+
await this.queue(async () => {
|
|
252
|
+
await BluetoothLe.disable();
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
async startEnabledNotifications(callback) {
|
|
256
|
+
await this.queue(async () => {
|
|
257
|
+
var _a;
|
|
258
|
+
const key = `onEnabledChanged`;
|
|
259
|
+
await ((_a = this.eventListeners.get(key)) === null || _a === undefined ? undefined : _a.remove());
|
|
260
|
+
const listener = await BluetoothLe.addListener(key, (result) => {
|
|
261
|
+
callback(result.value);
|
|
262
|
+
});
|
|
263
|
+
this.eventListeners.set(key, listener);
|
|
264
|
+
await BluetoothLe.startEnabledNotifications();
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
async stopEnabledNotifications() {
|
|
268
|
+
await this.queue(async () => {
|
|
269
|
+
var _a;
|
|
270
|
+
const key = `onEnabledChanged`;
|
|
271
|
+
await ((_a = this.eventListeners.get(key)) === null || _a === undefined ? undefined : _a.remove());
|
|
272
|
+
this.eventListeners.delete(key);
|
|
273
|
+
await BluetoothLe.stopEnabledNotifications();
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
async isLocationEnabled() {
|
|
277
|
+
const enabled = await this.queue(async () => {
|
|
278
|
+
const result = await BluetoothLe.isLocationEnabled();
|
|
279
|
+
return result.value;
|
|
280
|
+
});
|
|
281
|
+
return enabled;
|
|
282
|
+
}
|
|
283
|
+
async openLocationSettings() {
|
|
284
|
+
await this.queue(async () => {
|
|
285
|
+
await BluetoothLe.openLocationSettings();
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async openBluetoothSettings() {
|
|
289
|
+
await this.queue(async () => {
|
|
290
|
+
await BluetoothLe.openBluetoothSettings();
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
async openAppSettings() {
|
|
294
|
+
await this.queue(async () => {
|
|
295
|
+
await BluetoothLe.openAppSettings();
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
async setDisplayStrings(displayStrings) {
|
|
299
|
+
await this.queue(async () => {
|
|
300
|
+
await BluetoothLe.setDisplayStrings(displayStrings);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
async requestDevice(options) {
|
|
304
|
+
options = options ? this.validateRequestBleDeviceOptions(options) : undefined;
|
|
305
|
+
const result = await this.queue(async () => {
|
|
306
|
+
const device = await BluetoothLe.requestDevice(options);
|
|
307
|
+
return device;
|
|
308
|
+
});
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
async requestLEScan(options, callback) {
|
|
312
|
+
options = this.validateRequestBleDeviceOptions(options);
|
|
313
|
+
await this.queue(async () => {
|
|
314
|
+
var _a;
|
|
315
|
+
await ((_a = this.scanListener) === null || _a === undefined ? undefined : _a.remove());
|
|
316
|
+
this.scanListener = await BluetoothLe.addListener('onScanResult', (resultInternal) => {
|
|
317
|
+
const result = Object.assign(Object.assign({}, resultInternal), { manufacturerData: this.convertObject(resultInternal.manufacturerData), serviceData: this.convertObject(resultInternal.serviceData), rawAdvertisement: resultInternal.rawAdvertisement
|
|
318
|
+
? this.convertValue(resultInternal.rawAdvertisement)
|
|
319
|
+
: undefined });
|
|
320
|
+
callback(result);
|
|
321
|
+
});
|
|
322
|
+
await BluetoothLe.requestLEScan(options);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
async stopLEScan() {
|
|
326
|
+
await this.queue(async () => {
|
|
327
|
+
var _a;
|
|
328
|
+
await ((_a = this.scanListener) === null || _a === undefined ? undefined : _a.remove());
|
|
329
|
+
this.scanListener = null;
|
|
330
|
+
await BluetoothLe.stopLEScan();
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
async getDevices(deviceIds) {
|
|
334
|
+
if (!Array.isArray(deviceIds)) {
|
|
335
|
+
throw new Error('deviceIds must be an array');
|
|
336
|
+
}
|
|
337
|
+
return this.queue(async () => {
|
|
338
|
+
const result = await BluetoothLe.getDevices({ deviceIds });
|
|
339
|
+
return result.devices;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
async getConnectedDevices(services) {
|
|
343
|
+
if (!Array.isArray(services)) {
|
|
344
|
+
throw new Error('services must be an array');
|
|
345
|
+
}
|
|
346
|
+
services = services.map(parseUUID);
|
|
347
|
+
return this.queue(async () => {
|
|
348
|
+
const result = await BluetoothLe.getConnectedDevices({ services });
|
|
349
|
+
return result.devices;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async getBondedDevices() {
|
|
353
|
+
return this.queue(async () => {
|
|
354
|
+
const result = await BluetoothLe.getBondedDevices();
|
|
355
|
+
return result.devices;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
async connect(deviceId, onDisconnect, options) {
|
|
359
|
+
await this.queue(async () => {
|
|
360
|
+
var _a;
|
|
361
|
+
if (onDisconnect) {
|
|
362
|
+
const key = `disconnected|${deviceId}`;
|
|
363
|
+
await ((_a = this.eventListeners.get(key)) === null || _a === undefined ? undefined : _a.remove());
|
|
364
|
+
const listener = await BluetoothLe.addListener(key, () => {
|
|
365
|
+
onDisconnect(deviceId);
|
|
366
|
+
});
|
|
367
|
+
this.eventListeners.set(key, listener);
|
|
368
|
+
}
|
|
369
|
+
await BluetoothLe.connect(Object.assign({ deviceId }, options));
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
async createBond(deviceId, options) {
|
|
373
|
+
await this.queue(async () => {
|
|
374
|
+
await BluetoothLe.createBond(Object.assign({ deviceId }, options));
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
async isBonded(deviceId) {
|
|
378
|
+
const isBonded = await this.queue(async () => {
|
|
379
|
+
const result = await BluetoothLe.isBonded({ deviceId });
|
|
380
|
+
return result.value;
|
|
381
|
+
});
|
|
382
|
+
return isBonded;
|
|
383
|
+
}
|
|
384
|
+
async disconnect(deviceId) {
|
|
385
|
+
await this.queue(async () => {
|
|
386
|
+
await BluetoothLe.disconnect({ deviceId });
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
async getServices(deviceId) {
|
|
390
|
+
const services = await this.queue(async () => {
|
|
391
|
+
const result = await BluetoothLe.getServices({ deviceId });
|
|
392
|
+
return result.services;
|
|
393
|
+
});
|
|
394
|
+
return services;
|
|
395
|
+
}
|
|
396
|
+
async discoverServices(deviceId) {
|
|
397
|
+
await this.queue(async () => {
|
|
398
|
+
await BluetoothLe.discoverServices({ deviceId });
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
async getMtu(deviceId) {
|
|
402
|
+
const value = await this.queue(async () => {
|
|
403
|
+
const result = await BluetoothLe.getMtu({ deviceId });
|
|
404
|
+
return result.value;
|
|
405
|
+
});
|
|
406
|
+
return value;
|
|
407
|
+
}
|
|
408
|
+
async requestConnectionPriority(deviceId, connectionPriority) {
|
|
409
|
+
await this.queue(async () => {
|
|
410
|
+
await BluetoothLe.requestConnectionPriority({ deviceId, connectionPriority });
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
async readRssi(deviceId) {
|
|
414
|
+
const value = await this.queue(async () => {
|
|
415
|
+
const result = await BluetoothLe.readRssi({ deviceId });
|
|
416
|
+
return parseFloat(result.value);
|
|
417
|
+
});
|
|
418
|
+
return value;
|
|
419
|
+
}
|
|
420
|
+
async read(deviceId, service, characteristic, options) {
|
|
421
|
+
service = parseUUID(service);
|
|
422
|
+
characteristic = parseUUID(characteristic);
|
|
423
|
+
const value = await this.queue(async () => {
|
|
424
|
+
const result = await BluetoothLe.read(Object.assign({ deviceId,
|
|
425
|
+
service,
|
|
426
|
+
characteristic }, options));
|
|
427
|
+
return this.convertValue(result.value);
|
|
428
|
+
});
|
|
429
|
+
return value;
|
|
430
|
+
}
|
|
431
|
+
async write(deviceId, service, characteristic, value, options) {
|
|
432
|
+
service = parseUUID(service);
|
|
433
|
+
characteristic = parseUUID(characteristic);
|
|
434
|
+
return this.queue(async () => {
|
|
435
|
+
if (!(value === null || value === undefined ? undefined : value.buffer)) {
|
|
436
|
+
throw new Error('Invalid data.');
|
|
437
|
+
}
|
|
438
|
+
let writeValue = value;
|
|
439
|
+
if (core.Capacitor.getPlatform() !== 'web') {
|
|
440
|
+
// on native we can only write strings
|
|
441
|
+
writeValue = dataViewToHexString(value);
|
|
442
|
+
}
|
|
443
|
+
await BluetoothLe.write(Object.assign({ deviceId,
|
|
444
|
+
service,
|
|
445
|
+
characteristic, value: writeValue }, options));
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
async writeWithoutResponse(deviceId, service, characteristic, value, options) {
|
|
449
|
+
service = parseUUID(service);
|
|
450
|
+
characteristic = parseUUID(characteristic);
|
|
451
|
+
await this.queue(async () => {
|
|
452
|
+
if (!(value === null || value === undefined ? undefined : value.buffer)) {
|
|
453
|
+
throw new Error('Invalid data.');
|
|
454
|
+
}
|
|
455
|
+
let writeValue = value;
|
|
456
|
+
if (core.Capacitor.getPlatform() !== 'web') {
|
|
457
|
+
// on native we can only write strings
|
|
458
|
+
writeValue = dataViewToHexString(value);
|
|
459
|
+
}
|
|
460
|
+
await BluetoothLe.writeWithoutResponse(Object.assign({ deviceId,
|
|
461
|
+
service,
|
|
462
|
+
characteristic, value: writeValue }, options));
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
async readDescriptor(deviceId, service, characteristic, descriptor, options) {
|
|
466
|
+
service = parseUUID(service);
|
|
467
|
+
characteristic = parseUUID(characteristic);
|
|
468
|
+
descriptor = parseUUID(descriptor);
|
|
469
|
+
const value = await this.queue(async () => {
|
|
470
|
+
const result = await BluetoothLe.readDescriptor(Object.assign({ deviceId,
|
|
471
|
+
service,
|
|
472
|
+
characteristic,
|
|
473
|
+
descriptor }, options));
|
|
474
|
+
return this.convertValue(result.value);
|
|
475
|
+
});
|
|
476
|
+
return value;
|
|
477
|
+
}
|
|
478
|
+
async writeDescriptor(deviceId, service, characteristic, descriptor, value, options) {
|
|
479
|
+
service = parseUUID(service);
|
|
480
|
+
characteristic = parseUUID(characteristic);
|
|
481
|
+
descriptor = parseUUID(descriptor);
|
|
482
|
+
return this.queue(async () => {
|
|
483
|
+
if (!(value === null || value === undefined ? undefined : value.buffer)) {
|
|
484
|
+
throw new Error('Invalid data.');
|
|
485
|
+
}
|
|
486
|
+
let writeValue = value;
|
|
487
|
+
if (core.Capacitor.getPlatform() !== 'web') {
|
|
488
|
+
// on native we can only write strings
|
|
489
|
+
writeValue = dataViewToHexString(value);
|
|
490
|
+
}
|
|
491
|
+
await BluetoothLe.writeDescriptor(Object.assign({ deviceId,
|
|
492
|
+
service,
|
|
493
|
+
characteristic,
|
|
494
|
+
descriptor, value: writeValue }, options));
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
async startNotifications(deviceId, service, characteristic, callback, options) {
|
|
498
|
+
service = parseUUID(service);
|
|
499
|
+
characteristic = parseUUID(characteristic);
|
|
500
|
+
await this.queue(async () => {
|
|
501
|
+
var _a;
|
|
502
|
+
const key = `notification|${deviceId}|${service}|${characteristic}`;
|
|
503
|
+
await ((_a = this.eventListeners.get(key)) === null || _a === undefined ? undefined : _a.remove());
|
|
504
|
+
const listener = await BluetoothLe.addListener(key, (event) => {
|
|
505
|
+
callback(this.convertValue(event === null || event === undefined ? undefined : event.value));
|
|
506
|
+
});
|
|
507
|
+
this.eventListeners.set(key, listener);
|
|
508
|
+
await BluetoothLe.startNotifications(Object.assign({ deviceId,
|
|
509
|
+
service,
|
|
510
|
+
characteristic }, options));
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
async stopNotifications(deviceId, service, characteristic) {
|
|
514
|
+
service = parseUUID(service);
|
|
515
|
+
characteristic = parseUUID(characteristic);
|
|
516
|
+
await this.queue(async () => {
|
|
517
|
+
var _a;
|
|
518
|
+
const key = `notification|${deviceId}|${service}|${characteristic}`;
|
|
519
|
+
await ((_a = this.eventListeners.get(key)) === null || _a === undefined ? undefined : _a.remove());
|
|
520
|
+
this.eventListeners.delete(key);
|
|
521
|
+
await BluetoothLe.stopNotifications({
|
|
522
|
+
deviceId,
|
|
523
|
+
service,
|
|
524
|
+
characteristic,
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
validateRequestBleDeviceOptions(options) {
|
|
529
|
+
if (options.services) {
|
|
530
|
+
options.services = options.services.map(parseUUID);
|
|
531
|
+
}
|
|
532
|
+
if (options.optionalServices) {
|
|
533
|
+
options.optionalServices = options.optionalServices.map(parseUUID);
|
|
534
|
+
}
|
|
535
|
+
if (options.serviceData && core.Capacitor.getPlatform() !== 'web') {
|
|
536
|
+
// Native platforms: convert to hex strings
|
|
537
|
+
options.serviceData = options.serviceData.map((filter) => (Object.assign(Object.assign({}, filter), { serviceUuid: parseUUID(filter.serviceUuid), dataPrefix: toHexString(filter.dataPrefix), mask: toHexString(filter.mask) })));
|
|
538
|
+
}
|
|
539
|
+
if (options.manufacturerData) {
|
|
540
|
+
if (core.Capacitor.getPlatform() !== 'web') {
|
|
541
|
+
// Native platforms: convert to hex strings
|
|
542
|
+
options.manufacturerData = options.manufacturerData.map((filter) => (Object.assign(Object.assign({}, filter), { dataPrefix: toHexString(filter.dataPrefix), mask: toHexString(filter.mask) })));
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
// Web platform: convert to Uint8Array for Web Bluetooth API
|
|
546
|
+
options.manufacturerData = options.manufacturerData.map((filter) => (Object.assign(Object.assign({}, filter), { dataPrefix: toUint8Array(filter.dataPrefix), mask: toUint8Array(filter.mask) })));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return options;
|
|
550
|
+
}
|
|
551
|
+
convertValue(value) {
|
|
552
|
+
if (typeof value === 'string') {
|
|
553
|
+
return hexStringToDataView(value);
|
|
554
|
+
}
|
|
555
|
+
else if (value === undefined) {
|
|
556
|
+
return new DataView(new ArrayBuffer(0));
|
|
557
|
+
}
|
|
558
|
+
return value;
|
|
559
|
+
}
|
|
560
|
+
convertObject(obj) {
|
|
561
|
+
if (obj === undefined) {
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
const result = {};
|
|
565
|
+
for (const key of Object.keys(obj)) {
|
|
566
|
+
result[key] = this.convertValue(obj[key]);
|
|
567
|
+
}
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
571
|
const BleClient = new BleClientClass();
|
|
572
572
|
|
|
573
|
-
async function runWithTimeout(promise, time, exception) {
|
|
574
|
-
let timer;
|
|
575
|
-
return Promise.race([
|
|
576
|
-
promise,
|
|
577
|
-
new Promise((_, reject) => {
|
|
578
|
-
timer = setTimeout(() => reject(exception), time);
|
|
579
|
-
}),
|
|
580
|
-
]).finally(() => clearTimeout(timer));
|
|
573
|
+
async function runWithTimeout(promise, time, exception) {
|
|
574
|
+
let timer;
|
|
575
|
+
return Promise.race([
|
|
576
|
+
promise,
|
|
577
|
+
new Promise((_, reject) => {
|
|
578
|
+
timer = setTimeout(() => reject(exception), time);
|
|
579
|
+
}),
|
|
580
|
+
]).finally(() => clearTimeout(timer));
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
-
class BluetoothLeWeb extends core.WebPlugin {
|
|
584
|
-
constructor() {
|
|
585
|
-
super(...arguments);
|
|
586
|
-
this.deviceMap = new Map();
|
|
587
|
-
this.discoveredDevices = new Map();
|
|
588
|
-
this.scan = null;
|
|
589
|
-
this.DEFAULT_CONNECTION_TIMEOUT = 10000;
|
|
590
|
-
this.onAdvertisementReceivedCallback = this.onAdvertisementReceived.bind(this);
|
|
591
|
-
this.onDisconnectedCallback = this.onDisconnected.bind(this);
|
|
592
|
-
this.onCharacteristicValueChangedCallback = this.onCharacteristicValueChanged.bind(this);
|
|
593
|
-
}
|
|
594
|
-
async initialize() {
|
|
595
|
-
if (typeof navigator === 'undefined' || !navigator.bluetooth) {
|
|
596
|
-
throw this.unavailable('Web Bluetooth API not available in this browser.');
|
|
597
|
-
}
|
|
598
|
-
const isAvailable = await navigator.bluetooth.getAvailability();
|
|
599
|
-
if (!isAvailable) {
|
|
600
|
-
throw this.unavailable('No Bluetooth radio available.');
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
async isEnabled() {
|
|
604
|
-
// not available on web
|
|
605
|
-
return { value: true };
|
|
606
|
-
}
|
|
607
|
-
async requestEnable() {
|
|
608
|
-
throw this.unavailable('requestEnable is not available on web.');
|
|
609
|
-
}
|
|
610
|
-
async enable() {
|
|
611
|
-
throw this.unavailable('enable is not available on web.');
|
|
612
|
-
}
|
|
613
|
-
async disable() {
|
|
614
|
-
throw this.unavailable('disable is not available on web.');
|
|
615
|
-
}
|
|
616
|
-
async startEnabledNotifications() {
|
|
617
|
-
// not available on web
|
|
618
|
-
}
|
|
619
|
-
async stopEnabledNotifications() {
|
|
620
|
-
// not available on web
|
|
621
|
-
}
|
|
622
|
-
async isLocationEnabled() {
|
|
623
|
-
throw this.unavailable('isLocationEnabled is not available on web.');
|
|
624
|
-
}
|
|
625
|
-
async openLocationSettings() {
|
|
626
|
-
throw this.unavailable('openLocationSettings is not available on web.');
|
|
627
|
-
}
|
|
628
|
-
async openBluetoothSettings() {
|
|
629
|
-
throw this.unavailable('openBluetoothSettings is not available on web.');
|
|
630
|
-
}
|
|
631
|
-
async openAppSettings() {
|
|
632
|
-
throw this.unavailable('openAppSettings is not available on web.');
|
|
633
|
-
}
|
|
634
|
-
async setDisplayStrings() {
|
|
635
|
-
// not available on web
|
|
636
|
-
}
|
|
637
|
-
async requestDevice(options) {
|
|
638
|
-
const filters = this.getFilters(options);
|
|
639
|
-
const device = await navigator.bluetooth.requestDevice({
|
|
640
|
-
filters: filters.length ? filters : undefined,
|
|
641
|
-
optionalServices: options === null || options ===
|
|
642
|
-
acceptAllDevices: filters.length === 0,
|
|
643
|
-
});
|
|
644
|
-
this.deviceMap.set(device.id, device);
|
|
645
|
-
const bleDevice = this.getBleDevice(device);
|
|
646
|
-
return bleDevice;
|
|
647
|
-
}
|
|
648
|
-
async requestLEScan(options) {
|
|
649
|
-
this.requestBleDeviceOptions = options;
|
|
650
|
-
const filters = this.getFilters(options);
|
|
651
|
-
await this.stopLEScan();
|
|
652
|
-
this.discoveredDevices = new Map();
|
|
653
|
-
navigator.bluetooth.removeEventListener('advertisementreceived', this.onAdvertisementReceivedCallback);
|
|
654
|
-
navigator.bluetooth.addEventListener('advertisementreceived', this.onAdvertisementReceivedCallback);
|
|
655
|
-
this.scan = await navigator.bluetooth.requestLEScan({
|
|
656
|
-
filters: filters.length ? filters : undefined,
|
|
657
|
-
acceptAllAdvertisements: filters.length === 0,
|
|
658
|
-
keepRepeatedDevices: options === null || options ===
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
onAdvertisementReceived(event) {
|
|
662
|
-
var _a, _b, _c;
|
|
663
|
-
const deviceId = event.device.id;
|
|
664
|
-
this.deviceMap.set(deviceId, event.device);
|
|
665
|
-
const isNew = !this.discoveredDevices.has(deviceId);
|
|
666
|
-
// Apply service data filtering client-side (Web Bluetooth API doesn't support it in scan filters)
|
|
667
|
-
if (((_a = this.requestBleDeviceOptions) === null || _a ===
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
if (isNew || ((_b = this.requestBleDeviceOptions) === null || _b ===
|
|
671
|
-
this.discoveredDevices.set(deviceId, true);
|
|
672
|
-
const device = this.getBleDevice(event.device);
|
|
673
|
-
const result = {
|
|
674
|
-
device,
|
|
675
|
-
localName: device.name,
|
|
676
|
-
rssi: event.rssi,
|
|
677
|
-
txPower: event.txPower,
|
|
678
|
-
manufacturerData: mapToObject(event.manufacturerData),
|
|
679
|
-
serviceData: mapToObject(event.serviceData),
|
|
680
|
-
uuids: (_c = event.uuids) === null || _c ===
|
|
681
|
-
};
|
|
682
|
-
this.notifyListeners('onScanResult', result);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
async stopLEScan() {
|
|
686
|
-
var _a;
|
|
687
|
-
if ((_a = this.scan) === null || _a ===
|
|
688
|
-
this.scan.stop();
|
|
689
|
-
}
|
|
690
|
-
this.scan = null;
|
|
691
|
-
}
|
|
692
|
-
async getDevices(options) {
|
|
693
|
-
const devices = await navigator.bluetooth.getDevices();
|
|
694
|
-
const bleDevices = devices
|
|
695
|
-
.filter((device) => options.deviceIds.includes(device.id))
|
|
696
|
-
.map((device) => {
|
|
697
|
-
this.deviceMap.set(device.id, device);
|
|
698
|
-
const bleDevice = this.getBleDevice(device);
|
|
699
|
-
return bleDevice;
|
|
700
|
-
});
|
|
701
|
-
return { devices: bleDevices };
|
|
702
|
-
}
|
|
703
|
-
async getConnectedDevices(_options) {
|
|
704
|
-
const devices = await navigator.bluetooth.getDevices();
|
|
705
|
-
const bleDevices = devices
|
|
706
|
-
.filter((device) => {
|
|
707
|
-
var _a;
|
|
708
|
-
return (_a = device.gatt) === null || _a ===
|
|
709
|
-
})
|
|
710
|
-
.map((device) => {
|
|
711
|
-
this.deviceMap.set(device.id, device);
|
|
712
|
-
const bleDevice = this.getBleDevice(device);
|
|
713
|
-
return bleDevice;
|
|
714
|
-
});
|
|
715
|
-
return { devices: bleDevices };
|
|
716
|
-
}
|
|
717
|
-
async getBondedDevices() {
|
|
718
|
-
return {};
|
|
719
|
-
}
|
|
720
|
-
async connect(options) {
|
|
721
|
-
var _a, _b;
|
|
722
|
-
const device = this.getDeviceFromMap(options.deviceId);
|
|
723
|
-
device.removeEventListener('gattserverdisconnected', this.onDisconnectedCallback);
|
|
724
|
-
device.addEventListener('gattserverdisconnected', this.onDisconnectedCallback);
|
|
725
|
-
const timeoutError = Symbol();
|
|
726
|
-
if (device.gatt === undefined) {
|
|
727
|
-
throw new Error('No gatt server available.');
|
|
728
|
-
}
|
|
729
|
-
try {
|
|
730
|
-
const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : this.DEFAULT_CONNECTION_TIMEOUT;
|
|
731
|
-
await runWithTimeout(device.gatt.connect(), timeout, timeoutError);
|
|
732
|
-
}
|
|
733
|
-
catch (error) {
|
|
734
|
-
// cancel pending connect call, does not work yet in chromium because of a bug:
|
|
735
|
-
// https://bugs.chromium.org/p/chromium/issues/detail?id=684073
|
|
736
|
-
await ((_b = device.gatt) === null || _b ===
|
|
737
|
-
if (error === timeoutError) {
|
|
738
|
-
throw new Error('Connection timeout');
|
|
739
|
-
}
|
|
740
|
-
else {
|
|
741
|
-
throw error;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
onDisconnected(event) {
|
|
746
|
-
const deviceId = event.target.id;
|
|
747
|
-
const key = `disconnected|${deviceId}`;
|
|
748
|
-
this.notifyListeners(key, null);
|
|
749
|
-
}
|
|
750
|
-
async createBond(_options) {
|
|
751
|
-
throw this.unavailable('createBond is not available on web.');
|
|
752
|
-
}
|
|
753
|
-
async isBonded(_options) {
|
|
754
|
-
throw this.unavailable('isBonded is not available on web.');
|
|
755
|
-
}
|
|
756
|
-
async disconnect(options) {
|
|
757
|
-
var _a;
|
|
758
|
-
(_a = this.getDeviceFromMap(options.deviceId).gatt) === null || _a ===
|
|
759
|
-
}
|
|
760
|
-
async getServices(options) {
|
|
761
|
-
var _a, _b;
|
|
762
|
-
const services = (_b = (await ((_a = this.getDeviceFromMap(options.deviceId).gatt) === null || _a ===
|
|
763
|
-
const bleServices = [];
|
|
764
|
-
for (const service of services) {
|
|
765
|
-
const characteristics = await service.getCharacteristics();
|
|
766
|
-
const bleCharacteristics = [];
|
|
767
|
-
for (const characteristic of characteristics) {
|
|
768
|
-
bleCharacteristics.push({
|
|
769
|
-
uuid: characteristic.uuid,
|
|
770
|
-
properties: this.getProperties(characteristic),
|
|
771
|
-
descriptors: await this.getDescriptors(characteristic),
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
bleServices.push({ uuid: service.uuid, characteristics: bleCharacteristics });
|
|
775
|
-
}
|
|
776
|
-
return { services: bleServices };
|
|
777
|
-
}
|
|
778
|
-
async getDescriptors(characteristic) {
|
|
779
|
-
try {
|
|
780
|
-
const descriptors = await characteristic.getDescriptors();
|
|
781
|
-
return descriptors.map((descriptor) => ({
|
|
782
|
-
uuid: descriptor.uuid,
|
|
783
|
-
}));
|
|
784
|
-
}
|
|
785
|
-
catch (_a) {
|
|
786
|
-
return [];
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
getProperties(characteristic) {
|
|
790
|
-
return {
|
|
791
|
-
broadcast: characteristic.properties.broadcast,
|
|
792
|
-
read: characteristic.properties.read,
|
|
793
|
-
writeWithoutResponse: characteristic.properties.writeWithoutResponse,
|
|
794
|
-
write: characteristic.properties.write,
|
|
795
|
-
notify: characteristic.properties.notify,
|
|
796
|
-
indicate: characteristic.properties.indicate,
|
|
797
|
-
authenticatedSignedWrites: characteristic.properties.authenticatedSignedWrites,
|
|
798
|
-
reliableWrite: characteristic.properties.reliableWrite,
|
|
799
|
-
writableAuxiliaries: characteristic.properties.writableAuxiliaries,
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
async getCharacteristic(options) {
|
|
803
|
-
var _a;
|
|
804
|
-
const service = await ((_a = this.getDeviceFromMap(options.deviceId).gatt) === null || _a ===
|
|
805
|
-
return service === null || service ===
|
|
806
|
-
}
|
|
807
|
-
async getDescriptor(options) {
|
|
808
|
-
const characteristic = await this.getCharacteristic(options);
|
|
809
|
-
return characteristic === null || characteristic ===
|
|
810
|
-
}
|
|
811
|
-
async discoverServices(_options) {
|
|
812
|
-
throw this.unavailable('discoverServices is not available on web.');
|
|
813
|
-
}
|
|
814
|
-
async getMtu(_options) {
|
|
815
|
-
throw this.unavailable('getMtu is not available on web.');
|
|
816
|
-
}
|
|
817
|
-
async requestConnectionPriority(_options) {
|
|
818
|
-
throw this.unavailable('requestConnectionPriority is not available on web.');
|
|
819
|
-
}
|
|
820
|
-
async readRssi(_options) {
|
|
821
|
-
throw this.unavailable('readRssi is not available on web.');
|
|
822
|
-
}
|
|
823
|
-
async read(options) {
|
|
824
|
-
const characteristic = await this.getCharacteristic(options);
|
|
825
|
-
const value = await (characteristic === null || characteristic ===
|
|
826
|
-
return { value };
|
|
827
|
-
}
|
|
828
|
-
async write(options) {
|
|
829
|
-
const characteristic = await this.getCharacteristic(options);
|
|
830
|
-
let dataView;
|
|
831
|
-
if (typeof options.value === 'string') {
|
|
832
|
-
dataView = hexStringToDataView(options.value);
|
|
833
|
-
}
|
|
834
|
-
else {
|
|
835
|
-
dataView = options.value;
|
|
836
|
-
}
|
|
837
|
-
await (characteristic === null || characteristic ===
|
|
838
|
-
}
|
|
839
|
-
async writeWithoutResponse(options) {
|
|
840
|
-
const characteristic = await this.getCharacteristic(options);
|
|
841
|
-
let dataView;
|
|
842
|
-
if (typeof options.value === 'string') {
|
|
843
|
-
dataView = hexStringToDataView(options.value);
|
|
844
|
-
}
|
|
845
|
-
else {
|
|
846
|
-
dataView = options.value;
|
|
847
|
-
}
|
|
848
|
-
await (characteristic === null || characteristic ===
|
|
849
|
-
}
|
|
850
|
-
async readDescriptor(options) {
|
|
851
|
-
const descriptor = await this.getDescriptor(options);
|
|
852
|
-
const value = await (descriptor === null || descriptor ===
|
|
853
|
-
return { value };
|
|
854
|
-
}
|
|
855
|
-
async writeDescriptor(options) {
|
|
856
|
-
const descriptor = await this.getDescriptor(options);
|
|
857
|
-
let dataView;
|
|
858
|
-
if (typeof options.value === 'string') {
|
|
859
|
-
dataView = hexStringToDataView(options.value);
|
|
860
|
-
}
|
|
861
|
-
else {
|
|
862
|
-
dataView = options.value;
|
|
863
|
-
}
|
|
864
|
-
await (descriptor === null || descriptor ===
|
|
865
|
-
}
|
|
866
|
-
async startNotifications(options) {
|
|
867
|
-
const characteristic = await this.getCharacteristic(options);
|
|
868
|
-
characteristic === null || characteristic ===
|
|
869
|
-
characteristic === null || characteristic ===
|
|
870
|
-
await (characteristic === null || characteristic ===
|
|
871
|
-
}
|
|
872
|
-
onCharacteristicValueChanged(event) {
|
|
873
|
-
var _a, _b;
|
|
874
|
-
const characteristic = event.target;
|
|
875
|
-
const key = `notification|${(_a = characteristic.service) === null || _a ===
|
|
876
|
-
this.notifyListeners(key, {
|
|
877
|
-
value: characteristic.value,
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
async stopNotifications(options) {
|
|
881
|
-
const characteristic = await this.getCharacteristic(options);
|
|
882
|
-
await (characteristic === null || characteristic ===
|
|
883
|
-
}
|
|
884
|
-
getFilters(options) {
|
|
885
|
-
var _a, _b;
|
|
886
|
-
const filters = [];
|
|
887
|
-
for (const service of (_a = options === null || options ===
|
|
888
|
-
filters.push({
|
|
889
|
-
services: [service],
|
|
890
|
-
name: options === null || options ===
|
|
891
|
-
namePrefix: options === null || options ===
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
|
-
if (((options === null || options ===
|
|
895
|
-
filters.push({
|
|
896
|
-
name: options.name,
|
|
897
|
-
namePrefix: options.namePrefix,
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
for (const manufacturerData of (_b = options === null || options ===
|
|
901
|
-
// Cast to any to avoid type incompatibility - conversion is handled in bleClient.ts
|
|
902
|
-
filters.push({
|
|
903
|
-
manufacturerData: [manufacturerData],
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
// Note: Web Bluetooth API does not support service data in scan filters.
|
|
907
|
-
// Service data filtering will be done client-side in onAdvertisementReceived.
|
|
908
|
-
// We still accept serviceData in options for API consistency across platforms.
|
|
909
|
-
return filters;
|
|
910
|
-
}
|
|
911
|
-
matchesServiceDataFilter(event) {
|
|
912
|
-
var _a;
|
|
913
|
-
const filters = (_a = this.requestBleDeviceOptions) === null || _a ===
|
|
914
|
-
if (!filters || filters.length === 0) {
|
|
915
|
-
return true; // No filters, accept all
|
|
916
|
-
}
|
|
917
|
-
if (!event.serviceData) {
|
|
918
|
-
return false; // No service data in advertisement
|
|
919
|
-
}
|
|
920
|
-
// Check if any filter matches (OR logic)
|
|
921
|
-
for (const filter of filters) {
|
|
922
|
-
const serviceData = event.serviceData.get(filter.serviceUuid);
|
|
923
|
-
if (!serviceData) {
|
|
924
|
-
continue; // This filter doesn't match, try next
|
|
925
|
-
}
|
|
926
|
-
// If we have service data for this UUID
|
|
927
|
-
if (!filter.dataPrefix) {
|
|
928
|
-
return true; // Filter matched by service UUID alone
|
|
929
|
-
}
|
|
930
|
-
// Check data prefix (DataView on web)
|
|
931
|
-
const data = new Uint8Array(serviceData.buffer);
|
|
932
|
-
const prefixView = filter.dataPrefix;
|
|
933
|
-
if (data.length < prefixView.byteLength) {
|
|
934
|
-
continue; // Data too short
|
|
935
|
-
}
|
|
936
|
-
// Apply mask if provided
|
|
937
|
-
if (filter.mask) {
|
|
938
|
-
const maskView = filter.mask;
|
|
939
|
-
let matches = true;
|
|
940
|
-
for (let i = 0; i < prefixView.byteLength; i++) {
|
|
941
|
-
if ((data[i] & maskView.getUint8(i)) !== (prefixView.getUint8(i) & maskView.getUint8(i))) {
|
|
942
|
-
matches = false;
|
|
943
|
-
break;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
if (matches) {
|
|
947
|
-
return true;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
else {
|
|
951
|
-
// Check if data starts with prefix
|
|
952
|
-
let matches = true;
|
|
953
|
-
for (let i = 0; i < prefixView.byteLength; i++) {
|
|
954
|
-
if (data[i] !== prefixView.getUint8(i)) {
|
|
955
|
-
matches = false;
|
|
956
|
-
break;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
if (matches) {
|
|
960
|
-
return true;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
return false; // No filter matched
|
|
965
|
-
}
|
|
966
|
-
getDeviceFromMap(deviceId) {
|
|
967
|
-
const device = this.deviceMap.get(deviceId);
|
|
968
|
-
if (device === undefined) {
|
|
969
|
-
throw new Error('Device not found. Call "requestDevice", "requestLEScan" or "getDevices" first.');
|
|
970
|
-
}
|
|
971
|
-
return device;
|
|
972
|
-
}
|
|
973
|
-
getBleDevice(device) {
|
|
974
|
-
var _a;
|
|
975
|
-
const bleDevice = {
|
|
976
|
-
deviceId: device.id,
|
|
977
|
-
// use undefined instead of null if name is not available
|
|
978
|
-
name: (_a = device.name) !== null && _a !==
|
|
979
|
-
};
|
|
980
|
-
return bleDevice;
|
|
981
|
-
}
|
|
583
|
+
class BluetoothLeWeb extends core.WebPlugin {
|
|
584
|
+
constructor() {
|
|
585
|
+
super(...arguments);
|
|
586
|
+
this.deviceMap = new Map();
|
|
587
|
+
this.discoveredDevices = new Map();
|
|
588
|
+
this.scan = null;
|
|
589
|
+
this.DEFAULT_CONNECTION_TIMEOUT = 10000;
|
|
590
|
+
this.onAdvertisementReceivedCallback = this.onAdvertisementReceived.bind(this);
|
|
591
|
+
this.onDisconnectedCallback = this.onDisconnected.bind(this);
|
|
592
|
+
this.onCharacteristicValueChangedCallback = this.onCharacteristicValueChanged.bind(this);
|
|
593
|
+
}
|
|
594
|
+
async initialize() {
|
|
595
|
+
if (typeof navigator === 'undefined' || !navigator.bluetooth) {
|
|
596
|
+
throw this.unavailable('Web Bluetooth API not available in this browser.');
|
|
597
|
+
}
|
|
598
|
+
const isAvailable = await navigator.bluetooth.getAvailability();
|
|
599
|
+
if (!isAvailable) {
|
|
600
|
+
throw this.unavailable('No Bluetooth radio available.');
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async isEnabled() {
|
|
604
|
+
// not available on web
|
|
605
|
+
return { value: true };
|
|
606
|
+
}
|
|
607
|
+
async requestEnable() {
|
|
608
|
+
throw this.unavailable('requestEnable is not available on web.');
|
|
609
|
+
}
|
|
610
|
+
async enable() {
|
|
611
|
+
throw this.unavailable('enable is not available on web.');
|
|
612
|
+
}
|
|
613
|
+
async disable() {
|
|
614
|
+
throw this.unavailable('disable is not available on web.');
|
|
615
|
+
}
|
|
616
|
+
async startEnabledNotifications() {
|
|
617
|
+
// not available on web
|
|
618
|
+
}
|
|
619
|
+
async stopEnabledNotifications() {
|
|
620
|
+
// not available on web
|
|
621
|
+
}
|
|
622
|
+
async isLocationEnabled() {
|
|
623
|
+
throw this.unavailable('isLocationEnabled is not available on web.');
|
|
624
|
+
}
|
|
625
|
+
async openLocationSettings() {
|
|
626
|
+
throw this.unavailable('openLocationSettings is not available on web.');
|
|
627
|
+
}
|
|
628
|
+
async openBluetoothSettings() {
|
|
629
|
+
throw this.unavailable('openBluetoothSettings is not available on web.');
|
|
630
|
+
}
|
|
631
|
+
async openAppSettings() {
|
|
632
|
+
throw this.unavailable('openAppSettings is not available on web.');
|
|
633
|
+
}
|
|
634
|
+
async setDisplayStrings() {
|
|
635
|
+
// not available on web
|
|
636
|
+
}
|
|
637
|
+
async requestDevice(options) {
|
|
638
|
+
const filters = this.getFilters(options);
|
|
639
|
+
const device = await navigator.bluetooth.requestDevice({
|
|
640
|
+
filters: filters.length ? filters : undefined,
|
|
641
|
+
optionalServices: options === null || options === undefined ? undefined : options.optionalServices,
|
|
642
|
+
acceptAllDevices: filters.length === 0,
|
|
643
|
+
});
|
|
644
|
+
this.deviceMap.set(device.id, device);
|
|
645
|
+
const bleDevice = this.getBleDevice(device);
|
|
646
|
+
return bleDevice;
|
|
647
|
+
}
|
|
648
|
+
async requestLEScan(options) {
|
|
649
|
+
this.requestBleDeviceOptions = options;
|
|
650
|
+
const filters = this.getFilters(options);
|
|
651
|
+
await this.stopLEScan();
|
|
652
|
+
this.discoveredDevices = new Map();
|
|
653
|
+
navigator.bluetooth.removeEventListener('advertisementreceived', this.onAdvertisementReceivedCallback);
|
|
654
|
+
navigator.bluetooth.addEventListener('advertisementreceived', this.onAdvertisementReceivedCallback);
|
|
655
|
+
this.scan = await navigator.bluetooth.requestLEScan({
|
|
656
|
+
filters: filters.length ? filters : undefined,
|
|
657
|
+
acceptAllAdvertisements: filters.length === 0,
|
|
658
|
+
keepRepeatedDevices: options === null || options === undefined ? undefined : options.allowDuplicates,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
onAdvertisementReceived(event) {
|
|
662
|
+
var _a, _b, _c;
|
|
663
|
+
const deviceId = event.device.id;
|
|
664
|
+
this.deviceMap.set(deviceId, event.device);
|
|
665
|
+
const isNew = !this.discoveredDevices.has(deviceId);
|
|
666
|
+
// Apply service data filtering client-side (Web Bluetooth API doesn't support it in scan filters)
|
|
667
|
+
if (((_a = this.requestBleDeviceOptions) === null || _a === undefined ? undefined : _a.serviceData) && !this.matchesServiceDataFilter(event)) {
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (isNew || ((_b = this.requestBleDeviceOptions) === null || _b === undefined ? undefined : _b.allowDuplicates)) {
|
|
671
|
+
this.discoveredDevices.set(deviceId, true);
|
|
672
|
+
const device = this.getBleDevice(event.device);
|
|
673
|
+
const result = {
|
|
674
|
+
device,
|
|
675
|
+
localName: device.name,
|
|
676
|
+
rssi: event.rssi,
|
|
677
|
+
txPower: event.txPower,
|
|
678
|
+
manufacturerData: mapToObject(event.manufacturerData),
|
|
679
|
+
serviceData: mapToObject(event.serviceData),
|
|
680
|
+
uuids: (_c = event.uuids) === null || _c === undefined ? undefined : _c.map(webUUIDToString),
|
|
681
|
+
};
|
|
682
|
+
this.notifyListeners('onScanResult', result);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async stopLEScan() {
|
|
686
|
+
var _a;
|
|
687
|
+
if ((_a = this.scan) === null || _a === undefined ? undefined : _a.active) {
|
|
688
|
+
this.scan.stop();
|
|
689
|
+
}
|
|
690
|
+
this.scan = null;
|
|
691
|
+
}
|
|
692
|
+
async getDevices(options) {
|
|
693
|
+
const devices = await navigator.bluetooth.getDevices();
|
|
694
|
+
const bleDevices = devices
|
|
695
|
+
.filter((device) => options.deviceIds.includes(device.id))
|
|
696
|
+
.map((device) => {
|
|
697
|
+
this.deviceMap.set(device.id, device);
|
|
698
|
+
const bleDevice = this.getBleDevice(device);
|
|
699
|
+
return bleDevice;
|
|
700
|
+
});
|
|
701
|
+
return { devices: bleDevices };
|
|
702
|
+
}
|
|
703
|
+
async getConnectedDevices(_options) {
|
|
704
|
+
const devices = await navigator.bluetooth.getDevices();
|
|
705
|
+
const bleDevices = devices
|
|
706
|
+
.filter((device) => {
|
|
707
|
+
var _a;
|
|
708
|
+
return (_a = device.gatt) === null || _a === undefined ? undefined : _a.connected;
|
|
709
|
+
})
|
|
710
|
+
.map((device) => {
|
|
711
|
+
this.deviceMap.set(device.id, device);
|
|
712
|
+
const bleDevice = this.getBleDevice(device);
|
|
713
|
+
return bleDevice;
|
|
714
|
+
});
|
|
715
|
+
return { devices: bleDevices };
|
|
716
|
+
}
|
|
717
|
+
async getBondedDevices() {
|
|
718
|
+
return {};
|
|
719
|
+
}
|
|
720
|
+
async connect(options) {
|
|
721
|
+
var _a, _b;
|
|
722
|
+
const device = this.getDeviceFromMap(options.deviceId);
|
|
723
|
+
device.removeEventListener('gattserverdisconnected', this.onDisconnectedCallback);
|
|
724
|
+
device.addEventListener('gattserverdisconnected', this.onDisconnectedCallback);
|
|
725
|
+
const timeoutError = Symbol();
|
|
726
|
+
if (device.gatt === undefined) {
|
|
727
|
+
throw new Error('No gatt server available.');
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : this.DEFAULT_CONNECTION_TIMEOUT;
|
|
731
|
+
await runWithTimeout(device.gatt.connect(), timeout, timeoutError);
|
|
732
|
+
}
|
|
733
|
+
catch (error) {
|
|
734
|
+
// cancel pending connect call, does not work yet in chromium because of a bug:
|
|
735
|
+
// https://bugs.chromium.org/p/chromium/issues/detail?id=684073
|
|
736
|
+
await ((_b = device.gatt) === null || _b === undefined ? undefined : _b.disconnect());
|
|
737
|
+
if (error === timeoutError) {
|
|
738
|
+
throw new Error('Connection timeout');
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
onDisconnected(event) {
|
|
746
|
+
const deviceId = event.target.id;
|
|
747
|
+
const key = `disconnected|${deviceId}`;
|
|
748
|
+
this.notifyListeners(key, null);
|
|
749
|
+
}
|
|
750
|
+
async createBond(_options) {
|
|
751
|
+
throw this.unavailable('createBond is not available on web.');
|
|
752
|
+
}
|
|
753
|
+
async isBonded(_options) {
|
|
754
|
+
throw this.unavailable('isBonded is not available on web.');
|
|
755
|
+
}
|
|
756
|
+
async disconnect(options) {
|
|
757
|
+
var _a;
|
|
758
|
+
(_a = this.getDeviceFromMap(options.deviceId).gatt) === null || _a === undefined ? undefined : _a.disconnect();
|
|
759
|
+
}
|
|
760
|
+
async getServices(options) {
|
|
761
|
+
var _a, _b;
|
|
762
|
+
const services = (_b = (await ((_a = this.getDeviceFromMap(options.deviceId).gatt) === null || _a === undefined ? undefined : _a.getPrimaryServices()))) !== null && _b !== undefined ? _b : [];
|
|
763
|
+
const bleServices = [];
|
|
764
|
+
for (const service of services) {
|
|
765
|
+
const characteristics = await service.getCharacteristics();
|
|
766
|
+
const bleCharacteristics = [];
|
|
767
|
+
for (const characteristic of characteristics) {
|
|
768
|
+
bleCharacteristics.push({
|
|
769
|
+
uuid: characteristic.uuid,
|
|
770
|
+
properties: this.getProperties(characteristic),
|
|
771
|
+
descriptors: await this.getDescriptors(characteristic),
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
bleServices.push({ uuid: service.uuid, characteristics: bleCharacteristics });
|
|
775
|
+
}
|
|
776
|
+
return { services: bleServices };
|
|
777
|
+
}
|
|
778
|
+
async getDescriptors(characteristic) {
|
|
779
|
+
try {
|
|
780
|
+
const descriptors = await characteristic.getDescriptors();
|
|
781
|
+
return descriptors.map((descriptor) => ({
|
|
782
|
+
uuid: descriptor.uuid,
|
|
783
|
+
}));
|
|
784
|
+
}
|
|
785
|
+
catch (_a) {
|
|
786
|
+
return [];
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
getProperties(characteristic) {
|
|
790
|
+
return {
|
|
791
|
+
broadcast: characteristic.properties.broadcast,
|
|
792
|
+
read: characteristic.properties.read,
|
|
793
|
+
writeWithoutResponse: characteristic.properties.writeWithoutResponse,
|
|
794
|
+
write: characteristic.properties.write,
|
|
795
|
+
notify: characteristic.properties.notify,
|
|
796
|
+
indicate: characteristic.properties.indicate,
|
|
797
|
+
authenticatedSignedWrites: characteristic.properties.authenticatedSignedWrites,
|
|
798
|
+
reliableWrite: characteristic.properties.reliableWrite,
|
|
799
|
+
writableAuxiliaries: characteristic.properties.writableAuxiliaries,
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
async getCharacteristic(options) {
|
|
803
|
+
var _a;
|
|
804
|
+
const service = await ((_a = this.getDeviceFromMap(options.deviceId).gatt) === null || _a === undefined ? undefined : _a.getPrimaryService(options === null || options === undefined ? undefined : options.service));
|
|
805
|
+
return service === null || service === undefined ? undefined : service.getCharacteristic(options === null || options === undefined ? undefined : options.characteristic);
|
|
806
|
+
}
|
|
807
|
+
async getDescriptor(options) {
|
|
808
|
+
const characteristic = await this.getCharacteristic(options);
|
|
809
|
+
return characteristic === null || characteristic === undefined ? undefined : characteristic.getDescriptor(options === null || options === undefined ? undefined : options.descriptor);
|
|
810
|
+
}
|
|
811
|
+
async discoverServices(_options) {
|
|
812
|
+
throw this.unavailable('discoverServices is not available on web.');
|
|
813
|
+
}
|
|
814
|
+
async getMtu(_options) {
|
|
815
|
+
throw this.unavailable('getMtu is not available on web.');
|
|
816
|
+
}
|
|
817
|
+
async requestConnectionPriority(_options) {
|
|
818
|
+
throw this.unavailable('requestConnectionPriority is not available on web.');
|
|
819
|
+
}
|
|
820
|
+
async readRssi(_options) {
|
|
821
|
+
throw this.unavailable('readRssi is not available on web.');
|
|
822
|
+
}
|
|
823
|
+
async read(options) {
|
|
824
|
+
const characteristic = await this.getCharacteristic(options);
|
|
825
|
+
const value = await (characteristic === null || characteristic === undefined ? undefined : characteristic.readValue());
|
|
826
|
+
return { value };
|
|
827
|
+
}
|
|
828
|
+
async write(options) {
|
|
829
|
+
const characteristic = await this.getCharacteristic(options);
|
|
830
|
+
let dataView;
|
|
831
|
+
if (typeof options.value === 'string') {
|
|
832
|
+
dataView = hexStringToDataView(options.value);
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
dataView = options.value;
|
|
836
|
+
}
|
|
837
|
+
await (characteristic === null || characteristic === undefined ? undefined : characteristic.writeValueWithResponse(toArrayBufferDataView(dataView)));
|
|
838
|
+
}
|
|
839
|
+
async writeWithoutResponse(options) {
|
|
840
|
+
const characteristic = await this.getCharacteristic(options);
|
|
841
|
+
let dataView;
|
|
842
|
+
if (typeof options.value === 'string') {
|
|
843
|
+
dataView = hexStringToDataView(options.value);
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
dataView = options.value;
|
|
847
|
+
}
|
|
848
|
+
await (characteristic === null || characteristic === undefined ? undefined : characteristic.writeValueWithoutResponse(toArrayBufferDataView(dataView)));
|
|
849
|
+
}
|
|
850
|
+
async readDescriptor(options) {
|
|
851
|
+
const descriptor = await this.getDescriptor(options);
|
|
852
|
+
const value = await (descriptor === null || descriptor === undefined ? undefined : descriptor.readValue());
|
|
853
|
+
return { value };
|
|
854
|
+
}
|
|
855
|
+
async writeDescriptor(options) {
|
|
856
|
+
const descriptor = await this.getDescriptor(options);
|
|
857
|
+
let dataView;
|
|
858
|
+
if (typeof options.value === 'string') {
|
|
859
|
+
dataView = hexStringToDataView(options.value);
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
dataView = options.value;
|
|
863
|
+
}
|
|
864
|
+
await (descriptor === null || descriptor === undefined ? undefined : descriptor.writeValue(toArrayBufferDataView(dataView)));
|
|
865
|
+
}
|
|
866
|
+
async startNotifications(options) {
|
|
867
|
+
const characteristic = await this.getCharacteristic(options);
|
|
868
|
+
characteristic === null || characteristic === undefined ? undefined : characteristic.removeEventListener('characteristicvaluechanged', this.onCharacteristicValueChangedCallback);
|
|
869
|
+
characteristic === null || characteristic === undefined ? undefined : characteristic.addEventListener('characteristicvaluechanged', this.onCharacteristicValueChangedCallback);
|
|
870
|
+
await (characteristic === null || characteristic === undefined ? undefined : characteristic.startNotifications());
|
|
871
|
+
}
|
|
872
|
+
onCharacteristicValueChanged(event) {
|
|
873
|
+
var _a, _b;
|
|
874
|
+
const characteristic = event.target;
|
|
875
|
+
const key = `notification|${(_a = characteristic.service) === null || _a === undefined ? undefined : _a.device.id}|${(_b = characteristic.service) === null || _b === undefined ? undefined : _b.uuid}|${characteristic.uuid}`;
|
|
876
|
+
this.notifyListeners(key, {
|
|
877
|
+
value: characteristic.value,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
async stopNotifications(options) {
|
|
881
|
+
const characteristic = await this.getCharacteristic(options);
|
|
882
|
+
await (characteristic === null || characteristic === undefined ? undefined : characteristic.stopNotifications());
|
|
883
|
+
}
|
|
884
|
+
getFilters(options) {
|
|
885
|
+
var _a, _b;
|
|
886
|
+
const filters = [];
|
|
887
|
+
for (const service of (_a = options === null || options === undefined ? undefined : options.services) !== null && _a !== undefined ? _a : []) {
|
|
888
|
+
filters.push({
|
|
889
|
+
services: [service],
|
|
890
|
+
name: options === null || options === undefined ? undefined : options.name,
|
|
891
|
+
namePrefix: options === null || options === undefined ? undefined : options.namePrefix,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
if (((options === null || options === undefined ? undefined : options.name) || (options === null || options === undefined ? undefined : options.namePrefix)) && filters.length === 0) {
|
|
895
|
+
filters.push({
|
|
896
|
+
name: options.name,
|
|
897
|
+
namePrefix: options.namePrefix,
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
for (const manufacturerData of (_b = options === null || options === undefined ? undefined : options.manufacturerData) !== null && _b !== undefined ? _b : []) {
|
|
901
|
+
// Cast to any to avoid type incompatibility - conversion is handled in bleClient.ts
|
|
902
|
+
filters.push({
|
|
903
|
+
manufacturerData: [manufacturerData],
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
// Note: Web Bluetooth API does not support service data in scan filters.
|
|
907
|
+
// Service data filtering will be done client-side in onAdvertisementReceived.
|
|
908
|
+
// We still accept serviceData in options for API consistency across platforms.
|
|
909
|
+
return filters;
|
|
910
|
+
}
|
|
911
|
+
matchesServiceDataFilter(event) {
|
|
912
|
+
var _a;
|
|
913
|
+
const filters = (_a = this.requestBleDeviceOptions) === null || _a === undefined ? undefined : _a.serviceData;
|
|
914
|
+
if (!filters || filters.length === 0) {
|
|
915
|
+
return true; // No filters, accept all
|
|
916
|
+
}
|
|
917
|
+
if (!event.serviceData) {
|
|
918
|
+
return false; // No service data in advertisement
|
|
919
|
+
}
|
|
920
|
+
// Check if any filter matches (OR logic)
|
|
921
|
+
for (const filter of filters) {
|
|
922
|
+
const serviceData = event.serviceData.get(filter.serviceUuid);
|
|
923
|
+
if (!serviceData) {
|
|
924
|
+
continue; // This filter doesn't match, try next
|
|
925
|
+
}
|
|
926
|
+
// If we have service data for this UUID
|
|
927
|
+
if (!filter.dataPrefix) {
|
|
928
|
+
return true; // Filter matched by service UUID alone
|
|
929
|
+
}
|
|
930
|
+
// Check data prefix (DataView on web)
|
|
931
|
+
const data = new Uint8Array(serviceData.buffer);
|
|
932
|
+
const prefixView = filter.dataPrefix;
|
|
933
|
+
if (data.length < prefixView.byteLength) {
|
|
934
|
+
continue; // Data too short
|
|
935
|
+
}
|
|
936
|
+
// Apply mask if provided
|
|
937
|
+
if (filter.mask) {
|
|
938
|
+
const maskView = filter.mask;
|
|
939
|
+
let matches = true;
|
|
940
|
+
for (let i = 0; i < prefixView.byteLength; i++) {
|
|
941
|
+
if ((data[i] & maskView.getUint8(i)) !== (prefixView.getUint8(i) & maskView.getUint8(i))) {
|
|
942
|
+
matches = false;
|
|
943
|
+
break;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (matches) {
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
// Check if data starts with prefix
|
|
952
|
+
let matches = true;
|
|
953
|
+
for (let i = 0; i < prefixView.byteLength; i++) {
|
|
954
|
+
if (data[i] !== prefixView.getUint8(i)) {
|
|
955
|
+
matches = false;
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
if (matches) {
|
|
960
|
+
return true;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return false; // No filter matched
|
|
965
|
+
}
|
|
966
|
+
getDeviceFromMap(deviceId) {
|
|
967
|
+
const device = this.deviceMap.get(deviceId);
|
|
968
|
+
if (device === undefined) {
|
|
969
|
+
throw new Error('Device not found. Call "requestDevice", "requestLEScan" or "getDevices" first.');
|
|
970
|
+
}
|
|
971
|
+
return device;
|
|
972
|
+
}
|
|
973
|
+
getBleDevice(device) {
|
|
974
|
+
var _a;
|
|
975
|
+
const bleDevice = {
|
|
976
|
+
deviceId: device.id,
|
|
977
|
+
// use undefined instead of null if name is not available
|
|
978
|
+
name: (_a = device.name) !== null && _a !== undefined ? _a : undefined,
|
|
979
|
+
};
|
|
980
|
+
return bleDevice;
|
|
981
|
+
}
|
|
982
982
|
}
|
|
983
983
|
|
|
984
984
|
var web = /*#__PURE__*/Object.freeze({
|