@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
package/index.d.ts CHANGED
@@ -1,155 +1,200 @@
1
- // Type definitions for bleno 0.4
2
- // Project: https://github.com/sandeepmistry/bleno
3
- // Definitions by: Manuel Francisco Naranjo <naranjo.manuel@gmail.com>
4
- // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
-
6
1
  /// <reference types="node" />
7
-
8
- type State = 'poweredOn' | 'poweredOff' | 'unauthorized' | 'unsupported' | 'unknown' | 'resetting';
9
-
10
- type Property = 'read' | 'write' | 'indicate' | 'notify' | 'writeWithoutResponse';
11
-
12
- interface CharacteristicOptions {
13
- uuid: string;
14
- properties?: ReadonlyArray<Property> | null;
15
- secure?: ReadonlyArray<Property> | null;
16
- value?: Buffer | null;
17
- descriptors?: ReadonlyArray<Descriptor> | null;
18
- onIndicate?: (() => void) | null;
19
- onNotify?: (() => void) | null;
20
- onReadRequest?: ((
21
- offset: number,
22
- callback: (result: number, data?: Buffer) => void
23
- ) => void) | null;
24
- onSubscribe?: ((maxValueSize: number, updateValueCallback: (data: Buffer) => void) => void) | null;
25
- onUnsubscribe?: (() => void) | null;
26
- onWriteRequest?: ((
27
- data: Buffer,
28
- offset: number,
29
- withoutResponse: boolean,
30
- callback: (result: number) => void
31
- ) => void) | null;
2
+ declare module '@stoprocent/bluetooth-hci-socket' {
3
+ type DriverType = any; // Fallback type
4
+ interface BindParams {
5
+ [key: string]: any;
6
+ }
32
7
  }
33
8
 
34
- declare class Characteristic {
35
- uuid: string;
36
- properties: ReadonlyArray<Property>;
37
- secure: ReadonlyArray<Property>;
38
- value: Buffer | null;
39
- descriptors: ReadonlyArray<Descriptor>;
40
-
41
- constructor(options: CharacteristicOptions);
42
-
43
- onIndicate(): void;
44
-
45
- onNotify(): void;
46
-
47
- onReadRequest(offset: number, callback: (result: number, data?: Buffer) => void): void;
48
-
49
- onSubscribe(maxValueSize: number, updateValueCallback: (data: Buffer) => void): void;
50
-
51
- onUnsubscribe(): void;
52
-
53
- onWriteRequest(data: Buffer, offset: number, withoutResponse: boolean, callback: (result: number) => void): void;
54
-
55
- toString(): string;
9
+ declare module '@stoprocent/bleno' {
10
+ import { EventEmitter } from 'events';
11
+
12
+ export type State = 'poweredOn' | 'poweredOff' | 'unauthorized' | 'unsupported' | 'unknown' | 'resetting';
13
+
14
+ export type Property = 'read' | 'write' | 'indicate' | 'notify' | 'writeWithoutResponse';
15
+
16
+ export type ConnectionHandle = number | string;
56
17
 
57
- readonly RESULT_ATTR_NOT_LONG: number;
18
+ // Common callback types
19
+ export type ReadRequestCallback = (result: number, data?: Buffer) => void;
20
+ export type UpdateValueCallback = (data?: Buffer) => void;
21
+ export type WriteRequestCallback = (result: number) => void;
58
22
 
59
- readonly RESULT_INVALID_ATTRIBUTE_LENGTH: number;
23
+ // Common function types
24
+ export type OnReadRequestFn = (handle: ConnectionHandle, offset: number, callback: ReadRequestCallback) => void;
25
+ export type OnSubscribeFn = (handle: ConnectionHandle, maxValueSize: number, updateValueCallback: UpdateValueCallback) => void;
26
+ export type OnUnsubscribeFn = (handle: ConnectionHandle) => void;
27
+ export type OnWriteRequestFn = (handle: ConnectionHandle, data: Buffer, offset: number, withoutResponse: boolean, callback: WriteRequestCallback) => void;
28
+ export type OnIndicateFn = (handle: ConnectionHandle) => void;
29
+ export type OnNotifyFn = (handle: ConnectionHandle) => void;
60
30
 
61
- readonly RESULT_INVALID_OFFSET: number;
31
+ export interface CharacteristicOptions {
32
+ uuid: string;
33
+ properties?: ReadonlyArray<Property> | null;
34
+ secure?: ReadonlyArray<Property> | null;
35
+ value?: Buffer | null;
36
+ descriptors?: ReadonlyArray<Descriptor> | null;
37
+ onIndicate?: OnIndicateFn | null;
38
+ onNotify?: OnNotifyFn | null;
39
+ onReadRequest?: OnReadRequestFn | null;
40
+ onSubscribe?: OnSubscribeFn | null;
41
+ onUnsubscribe?: OnUnsubscribeFn | null;
42
+ onWriteRequest?: OnWriteRequestFn | null;
43
+ }
62
44
 
63
- readonly RESULT_SUCCESS: number;
45
+ export class Characteristic {
46
+ uuid: string;
47
+ properties: ReadonlyArray<Property>;
48
+ secure: ReadonlyArray<Property>;
49
+ value: Buffer | null;
50
+ descriptors: ReadonlyArray<Descriptor>;
64
51
 
65
- readonly RESULT_UNLIKELY_ERROR: number;
52
+ constructor(options: CharacteristicOptions);
66
53
 
67
- static readonly RESULT_ATTR_NOT_LONG: number;
54
+ onIndicate: OnIndicateFn;
55
+ onNotify: OnNotifyFn;
56
+ onReadRequest: OnReadRequestFn;
57
+ onSubscribe: OnSubscribeFn;
58
+ onUnsubscribe: OnUnsubscribeFn;
59
+ onWriteRequest: OnWriteRequestFn;
68
60
 
69
- static readonly RESULT_INVALID_ATTRIBUTE_LENGTH: number;
61
+ toString(): string;
70
62
 
71
- static readonly RESULT_INVALID_OFFSET: number;
63
+ readonly RESULT_ATTR_NOT_LONG: number;
64
+ readonly RESULT_INVALID_ATTRIBUTE_LENGTH: number;
65
+ readonly RESULT_INVALID_OFFSET: number;
66
+ readonly RESULT_SUCCESS: number;
67
+ readonly RESULT_UNLIKELY_ERROR: number;
68
+
69
+ static readonly RESULT_ATTR_NOT_LONG: number;
70
+ static readonly RESULT_INVALID_ATTRIBUTE_LENGTH: number;
71
+ static readonly RESULT_INVALID_OFFSET: number;
72
+ static readonly RESULT_SUCCESS: number;
73
+ static readonly RESULT_UNLIKELY_ERROR: number;
74
+ }
75
+
76
+ export interface DescriptorOptions {
77
+ uuid: string;
78
+ value?: Buffer | string | null;
79
+ }
72
80
 
73
- static readonly RESULT_SUCCESS: number;
81
+ export class Descriptor {
82
+ uuid: string;
83
+ value: Buffer;
84
+
85
+ constructor(options: DescriptorOptions);
74
86
 
75
- static readonly RESULT_UNLIKELY_ERROR: number;
76
- }
77
-
78
- interface DescriptorOptions {
79
- uuid: string;
80
- value?: Buffer | string | null;
81
- }
82
-
83
- declare class Descriptor {
84
- uuid: string;
85
- value: Buffer;
86
-
87
- constructor(options: DescriptorOptions);
87
+ toString(): string;
88
+ }
88
89
 
89
- toString(): string;
90
- }
90
+ export interface PrimaryServiceOptions {
91
+ uuid: string;
92
+ characteristics?: ReadonlyArray<Characteristic> | null;
93
+ }
91
94
 
92
- interface PrimaryServiceOptions {
93
- uuid: string;
94
- characteristics?: ReadonlyArray<Characteristic> | null;
95
- }
95
+ export class PrimaryService {
96
+ uuid: string;
97
+ characteristics: ReadonlyArray<Characteristic>;
96
98
 
97
- declare class PrimaryService {
98
- uuid: string;
99
- characteristics: ReadonlyArray<Characteristic>;
99
+ constructor(options: PrimaryServiceOptions);
100
100
 
101
- constructor(options: PrimaryServiceOptions);
101
+ toString(): string;
102
+ }
102
103
 
103
- toString(): string;
104
- }
104
+ export class Bleno extends EventEmitter {
105
+ readonly Characteristic: typeof Characteristic;
106
+ readonly Descriptor: typeof Descriptor;
107
+ readonly PrimaryService: typeof PrimaryService;
105
108
 
106
- export interface Bleno extends NodeJS.EventEmitter {
107
- readonly Characteristic: typeof Characteristic;
108
- readonly Descriptor: typeof Descriptor;
109
- readonly PrimaryService: typeof PrimaryService;
109
+ readonly address: string;
110
110
 
111
- readonly address: string;
111
+ readonly mtu: number;
112
+
113
+ readonly platform: string;
114
+
115
+ readonly rssi: number;
116
+
117
+ readonly state: State;
118
+
119
+ disconnect(): void;
120
+
121
+ stop(): void;
122
+
123
+ setAddress(address: string): void;
124
+
125
+ setServices(services: ReadonlyArray<PrimaryService>, callback?: (arg: Error | undefined | null) => void): void;
126
+
127
+ startAdvertising(name: string, serviceUuids?: ReadonlyArray<string>, callback?: (arg: Error | undefined | null) => void): void;
128
+
129
+ startAdvertisingIBeacon(uuid: string, major: number, minor: number, measuredPower: number, callback?: (arg: Error | undefined | null) => void): void;
112
130
 
113
- readonly mtu: number;
131
+ startAdvertisingWithEIRData(advertisementData: Buffer, callback?: (arg: Error | undefined | null) => void): void;
132
+ startAdvertisingWithEIRData(advertisementData: Buffer, scanData: Buffer, callback?: (arg: Error | undefined | null) => void): void;
133
+
134
+ stopAdvertising(callback?: () => void): void;
114
135
 
115
- readonly platform: string;
136
+ updateRssi(callback?: (err: null, rssi: number) => void): void;
116
137
 
117
- readonly rssi: number;
138
+ // Async methods
139
+ waitForPoweredOnAsync(timeout?: number): Promise<void>;
118
140
 
119
- readonly state: State;
141
+ setAddressAsync(address: string): Promise<void>;
142
+
143
+ setServicesAsync(services: ReadonlyArray<PrimaryService>): Promise<void>;
144
+
145
+ startAdvertisingAsync(name: string, serviceUuids?: ReadonlyArray<string>): Promise<void>;
146
+
147
+ startAdvertisingIBeaconAsync(
148
+ uuid: string,
149
+ major: number,
150
+ minor: number,
151
+ measuredPower: number
152
+ ): Promise<void>;
153
+
154
+ startAdvertisingWithEIRDataAsync(advertisementData: Buffer): Promise<void>;
155
+ startAdvertisingWithEIRDataAsync(advertisementData: Buffer, scanData: Buffer): Promise<void>;
156
+
157
+ stopAdvertisingAsync(): Promise<void>;
158
+
159
+ updateRssiAsync(): Promise<number>;
120
160
 
121
- disconnect(): void;
161
+ on(event: 'stateChange', cb: (state: State) => void): this;
162
+ on(event: 'platform', cb: (platform: NodeJS.Platform) => void): this;
163
+ on(event: 'addressChange', cb: (address: string) => void): this;
164
+ on(event: 'accept', cb: (address: string, handle: ConnectionHandle) => void): this;
165
+ on(event: 'mtuChange', cb: (mtu: number) => void): this;
166
+ on(event: 'disconnect', cb: (address: string, handle: ConnectionHandle) => void): this;
167
+ on(event: 'advertisingStart', cb: (err?: Error | null) => void): this;
168
+ on(event: 'advertisingStartError', cb: (err: Error) => void): this;
169
+ on(event: 'advertisingStop', cb: () => void): this;
170
+ on(event: 'servicesSet', cb: (err?: Error | null) => void): this;
171
+ on(event: 'servicesSetError', cb: (err: Error) => void): this;
172
+ on(event: 'rssiUpdate', cb: (rssi: number) => void): this;
173
+ }
122
174
 
123
- stop(): void;
175
+ export type BindingType = 'default' | 'hci' | 'mac';
124
176
 
125
- setAddress(address: string): void;
177
+ export interface BaseBindingsOptions {
126
178
 
127
- setServices(services: ReadonlyArray<PrimaryService>, callback?: (arg: Error | undefined | null) => void): void;
179
+ }
128
180
 
129
- startAdvertising(name: string, serviceUuids?: ReadonlyArray<string>, callback?: (arg: Error | undefined | null) => void): void;
181
+ export interface HciBindingsOptions extends BaseBindingsOptions {
182
+ hciDriver?: import('@stoprocent/bluetooth-hci-socket').DriverType;
183
+ bindParams?: import('@stoprocent/bluetooth-hci-socket').BindParams;
184
+ }
130
185
 
131
- startAdvertisingIBeacon(uuid: string, major: number, minor: number, measuredPower: number, callback?: (arg: Error | undefined | null) => void): void;
186
+ export interface MacBindingsOptions extends BaseBindingsOptions {
132
187
 
133
- startAdvertisingWithEIRData(advertisementData: Buffer, callback?: (arg: Error | undefined | null) => void): void;
134
- startAdvertisingWithEIRData(advertisementData: Buffer, scanData: Buffer, callback?: (arg: Error | undefined | null) => void): void;
188
+ }
135
189
 
136
- stopAdvertising(callback?: () => void): void;
190
+ export type WithBindingsOptions = HciBindingsOptions | MacBindingsOptions;
137
191
 
138
- updateRssi(callback?: (err: null, rssi: number) => void): void;
139
-
140
- on(event: 'stateChange', cb: (state: State) => void): this;
141
- on(event: 'platform', cb: (platform: NodeJS.Platform) => void): this;
142
- on(event: 'addressChange', cb: (address: string) => void): this;
143
- on(event: 'accept', cb: (address: string) => void): this;
144
- on(event: 'mtuChange', cb: (mtu: number) => void): this;
145
- on(event: 'disconnect', cb: (clientAddress: string) => void): this;
146
- on(event: 'advertisingStart', cb: (err?: Error | null) => void): this;
147
- on(event: 'advertisingStartError', cb: (err: Error) => void): this;
148
- on(event: 'advertisingStop', cb: () => void): this;
149
- on(event: 'servicesSet', cb: (err?: Error | null) => void): this;
150
- on(event: 'servicesSetError', cb: (err: Error) => void): this;
151
- on(event: 'rssiUpdate', cb: (rssi: number) => void): this;
152
- }
192
+ export function withBindings(
193
+ bindingType?: BindingType,
194
+ options?: WithBindingsOptions
195
+ ): Bleno;
153
196
 
154
- declare const bleno: Bleno;
155
- export = bleno;
197
+ // Define a default export
198
+ const BlenoDefault: Bleno;
199
+ export default BlenoDefault;
200
+ }
package/index.js CHANGED
@@ -1 +1,5 @@
1
- module.exports = require('./with-custom-binding')();
1
+ const withBindings = require('./lib/resolve-bindings');
2
+
3
+ // Expose both the default instance and the custom binding function
4
+ module.exports = withBindings();
5
+ module.exports.withBindings = withBindings;
package/lib/bleno.js CHANGED
@@ -59,22 +59,43 @@ class Bleno extends EventEmitter {
59
59
  this.address = address;
60
60
  }
61
61
 
62
- onAccept (clientAddress) {
63
- debug('accept ' + clientAddress);
64
- this.emit('accept', clientAddress);
62
+ onAccept (clientAddress, handle) {
63
+ debug('accept ' + clientAddress + ' ' + handle);
64
+ this.emit('accept', clientAddress, handle);
65
65
  }
66
66
 
67
67
  onMtuChange (mtu) {
68
68
  debug('mtu ' + mtu);
69
-
70
69
  this.mtu = mtu;
71
-
72
70
  this.emit('mtuChange', mtu);
73
71
  }
74
72
 
75
- onDisconnect (clientAddress) {
76
- debug('disconnect ' + clientAddress);
77
- this.emit('disconnect', clientAddress);
73
+ onDisconnect (clientAddress, handle) {
74
+ debug('disconnect ' + clientAddress + ' ' + handle);
75
+ this.emit('disconnect', clientAddress, handle);
76
+ }
77
+
78
+ async waitForPoweredOnAsync (timeout = 10000) {
79
+ if (this.state === 'poweredOn') {
80
+ return;
81
+ }
82
+
83
+ return new Promise((resolve, reject) => {
84
+ const timeoutId = setTimeout(() => {
85
+ this.removeListener('stateChange', stateChangeHandler);
86
+ reject(new Error(`Timeout waiting for poweredOn state. Current state: ${this.state}`));
87
+ }, timeout);
88
+
89
+ const stateChangeHandler = (state) => {
90
+ if (state === 'poweredOn') {
91
+ clearTimeout(timeoutId);
92
+ this.removeListener('stateChange', stateChangeHandler);
93
+ resolve();
94
+ }
95
+ };
96
+
97
+ this.on('stateChange', stateChangeHandler);
98
+ });
78
99
  }
79
100
 
80
101
  setAddress (address) {
@@ -85,6 +106,27 @@ class Bleno extends EventEmitter {
85
106
  }
86
107
  }
87
108
 
109
+ setAddressAsync (address) {
110
+ if (this._bindings.setAddress === undefined) {
111
+ return Promise.resolve();
112
+ }
113
+ return new Promise((resolve, reject) => {
114
+ const timeout = setTimeout(() => {
115
+ reject(new Error('Timeout waiting for address change'));
116
+ }, 2000);
117
+ this._bindings.once('addressChange', (address) => {
118
+ if (address === address) {
119
+ clearTimeout(timeout);
120
+ resolve();
121
+ }
122
+ else {
123
+ reject(new Error('Address change failed'));
124
+ }
125
+ });
126
+ this.setAddress(address);
127
+ });
128
+ }
129
+
88
130
  startAdvertising (name, serviceUuids, callback) {
89
131
  if (this.state !== 'poweredOn') {
90
132
  const error = new Error('Could not start advertising, state is ' + this.state + ' (not poweredOn)');
@@ -111,6 +153,15 @@ class Bleno extends EventEmitter {
111
153
  }
112
154
  }
113
155
 
156
+ startAdvertisingAsync (name, serviceUuids) {
157
+ return new Promise((resolve, reject) => {
158
+ this.startAdvertising(name, serviceUuids, (error) => {
159
+ if (error) reject(error);
160
+ else resolve();
161
+ });
162
+ });
163
+ }
164
+
114
165
  startAdvertisingIBeacon (uuid, major, minor, measuredPower, callback) {
115
166
  if (this.state !== 'poweredOn') {
116
167
  const error = new Error('Could not start advertising, state is ' + this.state + ' (not poweredOn)');
@@ -144,14 +195,18 @@ class Bleno extends EventEmitter {
144
195
  }
145
196
  }
146
197
 
198
+ startAdvertisingIBeaconAsync (uuid, major, minor, measuredPower) {
199
+ return new Promise((resolve, reject) => {
200
+ this.startAdvertisingIBeacon(uuid, major, minor, measuredPower, (error) => {
201
+ if (error) reject(error);
202
+ else resolve();
203
+ });
204
+ });
205
+ }
206
+
147
207
  onAdvertisingStart (error) {
148
208
  debug('advertisingStart: ' + error);
149
-
150
- if (error) {
151
- this.emit('advertisingStartError', error);
152
- } else {
153
- this.emit('advertisingStart', error);
154
- }
209
+ this.emit('advertisingStart', error);
155
210
  }
156
211
 
157
212
  startAdvertisingWithEIRData (advertisementData, scanData, callback) {
@@ -177,6 +232,15 @@ class Bleno extends EventEmitter {
177
232
  }
178
233
  }
179
234
 
235
+ startAdvertisingWithEIRDataAsync (advertisementData, scanData) {
236
+ return new Promise((resolve, reject) => {
237
+ this.startAdvertisingWithEIRData(advertisementData, scanData, (error) => {
238
+ if (error) reject(error);
239
+ else resolve();
240
+ });
241
+ });
242
+ }
243
+
180
244
  stopAdvertising (callback) {
181
245
  if (typeof callback === 'function') {
182
246
  this.once('advertisingStop', callback);
@@ -184,6 +248,15 @@ class Bleno extends EventEmitter {
184
248
  this._bindings.stopAdvertising();
185
249
  }
186
250
 
251
+ stopAdvertisingAsync () {
252
+ return new Promise((resolve, reject) => {
253
+ this.stopAdvertising((error) => {
254
+ if (error) reject(error);
255
+ else resolve();
256
+ });
257
+ });
258
+ }
259
+
187
260
  onAdvertisingStop () {
188
261
  debug('advertisingStop');
189
262
  this.emit('advertisingStop');
@@ -196,6 +269,15 @@ class Bleno extends EventEmitter {
196
269
  this._bindings.setServices(services);
197
270
  }
198
271
 
272
+ setServicesAsync (services) {
273
+ return new Promise((resolve, reject) => {
274
+ this.setServices(services, (error) => {
275
+ if (error) reject(error);
276
+ else resolve();
277
+ });
278
+ });
279
+ }
280
+
199
281
  onServicesSet (error) {
200
282
  debug('servicesSet');
201
283
 
@@ -206,9 +288,9 @@ class Bleno extends EventEmitter {
206
288
  }
207
289
  }
208
290
 
209
- disconnect () {
210
- debug('disconnect');
211
- this._bindings.disconnect();
291
+ disconnect (handle = null) {
292
+ debug('disconnect ' + (handle ? handle : 'all'));
293
+ this._bindings.disconnect(handle);
212
294
  }
213
295
 
214
296
  stop () {
@@ -224,6 +306,15 @@ class Bleno extends EventEmitter {
224
306
  this._bindings.updateRssi();
225
307
  }
226
308
 
309
+ updateRssiAsync () {
310
+ return new Promise((resolve, reject) => {
311
+ this.updateRssi((error) => {
312
+ if (error) reject(error);
313
+ else resolve();
314
+ });
315
+ });
316
+ }
317
+
227
318
  onRssiUpdate (rssi) {
228
319
  this.emit('rssiUpdate', rssi);
229
320
  }
@@ -45,6 +45,9 @@ class Characteristic extends EventEmitter {
45
45
  this.on('unsubscribe', this.onUnsubscribe.bind(this));
46
46
  this.on('notify', this.onNotify.bind(this));
47
47
  this.on('indicate', this.onIndicate.bind(this));
48
+
49
+ this._maxValueSizes = new Map();
50
+ this._updateValueCallbacks = new Map();
48
51
  }
49
52
 
50
53
  toString () {
@@ -57,28 +60,42 @@ class Characteristic extends EventEmitter {
57
60
  });
58
61
  }
59
62
 
60
- onReadRequest (offset, callback) {
63
+ getMaxValueSize (connection) {
64
+ return this._maxValueSizes.get(connection);
65
+ }
66
+
67
+ onReadRequest (connection, offset, callback) {
61
68
  callback(this.RESULT_UNLIKELY_ERROR, null);
62
69
  }
63
70
 
64
- onWriteRequest (data, offset, withoutResponse, callback) {
71
+ onWriteRequest (connection, data, offset, withoutResponse, callback) {
65
72
  callback(this.RESULT_UNLIKELY_ERROR);
66
73
  }
67
74
 
68
- onSubscribe (maxValueSize, updateValueCallback) {
69
- this.maxValueSize = maxValueSize;
70
- this.updateValueCallback = updateValueCallback;
75
+ onSubscribe (connection, maxValueSize, updateValueCallback) {
76
+ this._maxValueSizes.set(connection, maxValueSize);
77
+ this._updateValueCallbacks.set(connection, updateValueCallback);
71
78
  }
72
79
 
73
- onUnsubscribe () {
74
- this.maxValueSize = null;
75
- this.updateValueCallback = null;
80
+ onUnsubscribe (connection) {
81
+ this._maxValueSizes.delete(connection);
82
+ this._updateValueCallbacks.delete(connection);
76
83
  }
77
84
 
78
- onNotify () {
85
+ onNotify (connection) {
79
86
  }
80
87
 
81
- onIndicate () {
88
+ onIndicate (connection) {
89
+ }
90
+
91
+ notify (data, connection = null) {
92
+ if (connection && this._updateValueCallbacks.has(connection)) {
93
+ this._updateValueCallbacks.get(connection)(data);
94
+ } else if (!connection) {
95
+ for (const callback of this._updateValueCallbacks.values()) {
96
+ callback(data);
97
+ }
98
+ }
82
99
  }
83
100
  }
84
101
 
@@ -0,0 +1,95 @@
1
+ #pragma once
2
+
3
+ #include <napi.h>
4
+ #include <vector>
5
+ #include <functional>
6
+
7
+ class ThreadSafeCallback {
8
+ public:
9
+ // More descriptive type aliases
10
+ using ArgumentVector = std::vector<napi_value>;
11
+ using ArgumentFunction = std::function<void(napi_env, ArgumentVector&)>;
12
+
13
+ // Constructor with validation
14
+ ThreadSafeCallback(const Napi::Value& receiver, const Napi::Function& jsCallback);
15
+
16
+ // Destructor
17
+ ~ThreadSafeCallback();
18
+
19
+ // Delete copy and move operations explicitly
20
+ ThreadSafeCallback(const ThreadSafeCallback&) = delete;
21
+ ThreadSafeCallback& operator=(const ThreadSafeCallback&) = delete;
22
+ ThreadSafeCallback& operator=(ThreadSafeCallback&&) = delete;
23
+
24
+ // Public interface
25
+ void call(ArgumentFunction argFunction);
26
+
27
+ private:
28
+ // Static callback handler
29
+ static void callJsCallback(Napi::Env env,
30
+ Napi::Function jsCallback,
31
+ Napi::Reference<Napi::Value>* context,
32
+ ArgumentFunction* argFn);
33
+
34
+ // Type alias for the thread-safe function
35
+ using ThreadSafeFunc = Napi::TypedThreadSafeFunction<
36
+ Napi::Reference<Napi::Value>,
37
+ ArgumentFunction,
38
+ callJsCallback>;
39
+
40
+ // Member variables
41
+ Napi::Reference<Napi::Value> receiver_;
42
+ ThreadSafeFunc threadSafeFunction_;
43
+ };
44
+
45
+ // Implementation
46
+
47
+ inline ThreadSafeCallback::ThreadSafeCallback(
48
+ const Napi::Value& receiver,
49
+ const Napi::Function& jsCallback) {
50
+
51
+ if (!(receiver.IsObject() || receiver.IsFunction())) {
52
+ throw Napi::Error::New(jsCallback.Env(),
53
+ "Callback receiver must be an object or function");
54
+ }
55
+ if (!jsCallback.IsFunction()) {
56
+ throw Napi::Error::New(jsCallback.Env(),
57
+ "Callback must be a function");
58
+ }
59
+
60
+ receiver_ = Napi::Persistent(receiver);
61
+ threadSafeFunction_ = ThreadSafeFunc::New(
62
+ jsCallback.Env(),
63
+ jsCallback,
64
+ "ThreadSafeCallback callback",
65
+ 0, 1,
66
+ &receiver_);
67
+ }
68
+
69
+ inline ThreadSafeCallback::~ThreadSafeCallback() {
70
+ threadSafeFunction_.Abort();
71
+ }
72
+
73
+ inline void ThreadSafeCallback::call(ArgumentFunction argFunction) {
74
+ auto argFn = new ArgumentFunction(argFunction);
75
+ if (threadSafeFunction_.BlockingCall(argFn) != napi_ok) {
76
+ delete argFn;
77
+ }
78
+ }
79
+
80
+ inline void ThreadSafeCallback::callJsCallback(
81
+ Napi::Env env,
82
+ Napi::Function jsCallback,
83
+ Napi::Reference<Napi::Value>* context,
84
+ ArgumentFunction* argFn) {
85
+
86
+ if (argFn != nullptr) {
87
+ ArgumentVector args;
88
+ (*argFn)(env, args);
89
+ delete argFn;
90
+
91
+ if (env != nullptr && jsCallback != nullptr) {
92
+ jsCallback.Call(context->Value(), args);
93
+ }
94
+ }
95
+ }