@stoprocent/bleno 0.11.4 → 0.12.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/lib/bleno.js CHANGED
@@ -296,6 +296,9 @@ class Bleno extends EventEmitter {
296
296
  stop () {
297
297
  debug('stop');
298
298
  this._bindings.stop();
299
+ // Reset initialized flag so we can restart
300
+ this.initialized = false;
301
+ this.state = 'unknown';
299
302
  }
300
303
 
301
304
  updateRssi (callback) {
@@ -133,7 +133,6 @@ class Hci extends EventEmitter {
133
133
  }
134
134
 
135
135
  resetBuffers () {
136
- this._mainHandle = null;
137
136
  this._handleAclsInProgress = {};
138
137
  this._handleBuffers = {};
139
138
  this._aclOutQueue = [];
@@ -527,7 +526,7 @@ class Hci extends EventEmitter {
527
526
  if (subEventType === EVT_DISCONN_COMPLETE) {
528
527
  handle = data.readUInt16LE(4);
529
528
  debug('\t\thandle = ' + handle);
530
- if (handle !== this._mainHandle) {
529
+ if (this._handleAclsInProgress[handle] === undefined) {
531
530
  debug('\tignoring event because handle is unknown to bleno.');
532
531
  debug('This might be OK in a multi role scenario in which the handle is part of a noble connection.');
533
532
  return;
@@ -543,7 +542,6 @@ class Hci extends EventEmitter {
543
542
  Controller for the returned Handle have been flushed, and that the
544
543
  corresponding data buffers have been freed. */
545
544
  delete this._handleAclsInProgress[handle];
546
- this._mainHandle = null;
547
545
  const aclOutQueue = [];
548
546
  let discarded = 0;
549
547
  for (const i in this._aclOutQueue) {
@@ -789,7 +787,6 @@ class Hci extends EventEmitter {
789
787
  debug('\t\t\tsupervision timeout = ' + supervisionTimeout);
790
788
  debug('\t\t\tmaster clock accuracy = ' + masterClockAccuracy);
791
789
 
792
- this._mainHandle = handle;
793
790
  this._handleAclsInProgress[handle] = 0;
794
791
 
795
792
  this.emit('leConnComplete', status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy);
@@ -8,20 +8,30 @@
8
8
  #pragma once
9
9
 
10
10
  #include <map>
11
+ #include <set>
11
12
  #include "callbacks.h"
12
13
 
13
14
  #import <CoreBluetooth/CoreBluetooth.h>
14
15
 
16
+ // Restoration identifier for state restoration across restarts
17
+ static NSString * _Nonnull const kBlenoRestorationIdentifier = @"com.bleno.peripheral.manager";
18
+
15
19
  @interface BLEPeripheralManager : NSObject {
16
20
  @public Emit emit;
17
21
  @public std::map<CBUUID *, EmitCharacteristic> emitters;
18
22
  }
19
23
 
24
+ // Track connected centrals by their identifier
25
+ @property (nonatomic, strong, readonly) NSMutableSet<NSUUID *> * _Nullable connectedCentrals;
26
+ // Track current services for proper cleanup
27
+ @property (nonatomic, strong, readonly) NSMutableArray<CBMutableService *> * _Nullable currentServices;
28
+
20
29
  - (nonnull instancetype)init NS_DESIGNATED_INITIALIZER;
21
30
  - (void)start;
22
31
  - (void)startAdvertising:(NSString * _Nonnull)name serviceUUIDs:(NSArray<CBUUID *> * _Nonnull)serviceUUIDs;
23
32
  - (void)stopAdvertising;
24
33
  - (void)setServices:(NSArray<CBMutableService *> * _Nonnull)services;
34
+ - (void)removeAllServices;
25
35
  - (void)disconnect;
26
36
  - (void)updateRssi;
27
37
 
@@ -14,6 +14,10 @@
14
14
  @property (nonatomic, strong) dispatch_queue_t processingQueue;
15
15
  @property (nonatomic, strong) NSMutableArray<NSDictionary *> *pendingNotifications;
16
16
  @property (nonatomic, strong) CBPeripheralManager *peripheralManager;
17
+ @property (nonatomic, strong, readwrite) NSMutableSet<NSUUID *> *connectedCentrals;
18
+ @property (nonatomic, strong, readwrite) NSMutableArray<CBMutableService *> *currentServices;
19
+ @property (nonatomic, assign) BOOL triedWithRestoration;
20
+ @property (nonatomic, assign) BOOL retriedWithoutRestoration;
17
21
  @end
18
22
 
19
23
  @implementation BLEPeripheralManager
@@ -23,14 +27,19 @@
23
27
  if (self = [super init]) {
24
28
  self.processingQueue = dispatch_queue_create("com.bleno.processing.queue", DISPATCH_QUEUE_SERIAL);
25
29
  self.pendingNotifications = [NSMutableArray array];
30
+ self.connectedCentrals = [NSMutableSet set];
31
+ self.currentServices = [NSMutableArray array];
26
32
  }
27
33
  return self;
28
34
  }
29
35
 
30
36
  - (void)dealloc
31
37
  {
38
+ [self removeAllServices];
32
39
  self.peripheralManager.delegate = nil;
33
40
  [self.pendingNotifications removeAllObjects];
41
+ [self.connectedCentrals removeAllObjects];
42
+ [self.currentServices removeAllObjects];
34
43
  }
35
44
 
36
45
  #pragma mark - Notification Management
@@ -76,8 +85,36 @@
76
85
 
77
86
  - (void)start
78
87
  {
88
+ [self startWithRestoration:YES];
89
+ }
90
+
91
+ - (void)startWithRestoration:(BOOL)useRestoration
92
+ {
93
+ // Clean up any existing peripheral manager
94
+ if (self.peripheralManager) {
95
+ self.peripheralManager.delegate = nil;
96
+ self.peripheralManager = nil;
97
+ }
98
+
99
+ NSDictionary *options;
100
+ if (useRestoration) {
101
+ // Initialize with state restoration to recover from previous state
102
+ options = @{
103
+ CBPeripheralManagerOptionRestoreIdentifierKey: kBlenoRestorationIdentifier,
104
+ CBPeripheralManagerOptionShowPowerAlertKey: @YES
105
+ };
106
+ self.triedWithRestoration = YES;
107
+ } else {
108
+ // Initialize without state restoration
109
+ options = @{
110
+ CBPeripheralManagerOptionShowPowerAlertKey: @YES
111
+ };
112
+ self.triedWithRestoration = NO;
113
+ }
114
+
79
115
  self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self
80
- queue:self.processingQueue];
116
+ queue:self.processingQueue
117
+ options:options];
81
118
  }
82
119
 
83
120
  - (void)startAdvertising:(nonnull NSString *)name serviceUUIDs:(nonnull NSArray<CBUUID *> *)serviceUUIDs
@@ -99,11 +136,23 @@
99
136
 
100
137
  - (void)setServices:(NSArray<CBMutableService *> *)services
101
138
  {
139
+ // Store services for cleanup later
140
+ [self.currentServices addObjectsFromArray:services];
141
+
102
142
  for (CBMutableService *service in services) {
103
143
  [self.peripheralManager addService:service];
104
144
  }
105
145
  }
106
146
 
147
+ - (void)removeAllServices
148
+ {
149
+ if (self.peripheralManager) {
150
+ [self.peripheralManager removeAllServices];
151
+ }
152
+ [self.currentServices removeAllObjects];
153
+ emitters.clear();
154
+ }
155
+
107
156
  - (void)disconnect
108
157
  {
109
158
 
@@ -117,7 +166,20 @@
117
166
  #pragma mark - CBPeripheralManagerDelegate
118
167
 
119
168
  - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
120
- {
169
+ {
170
+ // Check if state is unsupported and we tried with restoration - retry without it
171
+ if (peripheral.state == CBManagerStateUnsupported &&
172
+ self.triedWithRestoration &&
173
+ !self.retriedWithoutRestoration) {
174
+ self.retriedWithoutRestoration = YES;
175
+
176
+ // Dispatch async to avoid modifying peripheral manager during delegate callback
177
+ dispatch_async(self.processingQueue, ^{
178
+ [self startWithRestoration:NO];
179
+ });
180
+ return;
181
+ }
182
+
121
183
  auto state = StringFromCBPeripheralState(peripheral.state);
122
184
  emit.StateChange(state);
123
185
  }
@@ -137,6 +199,13 @@
137
199
 
138
200
  - (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBMutableCharacteristic *)characteristic
139
201
  {
202
+ // Track connected centrals and emit accept event for new connections
203
+ BOOL isNewConnection = ![self.connectedCentrals containsObject:central.identifier];
204
+ if (isNewConnection) {
205
+ [self.connectedCentrals addObject:central.identifier];
206
+ emit.Accept(central.identifier);
207
+ }
208
+
140
209
  for (auto it = emitters.begin(); it != emitters.end(); ++it) {
141
210
  if ([it->first isEqual:characteristic.UUID] == NO) { continue; }
142
211
  auto cb = [weakSelf = self, characteristic, central](NSData *data) {
@@ -156,6 +225,13 @@
156
225
 
157
226
  - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request
158
227
  {
228
+ // Track connected centrals on read requests too
229
+ BOOL isNewConnection = ![self.connectedCentrals containsObject:request.central.identifier];
230
+ if (isNewConnection) {
231
+ [self.connectedCentrals addObject:request.central.identifier];
232
+ emit.Accept(request.central.identifier);
233
+ }
234
+
159
235
  for (auto it = emitters.begin(); it != emitters.end(); ++it) {
160
236
  if ([it->first isEqual:request.characteristic.UUID] == NO) { continue; }
161
237
  auto cb = [peripheral, request](int result, NSData *data) {
@@ -169,6 +245,13 @@
169
245
  - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests
170
246
  {
171
247
  for (CBATTRequest *request in requests) {
248
+ // Track connected centrals on write requests too
249
+ BOOL isNewConnection = ![self.connectedCentrals containsObject:request.central.identifier];
250
+ if (isNewConnection) {
251
+ [self.connectedCentrals addObject:request.central.identifier];
252
+ emit.Accept(request.central.identifier);
253
+ }
254
+
172
255
  CBCharacteristic *characteristic = request.characteristic;
173
256
  for (auto it = emitters.begin(); it != emitters.end(); ++it) {
174
257
  if ([it->first isEqual:characteristic.UUID] == NO) { continue; }
@@ -193,4 +276,33 @@
193
276
  [self processNotificationQueue];
194
277
  }
195
278
 
279
+ #pragma mark - State Restoration
280
+
281
+ - (void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary<NSString *, id> *)dict
282
+ {
283
+ // Restore services that were previously registered
284
+ NSArray<CBMutableService *> *restoredServices = dict[CBPeripheralManagerRestoredStateServicesKey];
285
+ if (restoredServices) {
286
+ [self.currentServices addObjectsFromArray:restoredServices];
287
+
288
+ // Iterate through restored services to find subscribed centrals
289
+ for (CBMutableService *service in restoredServices) {
290
+ if (service.characteristics) {
291
+ for (CBMutableCharacteristic *characteristic in service.characteristics) {
292
+ // Check for subscribed centrals on this characteristic
293
+ if (characteristic.subscribedCentrals && characteristic.subscribedCentrals.count > 0) {
294
+ for (CBCentral *central in characteristic.subscribedCentrals) {
295
+ if (![self.connectedCentrals containsObject:central.identifier]) {
296
+ [self.connectedCentrals addObject:central.identifier];
297
+ // Emit accept event for the restored connection
298
+ emit.Accept(central.identifier);
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+ }
305
+ }
306
+ }
307
+
196
308
  @end
@@ -54,6 +54,9 @@ Napi::Value BlenoMac::Init(const Napi::CallbackInfo& info) {
54
54
 
55
55
  Napi::Value BlenoMac::Stop(const Napi::CallbackInfo& info) {
56
56
  CHECK_MANAGER()
57
+ // Properly clean up: stop advertising, remove all services
58
+ [peripheralManager stopAdvertising];
59
+ [peripheralManager removeAllServices];
57
60
  peripheralManager = nil;
58
61
  return info.Env().Undefined();
59
62
  }
@@ -15,6 +15,8 @@ public:
15
15
  void AdvertisingStop();
16
16
  void ServicesSet(NSError * _Nullable error);
17
17
  void StateChange(const std::string& state);
18
+ void Accept(NSUUID *centralUuid);
19
+ void Disconnect(NSUUID *centralUuid);
18
20
  protected:
19
21
  std::shared_ptr<ThreadSafeCallback> mCallback;
20
22
  };
@@ -60,6 +60,18 @@ void Emit::StateChange(const std::string& state) {
60
60
  });
61
61
  }
62
62
 
63
+ void Emit::Accept(NSUUID *centralUuid) {
64
+ mCallback->call([centralUuid](Napi::Env env, std::vector<napi_value>& args) {
65
+ args = { _s("accept"), _a(centralUuid), _a(centralUuid) };
66
+ });
67
+ }
68
+
69
+ void Emit::Disconnect(NSUUID *centralUuid) {
70
+ mCallback->call([centralUuid](Napi::Env env, std::vector<napi_value>& args) {
71
+ args = { _s("disconnect"), _a(centralUuid) };
72
+ });
73
+ }
74
+
63
75
  void EmitCharacteristic::Wrap(const Napi::Value& receiver, const Napi::Function& callback) {
64
76
  mCallback = std::make_shared<ThreadSafeCallback>(receiver, callback);
65
77
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoprocent/bleno",
3
- "version": "0.11.4",
3
+ "version": "0.12.1",
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",