@stoprocent/noble 2.3.14 → 2.3.16
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/lib/characteristic.js +18 -18
- package/lib/descriptor.js +4 -4
- package/lib/noble-event-emitter.js +27 -0
- package/lib/noble.js +25 -30
- package/lib/peripheral.js +8 -8
- package/lib/service.js +4 -4
- package/lib/win/src/peripheral_winrt.cc +24 -7
- package/lib/win/src/peripheral_winrt.h +1 -0
- package/package.json +2 -2
- package/prebuilds/darwin-x64+arm64/@stoprocent+noble.node +0 -0
- package/prebuilds/win32-ia32/@stoprocent+noble.node +0 -0
- package/prebuilds/win32-x64/@stoprocent+noble.node +0 -0
package/lib/characteristic.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const
|
|
1
|
+
const NobleEventEmitter = require('./noble-event-emitter');
|
|
2
2
|
|
|
3
3
|
const characteristics = require('./characteristics.json');
|
|
4
4
|
|
|
5
|
-
class Characteristic extends
|
|
5
|
+
class Characteristic extends NobleEventEmitter {
|
|
6
6
|
|
|
7
7
|
constructor (noble, peripheralId, serviceUuid, uuid, properties) {
|
|
8
8
|
super();
|
|
@@ -81,7 +81,7 @@ class Characteristic extends EventEmitter {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
if (callback) {
|
|
84
|
-
this.
|
|
84
|
+
this.onceExclusive('write', error => callback(error));
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
this._noble.write(
|
|
@@ -134,7 +134,7 @@ class Characteristic extends EventEmitter {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
if (callback) {
|
|
137
|
-
this.
|
|
137
|
+
this.onceExclusive('notify', (state, error) => callback(error, state));
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
this._noble.notify(
|
|
@@ -180,30 +180,30 @@ class Characteristic extends EventEmitter {
|
|
|
180
180
|
} else if (notifying) {
|
|
181
181
|
// Wait for more data or notify=false
|
|
182
182
|
await new Promise(resolve => {
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
let resolved = false;
|
|
184
|
+
|
|
185
|
+
const cleanup = () => {
|
|
186
|
+
if (resolved) return;
|
|
187
|
+
resolved = true;
|
|
185
188
|
this.removeListener('data', tempDataListener);
|
|
186
189
|
this.removeListener('notify', tempNotifyListener);
|
|
187
190
|
resolve();
|
|
188
191
|
};
|
|
189
|
-
|
|
192
|
+
|
|
193
|
+
const tempDataListener = () => cleanup();
|
|
194
|
+
|
|
190
195
|
const tempNotifyListener = (state) => {
|
|
191
196
|
if (state === false) {
|
|
192
|
-
|
|
193
|
-
this.removeListener('notify', tempNotifyListener);
|
|
194
|
-
resolve();
|
|
197
|
+
cleanup();
|
|
195
198
|
}
|
|
196
199
|
};
|
|
197
|
-
|
|
198
|
-
// Set up temporary listeners
|
|
200
|
+
|
|
199
201
|
this.once('data', tempDataListener);
|
|
200
202
|
this.once('notify', tempNotifyListener);
|
|
201
|
-
|
|
203
|
+
|
|
202
204
|
// Clean up if we already have notifications (race condition)
|
|
203
205
|
if (notifications.length > 0) {
|
|
204
|
-
|
|
205
|
-
this.removeListener('notify', tempNotifyListener);
|
|
206
|
-
resolve();
|
|
206
|
+
cleanup();
|
|
207
207
|
}
|
|
208
208
|
});
|
|
209
209
|
}
|
|
@@ -219,7 +219,7 @@ class Characteristic extends EventEmitter {
|
|
|
219
219
|
|
|
220
220
|
discoverDescriptors (callback) {
|
|
221
221
|
if (callback) {
|
|
222
|
-
this.
|
|
222
|
+
this.onceExclusive('descriptorsDiscover', (descriptors, error) => callback(error, descriptors));
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
this._noble.discoverDescriptors(
|
|
@@ -239,7 +239,7 @@ class Characteristic extends EventEmitter {
|
|
|
239
239
|
|
|
240
240
|
broadcast (broadcast, callback) {
|
|
241
241
|
if (callback) {
|
|
242
|
-
this.
|
|
242
|
+
this.onceExclusive('broadcast', error => callback(error));
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
this._noble.broadcast(
|
package/lib/descriptor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const
|
|
1
|
+
const NobleEventEmitter = require('./noble-event-emitter');
|
|
2
2
|
const descriptors = require('./descriptors.json');
|
|
3
3
|
|
|
4
|
-
class Descriptor extends
|
|
4
|
+
class Descriptor extends NobleEventEmitter {
|
|
5
5
|
constructor (noble, peripheralId, serviceUuid, characteristicUuid, uuid) {
|
|
6
6
|
super();
|
|
7
7
|
|
|
@@ -31,7 +31,7 @@ class Descriptor extends EventEmitter {
|
|
|
31
31
|
|
|
32
32
|
readValue (callback) {
|
|
33
33
|
if (callback) {
|
|
34
|
-
this.
|
|
34
|
+
this.onceExclusive('valueRead', (data, error) => callback(error, data));
|
|
35
35
|
}
|
|
36
36
|
this._noble.readValue(
|
|
37
37
|
this._peripheralId,
|
|
@@ -53,7 +53,7 @@ class Descriptor extends EventEmitter {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
if (callback) {
|
|
56
|
-
this.
|
|
56
|
+
this.onceExclusive('valueWrite', error => callback(error));
|
|
57
57
|
}
|
|
58
58
|
this._noble.writeValue(
|
|
59
59
|
this._peripheralId,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { EventEmitter } = require('events');
|
|
2
|
+
|
|
3
|
+
class NobleEventEmitter extends EventEmitter {
|
|
4
|
+
/**
|
|
5
|
+
* Like once(), but ensures at most one listener exists for the given event.
|
|
6
|
+
* If a previous exclusive listener was registered for the same event, it is
|
|
7
|
+
* removed before the new one is added. This prevents listener accumulation
|
|
8
|
+
* when a method is called repeatedly before the event fires.
|
|
9
|
+
*/
|
|
10
|
+
onceExclusive (event, callback) {
|
|
11
|
+
if (!this._exclusiveCallbacks) {
|
|
12
|
+
this._exclusiveCallbacks = new Map();
|
|
13
|
+
}
|
|
14
|
+
const prev = this._exclusiveCallbacks.get(event);
|
|
15
|
+
if (prev) {
|
|
16
|
+
this.removeListener(event, prev);
|
|
17
|
+
}
|
|
18
|
+
const wrappedCallback = (...args) => {
|
|
19
|
+
this._exclusiveCallbacks.delete(event);
|
|
20
|
+
callback(...args);
|
|
21
|
+
};
|
|
22
|
+
this._exclusiveCallbacks.set(event, wrappedCallback);
|
|
23
|
+
this.once(event, wrappedCallback);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = NobleEventEmitter;
|
package/lib/noble.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
const debug = require('debug')('noble');
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const NobleEventEmitter = require('./noble-event-emitter');
|
|
4
4
|
|
|
5
5
|
const Peripheral = require('./peripheral');
|
|
6
6
|
const Service = require('./service');
|
|
7
7
|
const Characteristic = require('./characteristic');
|
|
8
8
|
const Descriptor = require('./descriptor');
|
|
9
9
|
|
|
10
|
-
class Noble extends
|
|
10
|
+
class Noble extends NobleEventEmitter {
|
|
11
11
|
|
|
12
12
|
constructor (bindings) {
|
|
13
13
|
super();
|
|
@@ -142,7 +142,7 @@ class Noble extends EventEmitter {
|
|
|
142
142
|
|
|
143
143
|
setScanParameters (interval, window, callback) {
|
|
144
144
|
if (callback) {
|
|
145
|
-
this.
|
|
145
|
+
this.onceExclusive('scanParametersSet', callback);
|
|
146
146
|
}
|
|
147
147
|
this._bindings.setScanParameters(interval, window);
|
|
148
148
|
}
|
|
@@ -200,7 +200,7 @@ class Noble extends EventEmitter {
|
|
|
200
200
|
}
|
|
201
201
|
} else {
|
|
202
202
|
if (callback) {
|
|
203
|
-
this.
|
|
203
|
+
this.onceExclusive('scanStart', filterDuplicates => callback(null, filterDuplicates));
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
this._discoveredPeripherals.clear();
|
|
@@ -235,7 +235,7 @@ class Noble extends EventEmitter {
|
|
|
235
235
|
return;
|
|
236
236
|
}
|
|
237
237
|
if (callback) {
|
|
238
|
-
this.
|
|
238
|
+
this.onceExclusive('scanStop', callback);
|
|
239
239
|
}
|
|
240
240
|
this._bindings.stopScanning();
|
|
241
241
|
}
|
|
@@ -272,38 +272,33 @@ class Noble extends EventEmitter {
|
|
|
272
272
|
} else if (scanning) {
|
|
273
273
|
// Wait for either a new device or scan stop
|
|
274
274
|
await new Promise(resolve => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const tempScanStopListener = () => {
|
|
275
|
+
let resolved = false;
|
|
276
|
+
let timeoutId = null;
|
|
277
|
+
|
|
278
|
+
const cleanup = () => {
|
|
279
|
+
if (resolved) return;
|
|
280
|
+
resolved = true;
|
|
282
281
|
this.removeListener('discover', tempDiscoverListener);
|
|
282
|
+
this.removeListener('scanStop', tempScanStopListener);
|
|
283
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
283
284
|
resolve();
|
|
284
285
|
};
|
|
286
|
+
|
|
287
|
+
const tempDiscoverListener = () => cleanup();
|
|
288
|
+
const tempScanStopListener = () => cleanup();
|
|
289
|
+
|
|
290
|
+
this.once('discover', tempDiscoverListener);
|
|
285
291
|
this.once('scanStop', tempScanStopListener);
|
|
286
|
-
|
|
292
|
+
|
|
287
293
|
// Handle race condition where a device might arrive during promise setup
|
|
288
294
|
if (deviceQueue.length > 0) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
resolve();
|
|
295
|
+
cleanup();
|
|
296
|
+
return;
|
|
292
297
|
}
|
|
293
|
-
|
|
294
|
-
//
|
|
295
|
-
// This can be removed to eliminate timer dependency
|
|
298
|
+
|
|
299
|
+
// Add a maximum wait time with proper cleanup
|
|
296
300
|
if (scanning) {
|
|
297
|
-
|
|
298
|
-
this.removeListener('discover', tempDiscoverListener);
|
|
299
|
-
this.removeListener('scanStop', tempScanStopListener);
|
|
300
|
-
resolve();
|
|
301
|
-
}, 1000);
|
|
302
|
-
|
|
303
|
-
// Make sure we clear the timeout if we resolve before timeout
|
|
304
|
-
const clearTimeoutFn = () => clearTimeout(timeoutId);
|
|
305
|
-
this.once('discover', clearTimeoutFn);
|
|
306
|
-
this.once('scanStop', clearTimeoutFn);
|
|
301
|
+
timeoutId = setTimeout(() => cleanup(), 1000);
|
|
307
302
|
}
|
|
308
303
|
});
|
|
309
304
|
}
|
|
@@ -383,7 +378,7 @@ class Noble extends EventEmitter {
|
|
|
383
378
|
// Check if callback is a function
|
|
384
379
|
if (typeof callback === 'function') {
|
|
385
380
|
// Add a one-time listener for this specific event
|
|
386
|
-
this.
|
|
381
|
+
this.onceExclusive(`connect:${identifier}`, error => callback(error, this._peripherals.get(identifier)));
|
|
387
382
|
}
|
|
388
383
|
|
|
389
384
|
// Proceed to initiate the connection
|
package/lib/peripheral.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const
|
|
1
|
+
const NobleEventEmitter = require('./noble-event-emitter');
|
|
2
2
|
|
|
3
|
-
class Peripheral extends
|
|
3
|
+
class Peripheral extends NobleEventEmitter {
|
|
4
4
|
constructor (noble, id, address, addressType, connectable, advertisement, rssi, scannable) {
|
|
5
5
|
super();
|
|
6
6
|
this._noble = noble;
|
|
@@ -40,7 +40,7 @@ class Peripheral extends EventEmitter {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (callback) {
|
|
43
|
-
this.
|
|
43
|
+
this.onceExclusive('connect', error => callback(error));
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
if (this.state === 'connected') {
|
|
@@ -66,7 +66,7 @@ class Peripheral extends EventEmitter {
|
|
|
66
66
|
|
|
67
67
|
disconnect (callback) {
|
|
68
68
|
if (callback) {
|
|
69
|
-
this.
|
|
69
|
+
this.onceExclusive('disconnect', () => callback(null));
|
|
70
70
|
}
|
|
71
71
|
this.state = 'disconnecting';
|
|
72
72
|
this._noble.disconnect(this.id);
|
|
@@ -80,7 +80,7 @@ class Peripheral extends EventEmitter {
|
|
|
80
80
|
|
|
81
81
|
updateRssi (callback) {
|
|
82
82
|
if (callback) {
|
|
83
|
-
this.
|
|
83
|
+
this.onceExclusive('rssiUpdate', (rssi, error) => callback(error, rssi));
|
|
84
84
|
}
|
|
85
85
|
this._noble.updateRssi(this.id);
|
|
86
86
|
}
|
|
@@ -95,7 +95,7 @@ class Peripheral extends EventEmitter {
|
|
|
95
95
|
|
|
96
96
|
discoverServices (uuids, callback) {
|
|
97
97
|
if (callback) {
|
|
98
|
-
this.
|
|
98
|
+
this.onceExclusive('servicesDiscover', (services, error) => callback(error, services));
|
|
99
99
|
}
|
|
100
100
|
this._noble.discoverServices(this.id, uuids);
|
|
101
101
|
}
|
|
@@ -172,7 +172,7 @@ class Peripheral extends EventEmitter {
|
|
|
172
172
|
|
|
173
173
|
readHandle (handle, callback) {
|
|
174
174
|
if (callback) {
|
|
175
|
-
this.
|
|
175
|
+
this.onceExclusive(`handleRead${handle}`, (data, error) => callback(error, data));
|
|
176
176
|
}
|
|
177
177
|
this._noble.readHandle(this.id, handle);
|
|
178
178
|
}
|
|
@@ -191,7 +191,7 @@ class Peripheral extends EventEmitter {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
if (callback) {
|
|
194
|
-
this.
|
|
194
|
+
this.onceExclusive(`handleWrite${handle}`, (error) => callback(error));
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
this._noble.writeHandle(this.id, handle, data, withoutResponse);
|
package/lib/service.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const
|
|
1
|
+
const NobleEventEmitter = require('./noble-event-emitter');
|
|
2
2
|
const services = require('./services.json');
|
|
3
3
|
|
|
4
|
-
class Service extends
|
|
4
|
+
class Service extends NobleEventEmitter {
|
|
5
5
|
|
|
6
6
|
constructor (noble, peripheralId, uuid) {
|
|
7
7
|
super();
|
|
@@ -33,7 +33,7 @@ class Service extends EventEmitter {
|
|
|
33
33
|
|
|
34
34
|
discoverIncludedServices (serviceUuids, callback) {
|
|
35
35
|
if (callback) {
|
|
36
|
-
this.
|
|
36
|
+
this.onceExclusive('includedServicesDiscover', (includedServiceUuids, error) => callback(error, includedServiceUuids));
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
this._noble.discoverIncludedServices(
|
|
@@ -53,7 +53,7 @@ class Service extends EventEmitter {
|
|
|
53
53
|
|
|
54
54
|
discoverCharacteristics (characteristicUuids, callback) {
|
|
55
55
|
if (callback) {
|
|
56
|
-
this.
|
|
56
|
+
this.onceExclusive('characteristicsDiscover', (characteristics, error) => callback(error, characteristics));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
this._noble.discoverCharacteristics(
|
|
@@ -92,16 +92,21 @@ void PeripheralWinrt::ProcessServiceData(const BluetoothLEAdvertisementDataSecti
|
|
|
92
92
|
dr.Close();
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
static std::string advSectionToString(const winrt::Windows::Devices::Bluetooth::Advertisement::BluetoothLEAdvertisementDataSection &sec)
|
|
96
|
+
{
|
|
97
|
+
std::string result;
|
|
98
|
+
result.resize(sec.Data().Length());
|
|
99
|
+
auto reader = DataReader::FromBuffer(sec.Data());
|
|
100
|
+
reader.ReadBytes(winrt::array_view<uint8_t>(
|
|
101
|
+
reinterpret_cast<uint8_t*>(result.data()),
|
|
102
|
+
reinterpret_cast<uint8_t*>(result.data() + result.size())
|
|
103
|
+
));
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
95
107
|
void PeripheralWinrt::Update(const int rssiValue, const BluetoothLEAdvertisement& advertisment,
|
|
96
108
|
const BluetoothLEAdvertisementType& advertismentType)
|
|
97
109
|
{
|
|
98
|
-
// Handle name
|
|
99
|
-
std::string localName = ws2s(advertisment.LocalName().c_str());
|
|
100
|
-
if (!localName.empty())
|
|
101
|
-
{
|
|
102
|
-
name = std::optional<std::string>(localName);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
110
|
connectable = advertismentType == BluetoothLEAdvertisementType::ConnectableUndirected ||
|
|
106
111
|
advertismentType == BluetoothLEAdvertisementType::ConnectableDirected;
|
|
107
112
|
|
|
@@ -144,6 +149,18 @@ void PeripheralWinrt::Update(const int rssiValue, const BluetoothLEAdvertisement
|
|
|
144
149
|
{
|
|
145
150
|
ProcessServiceData(ds, 16); // 16 bytes for 128-bit UUID
|
|
146
151
|
}
|
|
152
|
+
else if (ds.DataType() == BluetoothLEAdvertisementDataTypes::ShortenedLocalName())
|
|
153
|
+
{
|
|
154
|
+
if (!nameIsComplete)
|
|
155
|
+
{
|
|
156
|
+
name = advSectionToString(ds); // use shortened local name if no complete name is available
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (ds.DataType() == BluetoothLEAdvertisementDataTypes::CompleteLocalName())
|
|
160
|
+
{
|
|
161
|
+
name = advSectionToString(ds);
|
|
162
|
+
nameIsComplete = true; // set the flag to ignore shortened local names
|
|
163
|
+
}
|
|
147
164
|
}
|
|
148
165
|
|
|
149
166
|
// Handle service UUIDs
|
|
@@ -73,6 +73,7 @@ public:
|
|
|
73
73
|
std::optional<GattSession> gattSession;
|
|
74
74
|
winrt::event_token maxPduSizeChangedToken;
|
|
75
75
|
std::unordered_map<winrt::guid, CachedService> cachedServices;
|
|
76
|
+
bool nameIsComplete { false }; // `name` is the Complete Local Name
|
|
76
77
|
|
|
77
78
|
private:
|
|
78
79
|
void GetServiceFromDevice(winrt::guid serviceUuid,
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"name": "@stoprocent/noble",
|
|
8
8
|
"description": "A Node.js BLE (Bluetooth Low Energy) central library.",
|
|
9
|
-
"version": "2.3.
|
|
9
|
+
"version": "2.3.16",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/stoprocent/noble.git"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"patch-package": "^8.0.0"
|
|
35
35
|
},
|
|
36
36
|
"optionalDependencies": {
|
|
37
|
-
"@stoprocent/bluetooth-hci-socket": "^2.2.
|
|
37
|
+
"@stoprocent/bluetooth-hci-socket": "^2.2.5"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@babel/eslint-parser": "^7.27.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|