@stoprocent/bleno 0.8.6 → 0.10.0

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.
Files changed (42) hide show
  1. package/README.md +197 -30
  2. package/examples/echo/async.js +47 -0
  3. package/examples/echo/characteristic.js +15 -17
  4. package/examples/echo/main.js +19 -1
  5. package/examples/uart/main.js +3 -2
  6. package/examples/with-bindings/main.js +35 -0
  7. package/index.d.ts +166 -121
  8. package/index.js +5 -1
  9. package/lib/bleno.js +108 -17
  10. package/lib/characteristic.js +27 -10
  11. package/lib/common/include/ThreadSafeCallback.h +95 -0
  12. package/lib/hci-socket/acl-stream.js +3 -3
  13. package/lib/hci-socket/bindings.js +36 -29
  14. package/lib/hci-socket/gatt.js +177 -105
  15. package/lib/hci-socket/hci.js +5 -3
  16. package/lib/hci-socket/mgmt.js +1 -1
  17. package/lib/hci-socket/smp.js +5 -4
  18. package/lib/mac/binding.gyp +2 -6
  19. package/lib/mac/bindings.js +2 -3
  20. package/lib/mac/src/ble_peripheral_manager.h +3 -7
  21. package/lib/mac/src/ble_peripheral_manager.mm +101 -171
  22. package/lib/mac/src/bleno_mac.h +0 -1
  23. package/lib/mac/src/bleno_mac.mm +21 -33
  24. package/lib/mac/src/callbacks.h +12 -48
  25. package/lib/mac/src/callbacks.mm +35 -45
  26. package/lib/mac/src/napi_objc.mm +9 -30
  27. package/lib/mac/src/objc_cpp.h +2 -2
  28. package/lib/mac/src/objc_cpp.mm +3 -37
  29. package/lib/resolve-bindings.js +27 -6
  30. package/package.json +7 -10
  31. package/prebuilds/darwin-x64+arm64/@stoprocent+bleno.node +0 -0
  32. package/prebuilds/win32-ia32/@stoprocent+bleno.node +0 -0
  33. package/prebuilds/win32-x64/@stoprocent+bleno.node +0 -0
  34. package/test/characteristic.test.js +158 -11
  35. package/examples/battery-service/README.md +0 -14
  36. package/examples/blink1/README.md +0 -44
  37. package/examples/pizza/README.md +0 -16
  38. package/lib/mac/src/noble_mac.h +0 -34
  39. package/lib/mac/src/noble_mac.mm +0 -267
  40. package/lib/mac/src/peripheral.h +0 -23
  41. package/with-bindings.js +0 -5
  42. package/with-custom-binding.js +0 -6
@@ -4,12 +4,13 @@
4
4
  //
5
5
  // Created by Georg Vienna on 30.08.18.
6
6
  //
7
- #include "callbacks.h"
8
-
9
- #import <napi-thread-safe-callback.hpp>
10
7
 
8
+ #include "callbacks.h"
11
9
  #include "napi_objc.h"
10
+ #include "objc_cpp.h"
11
+ #include "ThreadSafeCallback.h"
12
12
 
13
+ #define _a(val) Napi::String::New(env, convertToBlenoAddress(val))
13
14
  #define _s(val) Napi::String::New(env, val)
14
15
  #define _b(val) Napi::Boolean::New(env, val)
15
16
  #define _n(val) Napi::Number::New(env, val)
@@ -33,23 +34,28 @@ void Emit::Wrap(const Napi::Value& receiver, const Napi::Function& callback) {
33
34
  mCallback = std::make_shared<ThreadSafeCallback>(receiver, callback);
34
35
  }
35
36
 
36
- void Emit::AdvertisingStart() {
37
- mCallback->call([](Napi::Env env, std::vector<napi_value>& args) {
38
- // emit('advertisingStart', error)
39
- args = { _s("advertisingStart") };
37
+ void Emit::AdvertisingStart(NSError * _Nullable error) {
38
+ mCallback->call([error](Napi::Env env, std::vector<napi_value>& args) {
39
+ const char *cerror = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
40
+ args = { _s("advertisingStart"), error ? Napi::Error::New(env, cerror).Value() : env.Null() };
40
41
  });
41
42
  }
42
43
 
43
- void Emit::ServicesSet() {
44
+ void Emit::AdvertisingStop() {
44
45
  mCallback->call([](Napi::Env env, std::vector<napi_value>& args) {
45
- // emit('servicesSet', this._setServicesError)
46
- args = { _s("servicesSet") };
46
+ args = { _s("advertisingStop") };
47
+ });
48
+ }
49
+
50
+ void Emit::ServicesSet(NSError * _Nullable error) {
51
+ mCallback->call([error](Napi::Env env, std::vector<napi_value>& args) {
52
+ const char *cerror = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
53
+ args = { _s("servicesSet"), error ? Napi::Error::New(env, cerror).Value() : env.Null() };
47
54
  });
48
55
  }
49
56
 
50
57
  void Emit::StateChange(const std::string& state) {
51
58
  mCallback->call([state](Napi::Env env, std::vector<napi_value>& args) {
52
- // emit('stateChange', state);
53
59
  args = { _s("stateChange"), _s(state) };
54
60
  });
55
61
  }
@@ -58,67 +64,51 @@ void EmitCharacteristic::Wrap(const Napi::Value& receiver, const Napi::Function&
58
64
  mCallback = std::make_shared<ThreadSafeCallback>(receiver, callback);
59
65
  }
60
66
 
61
- void EmitCharacteristic::ReadRequest(int offset, std::function<void (int, NSData *)> completion) {
62
- mCallback->call([offset, completion](Napi::Env env, std::vector<napi_value>& args) {
63
- // callback(result, data)
67
+ void EmitCharacteristic::ReadRequest(NSUUID *handle, uint16_t offset, std::function<void (uint16_t, NSData *)> completion) {
68
+ mCallback->call([handle, offset, completion](Napi::Env env, std::vector<napi_value>& args) {
64
69
  auto callable = [completion](const Napi::CallbackInfo& info){
65
70
  completion(info[0].As<Napi::Number>().Int32Value(),
66
71
  napiToData(info[1].As<Napi::Buffer<Byte>>()));
67
72
  };
68
73
  Napi::Function cb = Napi::Function::New(env, callable);
69
-
70
- // emit('readRequest', offset, callback);
71
- args = { _s("readRequest"), _n(offset), cb };
74
+ args = { _s("readRequest"), _a(handle), _n(offset), cb };
72
75
  });
73
76
  }
74
77
 
75
- void EmitCharacteristic::WriteRequest(NSData *data, int offset, bool ignoreResponse, std::function<void (int)> completion) {
76
- mCallback->call([data, offset, ignoreResponse, completion](Napi::Env env, std::vector<napi_value>& args) {
77
- // callback(result)
78
+ void EmitCharacteristic::WriteRequest(NSUUID *handle, NSData *data, uint16_t offset, bool ignoreResponse, std::function<void (uint16_t)> completion) {
79
+ mCallback->call([handle, data, offset, ignoreResponse, completion](Napi::Env env, std::vector<napi_value>& args) {
78
80
  auto callable = [completion](const Napi::CallbackInfo& info){
79
81
  completion(info[0].As<Napi::Number>().Int32Value());
80
82
  };
81
83
  Napi::Function cb = Napi::Function::New(env, callable);
82
-
83
- // emit('writeRequest', data, offset, ignoreResponse, callback)
84
- args = { _s("writeRequest"), toBufferFromNSData(env, data), _n(offset), _b(ignoreResponse), cb };
84
+ args = { _s("writeRequest"), _a(handle), toBufferFromNSData(env, data), _n(offset), _b(ignoreResponse), cb };
85
85
  });
86
86
  }
87
87
 
88
- void EmitCharacteristic::Subscribe(int maxValueSize, std::function<void (NSData *)> completion) {
89
- mCallback->call([maxValueSize, completion](Napi::Env env, std::vector<napi_value>& args) {
90
- // callback(data)
88
+ void EmitCharacteristic::Subscribe(NSUUID *handle, uint16_t maxValueSize, std::function<void (NSData *)> completion) {
89
+ mCallback->call([handle, maxValueSize, completion](Napi::Env env, std::vector<napi_value>& args) {
91
90
  auto callable = [completion](const Napi::CallbackInfo& info){
92
91
  completion(napiToData(info[0].As<Napi::Buffer<Byte>>()));
93
92
  };
94
93
  Napi::Function cb = Napi::Function::New(env, callable);
95
-
96
- // emit('subscribe', maxValueSize, callback)
97
- args = { _s("subscribe"), _n(maxValueSize), cb };
94
+ args = { _s("subscribe"), _a(handle), _n(maxValueSize), cb };
98
95
  });
99
96
  }
100
97
 
101
- void EmitCharacteristic::Unsubscribe() {
102
- mCallback->call([](Napi::Env env, std::vector<napi_value>& args) {
103
- // emit('unsubscribe')
104
- args = { _s("unsubscribe") };
98
+ void EmitCharacteristic::Unsubscribe(NSUUID *handle) {
99
+ mCallback->call([handle](Napi::Env env, std::vector<napi_value>& args) {
100
+ args = { _s("unsubscribe"), _a(handle) };
105
101
  });
106
102
  }
107
103
 
108
- void EmitCharacteristic::Notify() {
109
- mCallback->call([](Napi::Env env, std::vector<napi_value>& args) {
110
- // emit('notify')
111
- args = { _s("notify") };
104
+ void EmitCharacteristic::Notify(NSUUID *handle) {
105
+ mCallback->call([handle](Napi::Env env, std::vector<napi_value>& args) {
106
+ args = { _s("notify"), _a(handle) };
112
107
  });
113
108
  }
114
109
 
115
- void EmitCharacteristic::Indicate() {
116
- mCallback->call([](Napi::Env env, std::vector<napi_value>& args) {
117
- // emit('indicate')
118
- args = { _s("indicate") };
110
+ void EmitCharacteristic::Indicate(NSUUID *handle) {
111
+ mCallback->call([handle](Napi::Env env, std::vector<napi_value>& args) {
112
+ args = { _s("indicate"), _a(handle) };
119
113
  });
120
114
  }
121
-
122
-
123
- // emit('notify');
124
- // emit('indicate');
@@ -25,7 +25,7 @@ NSString* napiToUuidString(Napi::String string) {
25
25
 
26
26
  NSArray* napiToUuidArray(Napi::Array array) {
27
27
  NSMutableArray* serviceUuids = [NSMutableArray arrayWithCapacity:array.Length()];
28
- for(size_t i = 0; i < array.Length(); i++) {
28
+ for(uint32_t i = 0; i < array.Length(); i++) {
29
29
  Napi::Value val = array[i];
30
30
  [serviceUuids addObject:napiToUuidString(val.As<Napi::String>())];
31
31
  }
@@ -37,15 +37,14 @@ NSData* napiToData(Napi::Buffer<Byte> buffer) {
37
37
  }
38
38
 
39
39
  NSNumber* napiToNumber(Napi::Number number) {
40
- return [NSNumber numberWithInt:number.Int64Value()];
40
+ return [NSNumber numberWithLong:number.Int64Value()];
41
41
  }
42
42
 
43
43
  NSArray<CBMutableService *> *napiArrayToCBMutableServices(Napi::Array array) {
44
- // NSLog(@"napiArrayToCBMutableServices");
45
44
 
46
45
  NSMutableArray *services = [NSMutableArray array];
47
46
 
48
- for (size_t i = 0; i < array.Length(); i++) {
47
+ for (uint32_t i = 0; i < array.Length(); i++) {
49
48
  Napi::Value v = array[i];
50
49
  Napi::Object obj = v.As<Napi::Object>();
51
50
 
@@ -56,12 +55,8 @@ NSArray<CBMutableService *> *napiArrayToCBMutableServices(Napi::Array array) {
56
55
  }
57
56
 
58
57
  CBMutableService *napiToCBMutableService(Napi::Object obj) {
59
- // NSLog(@"napiToCBMutableService");
60
-
61
58
  NSString *uuid = napiToUuidString(obj.Get("uuid").ToString());
62
59
 
63
- // NSLog(@"napiArrayToCBMutableService: uuid:%@", uuid);
64
-
65
60
  CBMutableService *service = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:uuid]
66
61
  primary:YES];
67
62
 
@@ -73,11 +68,9 @@ CBMutableService *napiToCBMutableService(Napi::Object obj) {
73
68
 
74
69
 
75
70
  NSArray<CBMutableCharacteristic *> *napiArrayToCBMutableCharacteristics(Napi::Array array) {
76
- // NSLog(@"napiArrayToCBMutableCharacteristics");
77
-
78
71
  NSMutableArray *characteristics = [NSMutableArray array];
79
72
 
80
- for (size_t i = 0; i < array.Length(); i++) {
73
+ for (uint32_t i = 0; i < array.Length(); i++) {
81
74
  Napi::Value v = array[i];
82
75
  Napi::Object obj = v.As<Napi::Object>();
83
76
 
@@ -88,13 +81,9 @@ NSArray<CBMutableCharacteristic *> *napiArrayToCBMutableCharacteristics(Napi::Ar
88
81
  }
89
82
 
90
83
  CBMutableCharacteristic *napiToCBMutableCharacteristic(Napi::Object obj) {
91
- // NSLog(@"napiToCBMutableCharacteristic");
92
-
93
84
  NSString *uuid = napiToUuidString(obj.Get("uuid").ToString());
94
- // NSLog(@"napiToCBMutableCharacteristic: cUUID:%@", uuid);
95
85
 
96
86
  NSData *value = obj.Get("value").IsBuffer() ? napiToData(obj.Get("value").As<Napi::Buffer<Byte>>()) : nil;
97
- // NSLog(@"napiToCBMutableCharacteristic: value:%@", value);
98
87
 
99
88
  auto properties = obj.Get("properties").As<Napi::Array>();
100
89
  auto secure = obj.Get("secure").As<Napi::Array>();
@@ -112,8 +101,6 @@ CBMutableCharacteristic *napiToCBMutableCharacteristic(Napi::Object obj) {
112
101
  }
113
102
 
114
103
  CBCharacteristicProperties napiToCBCharacteristicProperties(Napi::Array properties, Napi::Array secure) {
115
- // NSLog(@"napiToCBCharacteristicProperties");
116
-
117
104
  NSArray<NSString *> *p = napiToStringArray(properties);
118
105
  NSArray<NSString *> *s = napiToStringArray(secure);
119
106
 
@@ -151,8 +138,6 @@ CBCharacteristicProperties napiToCBCharacteristicProperties(Napi::Array properti
151
138
  }
152
139
 
153
140
  CBAttributePermissions napiToCBAttributePermissions(Napi::Array properties, Napi::Array secure) {
154
- // NSLog(@"napiToCBAttributePermissions");
155
-
156
141
  NSArray<NSString *> *p = napiToStringArray(properties);
157
142
  NSArray<NSString *> *s = napiToStringArray(secure);
158
143
 
@@ -186,11 +171,9 @@ CBAttributePermissions napiToCBAttributePermissions(Napi::Array properties, Napi
186
171
  }
187
172
 
188
173
  NSArray<CBDescriptor *> *napiArrayToCBDescriptors(Napi::Array array) {
189
- // NSLog(@"napiArrayToCBDescriptors");
190
-
191
174
  NSMutableArray *descriptors = [NSMutableArray array];
192
175
 
193
- for (size_t i = 0; i < array.Length(); i++) {
176
+ for (uint32_t i = 0; i < array.Length(); i++) {
194
177
  Napi::Value v = array[i];
195
178
  Napi::Object obj = v.As<Napi::Object>();
196
179
 
@@ -204,8 +187,6 @@ CBDescriptor *napiToCBDescriptor(Napi::Object obj) {
204
187
  NSString *uuid = napiToUuidString(obj.Get("uuid").ToString());
205
188
  NSString *value = napiToString(obj.Get("value").ToString());
206
189
 
207
- // NSLog(@"napiToCBDescriptor uuid:%@ value:%@", uuid, value);
208
-
209
190
  return [[CBMutableDescriptor alloc] initWithType:[CBUUID UUIDWithString:uuid]
210
191
  value:value];
211
192
  }
@@ -213,7 +194,7 @@ CBDescriptor *napiToCBDescriptor(Napi::Object obj) {
213
194
  NSArray<NSString *> *napiToStringArray(Napi::Array array) {
214
195
  NSMutableArray *ret = [NSMutableArray arrayWithCapacity:array.Length()];
215
196
 
216
- for (size_t i = 0; i < array.Length(); i++) {
197
+ for (uint32_t i = 0; i < array.Length(); i++) {
217
198
  Napi::Value v = array[i];
218
199
  Napi::String str = v.ToString();
219
200
 
@@ -224,11 +205,9 @@ NSArray<NSString *> *napiToStringArray(Napi::Array array) {
224
205
  }
225
206
 
226
207
  std::map<Napi::String, Napi::Object> napiArrayToUUIDEmitters(Napi::Array services) {
227
- // NSLog(@"napiArrayToUUIDEmitters");
228
-
229
208
  std::map<Napi::String, Napi::Object> map;
230
209
 
231
- for (size_t i = 0; i < services.Length(); i++) {
210
+ for (uint32_t i = 0; i < services.Length(); i++) {
232
211
  Napi::Value vS = services[i];
233
212
  Napi::Object objS = vS.As<Napi::Object>();
234
213
 
@@ -237,7 +216,7 @@ std::map<Napi::String, Napi::Object> napiArrayToUUIDEmitters(Napi::Array service
237
216
  map[uuidS] = objS;
238
217
 
239
218
  Napi::Array characteristics = objS.Get("characteristics").As<Napi::Array>();
240
- for (size_t j = 0; j < characteristics.Length(); j++) {
219
+ for (uint32_t j = 0; j < characteristics.Length(); j++) {
241
220
  Napi::Value vC = characteristics[j];
242
221
  Napi::Object objC = vC.As<Napi::Object>();
243
222
 
@@ -266,7 +245,7 @@ BOOL getBool(const Napi::Value& value, BOOL def) {
266
245
 
267
246
  NSArray* napiToCBUuidArray(Napi::Array array) {
268
247
  NSMutableArray* serviceUuids = [NSMutableArray arrayWithCapacity:array.Length()];
269
- for(size_t i = 0; i < array.Length(); i++) {
248
+ for(uint32_t i = 0; i < array.Length(); i++) {
270
249
  Napi::Value val = array[i];
271
250
  [serviceUuids addObject:napiToCBUuidString(val.As<Napi::String>())];
272
251
  }
@@ -2,9 +2,9 @@
2
2
 
3
3
  #include <string>
4
4
  #include <vector>
5
+
5
6
  #import <Foundation/Foundation.h>
6
7
  #import <CoreBluetooth/CoreBluetooth.h>
7
- #include "peripheral.h"
8
8
 
9
9
  #define IF(type, var, code) type var = code; if(var)
10
10
 
@@ -21,7 +21,7 @@ std::string StringFromCBPeripheralState(CBManagerState state);
21
21
 
22
22
  NSString* getNSUuid(CBPeripheral* peripheral);
23
23
  std::string getUuid(CBPeripheral* peripheral);
24
- std::string getAddress(std::string uuid, AddressType* addressType);
24
+ std::string convertToBlenoAddress(NSUUID* uuid);
25
25
  std::vector<std::string> getServices(NSArray<CBService*>* services);
26
26
  std::vector<std::pair<std::string, std::vector<std::string>>> getCharacteristics(NSArray<CBCharacteristic*>* characteristics);
27
27
  std::vector<std::string> getDescriptors(NSArray<CBDescriptor*>* descriptors);
@@ -7,28 +7,6 @@
7
7
  #include "objc_cpp.h"
8
8
 
9
9
  #if defined(MAC_OS_X_VERSION_10_13)
10
- //#pragma clang diagnostic push
11
- //#pragma clang diagnostic ignored "-Wunguarded-availability"
12
- //std::string stateToString(CBManagerState state)
13
- //{
14
- // switch(state) {
15
- // case CBManagerStatePoweredOff:
16
- // return "poweredOff";
17
- // case CBManagerStatePoweredOn:
18
- // return "poweredOn";
19
- // case CBManagerStateResetting:
20
- // return "resetting";
21
- // case CBManagerStateUnauthorized:
22
- // return "unauthorized";
23
- // case CBManagerStateUnknown:
24
- // return "unknown";
25
- // case CBManagerStateUnsupported:
26
- // return "unsupported";
27
- // }
28
- // return "unknown";
29
- //}
30
- //#pragma clang diagnostic pop
31
-
32
10
  // In the 10.13 SDK, CBPeripheral became a subclass of CBPeer, which defines
33
11
  // -[CBPeer identifier] as partially available. Pretend it still exists on
34
12
  // CBPeripheral. At runtime the implementation on CBPeer will be invoked.
@@ -82,21 +60,9 @@ std::string getUuid(CBPeripheral* peripheral) {
82
60
  return std::string([peripheral.identifier.UUIDString UTF8String]);
83
61
  }
84
62
 
85
- std::string getAddress(std::string uuid, AddressType* addressType) {
86
- NSString* deviceUuid = [[NSString alloc] initWithCString:uuid.c_str() encoding:NSASCIIStringEncoding];
87
- IF(NSDictionary*, plist, [NSDictionary dictionaryWithContentsOfFile:@"/Library/Preferences/com.apple.Bluetooth.plist"]) {
88
- IF(NSDictionary*, cache, [plist objectForKey:@"CoreBluetoothCache"]) {
89
- IF(NSDictionary*, entry, [cache objectForKey:deviceUuid]) {
90
- IF(NSNumber*, type, [entry objectForKey:@"DeviceAddressType"]) {
91
- *addressType = [type boolValue] ? RANDOM : PUBLIC;
92
- }
93
- IF(NSString*, address, [entry objectForKey:@"DeviceAddress"]) {
94
- return [address UTF8String];
95
- }
96
- }
97
- }
98
- }
99
- return "";
63
+ std::string convertToBlenoAddress(NSUUID* uuid) {
64
+ NSString* addressString = [[uuid.UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString];
65
+ return std::string([addressString UTF8String]);
100
66
  }
101
67
 
102
68
  std::vector<std::string> getServices(NSArray<CBService*>* services) {
@@ -1,19 +1,40 @@
1
1
  const os = require('os');
2
+ const platform = os.platform();
2
3
 
3
- module.exports = function (options = {}) {
4
- const platform = os.platform();
4
+ const Bleno = require('./bleno');
5
5
 
6
+ function loadBindings (bindingType = null, options = {}) {
7
+ switch (bindingType) {
8
+ case 'hci':
9
+ return new (require('./hci-socket/bindings'))(options);
10
+ case 'mac':
11
+ return new (require('./mac/bindings'))(options);
12
+ default:
13
+ throw new Error('Unsupported binding type: ' + bindingType);
14
+ }
15
+ }
16
+
17
+ // Default binding selection logic
18
+ function getDefaultBindings (options = {}) {
6
19
  if (
7
20
  platform === 'linux' ||
8
- platform === 'freebsd' ||
21
+ platform === 'freebsd' ||
9
22
  platform === 'win32' ||
10
23
  platform === 'android' ||
11
24
  process.env.BLUETOOTH_HCI_SOCKET_UART_PORT ||
12
- process.env.BLUETOOTH_HCI_SOCKET_FORCE_UART) {
13
- return new (require('./hci-socket/bindings'))(options);
25
+ process.env.BLUETOOTH_HCI_SOCKET_FORCE_UART
26
+ ) {
27
+ return loadBindings('hci', options);
14
28
  } else if (platform === 'darwin') {
15
- return new (require('./mac/bindings'))(options);
29
+ return loadBindings('mac', options);
16
30
  } else {
17
31
  throw new Error('Unsupported platform ' + platform);
18
32
  }
33
+ }
34
+
35
+ module.exports = function (bindingType = 'default', options = {}) {
36
+ if (bindingType === 'default') {
37
+ return new Bleno(getDefaultBindings(options));
38
+ }
39
+ return new Bleno(loadBindings(bindingType, options));
19
40
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoprocent/bleno",
3
- "version": "0.8.6",
3
+ "version": "0.10.0",
4
4
  "description": "A Node.js module for implementing BLE (Bluetooth Low Energy) peripherals",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
@@ -49,7 +49,7 @@
49
49
  "@semantic-release/changelog": "^6.0.3",
50
50
  "@semantic-release/exec": "^6.0.3",
51
51
  "@semantic-release/git": "^10.0.1",
52
- "@semantic-release/github": "^10.1.1",
52
+ "@semantic-release/github": "^11.0.1",
53
53
  "jshint": "^2.13.6",
54
54
  "cross-env": "^7.0.3",
55
55
  "eslint": "^8.57.1",
@@ -60,19 +60,16 @@
60
60
  "mocha": "^10.7.0",
61
61
  "nyc": "^17.0.0",
62
62
  "prebuildify": "^6.0.1",
63
- "prebuildify-cross": "thegecko/prebuildify-cross#fix-docker",
63
+ "prebuildify-cross": "^5.1.1",
64
64
  "should": "^13.2.3"
65
65
  },
66
66
  "dependencies": {
67
- "debug": "^4.3.7",
68
- "napi-thread-safe-callback": "^0.0.6",
69
- "node-addon-api": "^8.1.0",
70
- "node-gyp-build": "^4.8.1"
67
+ "debug": "^4.4.0",
68
+ "node-addon-api": "^8.3.1",
69
+ "node-gyp-build": "^4.8.4"
71
70
  },
72
71
  "optionalDependencies": {
73
- "@stoprocent/bluetooth-hci-socket": "^1.5.2",
74
- "bplist-parser": "0.3.2",
75
- "xpc-connect": "^3.0.0"
72
+ "@stoprocent/bluetooth-hci-socket": "^2.1.0"
76
73
  },
77
74
  "publishConfig": {
78
75
  "access": "public"
@@ -3,6 +3,7 @@
3
3
  const should = require('should');
4
4
 
5
5
  const Characteristic = require('../lib/characteristic');
6
+ Characteristic.RESULT_SUCCESS = 0x99; // override default value as 0x00 isnt the best value for this test
6
7
 
7
8
  describe('Characteristic', function () {
8
9
  const mockUuid = 'mockuuid';
@@ -141,34 +142,180 @@ describe('Characteristic', function () {
141
142
 
142
143
  it('should handle read request', function (done) {
143
144
  const characteristic = new Characteristic({});
145
+ const handle = 0;
144
146
 
145
- characteristic.emit('readRequest', 0, function (result, data) {
146
- result.should.equal(0x0e);
147
+ characteristic.emit('readRequest', handle, 0, function (result, data) {
148
+ result.should.equal(Characteristic.RESULT_UNLIKELY_ERROR);
147
149
  should(data).equal(null);
148
-
149
150
  done();
150
151
  });
151
152
  });
152
153
 
153
154
  it('should handle write request', function (done) {
154
155
  const characteristic = new Characteristic({});
156
+ const handle = 0;
155
157
 
156
- characteristic.emit('writeRequest', Buffer.alloc(0), 0, false, function (result) {
157
- result.should.equal(0x0e);
158
-
158
+ characteristic.emit('writeRequest', handle, Buffer.alloc(0), 0, false, function (result) {
159
+ result.should.equal(Characteristic.RESULT_UNLIKELY_ERROR);
159
160
  done();
160
161
  });
161
162
  });
162
163
 
163
164
  it('should handle unsubscribe', function () {
164
165
  const characteristic = new Characteristic({});
166
+ const handle = 0;
167
+
168
+ characteristic._maxValueSizes.set(handle, mockMaxValueSize);
169
+ characteristic._updateValueCallbacks.set(handle, mockUpdateValueCallback);
170
+
171
+ characteristic.emit('unsubscribe', handle);
172
+
173
+ should(characteristic.getMaxValueSize(handle)).equal(undefined);
174
+ should(characteristic._maxValueSizes.has(handle)).equal(false);
175
+ should(characteristic._updateValueCallbacks.has(handle)).equal(false);
176
+ });
177
+
178
+ describe('Handle-based updates', function () {
179
+ it('should handle subscribe with different handles', function () {
180
+ const characteristic = new Characteristic({});
181
+ const handle1 = 1;
182
+ const handle2 = 2;
183
+ const maxValueSize1 = 20;
184
+ const maxValueSize2 = 30;
185
+ let updateCallback1Called = false;
186
+ let updateCallback2Called = false;
187
+
188
+ const updateCallback1 = (data) => {
189
+ updateCallback1Called = true;
190
+ should(data).equal('test1');
191
+ };
192
+
193
+ const updateCallback2 = (data) => {
194
+ updateCallback2Called = true;
195
+ should(data).equal('test2');
196
+ };
197
+
198
+ // Subscribe first client
199
+ characteristic.emit('subscribe', handle1, maxValueSize1, updateCallback1);
200
+ should(characteristic.getMaxValueSize(handle1)).equal(maxValueSize1);
201
+ should(characteristic._updateValueCallbacks.has(handle1)).equal(true);
202
+ should(characteristic._updateValueCallbacks.get(handle1)).equal(updateCallback1);
203
+
204
+ // Subscribe second client
205
+ characteristic.emit('subscribe', handle2, maxValueSize2, updateCallback2);
206
+ should(characteristic.getMaxValueSize(handle2)).equal(maxValueSize2);
207
+ should(characteristic._updateValueCallbacks.has(handle2)).equal(true);
208
+ should(characteristic._updateValueCallbacks.get(handle2)).equal(updateCallback2);
209
+
210
+ // Test updates for each handle
211
+ characteristic._updateValueCallbacks.get(handle1)('test1');
212
+ should(updateCallback1Called).equal(true);
213
+
214
+ characteristic._updateValueCallbacks.get(handle2)('test2');
215
+ should(updateCallback2Called).equal(true);
216
+ });
217
+
218
+ it('should handle unsubscribe for specific handle', function () {
219
+ const characteristic = new Characteristic({});
220
+ const handle1 = 1;
221
+ const handle2 = 2;
222
+ const maxValueSize = 20;
223
+
224
+ // Subscribe both clients
225
+ characteristic.emit('subscribe', handle1, maxValueSize, () => {});
226
+ characteristic.emit('subscribe', handle2, maxValueSize, () => {});
227
+
228
+ // Unsubscribe first client
229
+ characteristic.emit('unsubscribe', handle1);
230
+
231
+ // First client should be removed
232
+ should(characteristic.getMaxValueSize(handle1)).equal(undefined);
233
+ should(characteristic._updateValueCallbacks.has(handle1)).equal(false);
234
+
235
+ // Second client should still be subscribed
236
+ should(characteristic.getMaxValueSize(handle2)).equal(maxValueSize);
237
+ should(characteristic._updateValueCallbacks.has(handle2)).equal(true);
238
+
239
+ // Unsubscribe second client
240
+ characteristic.emit('unsubscribe', handle2);
241
+
242
+ // Both clients should be removed
243
+ should(characteristic.getMaxValueSize(handle1)).equal(undefined);
244
+ should(characteristic.getMaxValueSize(handle2)).equal(undefined);
245
+ should(characteristic._updateValueCallbacks.has(handle1)).equal(false);
246
+ should(characteristic._updateValueCallbacks.has(handle2)).equal(false);
247
+ });
165
248
 
166
- characteristic.maxValueSize = mockMaxValueSize;
167
- characteristic.updateValueCallback = mockUpdateValueCallback;
249
+ it('should handle read requests for different handles', function (done) {
250
+ const handle1 = 1;
251
+ const handle2 = 2;
252
+
253
+ let readCallback1Called = false;
254
+ let readCallback2Called = false;
255
+
256
+ const characteristic = new Characteristic({
257
+ onReadRequest: (handle, offset, callback) => {
258
+ if (handle === handle1) {
259
+ readCallback1Called = true;
260
+ callback(Characteristic.RESULT_SUCCESS, Buffer.from('test1'));
261
+ } else if (handle === handle2) {
262
+ readCallback2Called = true;
263
+ callback(Characteristic.RESULT_SUCCESS, Buffer.from('test2'));
264
+ }
265
+ }
266
+ });
168
267
 
169
- characteristic.emit('unsubscribe');
268
+ // Test read request for first handle
269
+ characteristic.emit('readRequest', handle1, 0, (result, data) => {
270
+ should(result).equal(Characteristic.RESULT_SUCCESS);
271
+ should(data.toString()).equal('test1');
272
+ should(readCallback1Called).equal(true);
273
+ });
170
274
 
171
- should(characteristic.maxValueSize).equal(null);
172
- should(characteristic.updateValueCallback).equal(null);
275
+ // Test read request for second handle
276
+ characteristic.emit('readRequest', handle2, 0, (result, data) => {
277
+ should(result).equal(Characteristic.RESULT_SUCCESS);
278
+ should(data.toString()).equal('test2');
279
+ should(readCallback2Called).equal(true);
280
+ done();
281
+ });
282
+ });
283
+
284
+ it('should handle write requests for different handles', function (done) {
285
+ const handle1 = 1;
286
+ const handle2 = 2;
287
+
288
+ const data1 = Buffer.from('test1');
289
+ const data2 = Buffer.from('test2');
290
+
291
+ let writeCallback1Called = false;
292
+ let writeCallback2Called = false;
293
+
294
+ const characteristic = new Characteristic({
295
+ onWriteRequest: (handle, data, offset, withoutResponse, callback) => {
296
+ if (handle === handle1) {
297
+ writeCallback1Called = true;
298
+ should(data).equal(data1);
299
+ callback(Characteristic.RESULT_SUCCESS);
300
+ } else if (handle === handle2) {
301
+ writeCallback2Called = true;
302
+ should(data).equal(data2);
303
+ callback(Characteristic.RESULT_SUCCESS);
304
+ }
305
+ }
306
+ });
307
+ // Test write request for first handle
308
+ characteristic.emit('writeRequest', handle1, data1, 0, false, (result) => {
309
+ should(result).equal(Characteristic.RESULT_SUCCESS);
310
+ should(writeCallback1Called).equal(true);
311
+ });
312
+
313
+ // Test write request for second handle
314
+ characteristic.emit('writeRequest', handle2, data2, 0, false, (result) => {
315
+ should(result).equal(Characteristic.RESULT_SUCCESS);
316
+ should(writeCallback2Called).equal(true);
317
+ done();
318
+ });
319
+ });
173
320
  });
174
321
  });
@@ -1,14 +0,0 @@
1
- # BLE Battery Service
2
-
3
- This example provides a BLE battery service (0x180F) for your Mac.
4
-
5
- Install dependencies
6
-
7
- npm install
8
-
9
- Run the example
10
-
11
- node main.js
12
-
13
-
14
- NOTE: This example no longer works on OSX starting in 10.10 (Yosemite). Apple has apparently blacklisted the battery uuid.