@stoprocent/noble 2.3.6 → 2.3.8

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.
@@ -72,7 +72,9 @@ inline ThreadSafeCallback::~ThreadSafeCallback() {
72
72
 
73
73
  inline void ThreadSafeCallback::call(ArgumentFunction argFunction) {
74
74
  auto argFn = new ArgumentFunction(argFunction);
75
- if (threadSafeFunction_.BlockingCall(argFn) != napi_ok) {
75
+ // Use NonBlockingCall to avoid hanging if environment is destroyed
76
+ // This will queue the callback but not block waiting for it
77
+ if (threadSafeFunction_.NonBlockingCall(argFn) != napi_ok) {
76
78
  delete argFn;
77
79
  }
78
80
  }
@@ -83,13 +85,50 @@ inline void ThreadSafeCallback::callJsCallback(
83
85
  Napi::Reference<Napi::Value>* context,
84
86
  ArgumentFunction* argFn) {
85
87
 
86
- if (argFn != nullptr) {
88
+ if (argFn == nullptr) {
89
+ return;
90
+ }
91
+
92
+ // Check if environment and callback are valid before proceeding
93
+ if (env == nullptr || jsCallback == nullptr || context == nullptr) {
94
+ delete argFn;
95
+ return;
96
+ }
97
+
98
+ // Check if context reference is still valid
99
+ if (context->IsEmpty()) {
100
+ delete argFn;
101
+ return;
102
+ }
103
+
104
+ try {
87
105
  ArgumentVector args;
88
106
  (*argFn)(env, args);
89
107
  delete argFn;
90
108
 
91
- if (env != nullptr && jsCallback != nullptr) {
92
- jsCallback.Call(context->Value(), args);
109
+ // Get the receiver value and check if it's valid
110
+ Napi::Value receiverValue = context->Value();
111
+ // Check if receiver value is null or undefined (invalid)
112
+ if (receiverValue.IsNull() || receiverValue.IsUndefined()) {
113
+ return;
114
+ }
115
+
116
+ // Attempt to call the callback with error handling
117
+ // If the environment is being destroyed, this may fail
118
+ // Note: N-API errors don't throw C++ exceptions, so this won't catch
119
+ // napi_open_callback_scope failures, but it helps with other cases
120
+ try {
121
+ jsCallback.Call(receiverValue, args);
122
+ } catch (const std::exception&) {
123
+ // Silently ignore exceptions - environment might be destroyed
124
+ } catch (...) {
125
+ // Catch any other exceptions during callback execution
93
126
  }
127
+ } catch (const std::exception&) {
128
+ // If argument building fails, just clean up
129
+ delete argFn;
130
+ } catch (...) {
131
+ // Catch any other exceptions and clean up
132
+ delete argFn;
94
133
  }
95
134
  }
@@ -158,20 +158,29 @@ Hci.prototype.init = function (options) {
158
158
  this._socket.on('error', this.onSocketError.bind(this));
159
159
  this._socket.on('state', this.pollIsDevUp.bind(this));
160
160
 
161
- if (this._userChannel) {
162
- this._socket.bindUser(this._deviceId, this._bindParams);
163
- this._socket.start();
164
-
165
- this.reset();
166
- } else {
167
- if (!this._bound) {
168
- this._socket.bindRaw(this._deviceId, this._bindParams);
169
- this._bound = true;
161
+ try {
162
+ // Bind first (either user channel or raw)
163
+ if (this._userChannel) {
164
+ this._socket.bindUser(this._deviceId, this._bindParams);
165
+ } else {
166
+ if (!this._bound) {
167
+ this._socket.bindRaw(this._deviceId, this._bindParams);
168
+ this._bound = true;
169
+ }
170
170
  }
171
+
172
+ // Start and reset (common to both paths)
171
173
  this._socket.start();
172
- this._isStarted = true;
173
174
  this.reset();
174
- this.pollIsDevUp();
175
+
176
+ // Set started flag and poll only for non-userChannel
177
+ if (!this._userChannel) {
178
+ this._isStarted = true;
179
+ this.pollIsDevUp();
180
+ }
181
+ } catch (error) {
182
+ debug(`init error: ${error.message}`);
183
+ this.emit('stateChange', 'unsupported');
175
184
  }
176
185
  };
177
186
 
@@ -16,6 +16,7 @@
16
16
  @property NSMutableDictionary *peripherals;
17
17
  @property NSMutableDictionary *mtus;
18
18
  @property NSMutableSet *discovered;
19
+ @property (strong) dispatch_source_t stateCheckTimer;
19
20
 
20
21
 
21
22
  - (instancetype)init: (const Napi::Value&) receiver with: (const Napi::Function&) callback;
@@ -21,6 +21,27 @@
21
21
  self.discovered = [NSMutableSet set];
22
22
  self.peripherals = [NSMutableDictionary dictionaryWithCapacity:10];
23
23
  self.mtus = [NSMutableDictionary dictionaryWithCapacity:10];
24
+
25
+ // Set up periodic state checking to handle sleep/wake cycles using GCD timer
26
+ // Check every 2 seconds for state changes on the same dispatch queue
27
+ self.stateCheckTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.dispatchQueue);
28
+ if (self.stateCheckTimer) {
29
+ dispatch_source_set_timer(self.stateCheckTimer,
30
+ dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC),
31
+ 2.0 * NSEC_PER_SEC,
32
+ 0.1 * NSEC_PER_SEC); // 100ms leeway
33
+ __weak typeof(self) weakSelf = self;
34
+ dispatch_source_set_event_handler(self.stateCheckTimer, ^{
35
+ __strong typeof(weakSelf) strongSelf = weakSelf;
36
+ if (strongSelf) {
37
+ CBManagerState currentState = strongSelf.centralManager.state;
38
+ if (currentState != strongSelf.lastState) {
39
+ [strongSelf centralManagerDidUpdateState:strongSelf.centralManager];
40
+ }
41
+ }
42
+ });
43
+ dispatch_resume(self.stateCheckTimer);
44
+ }
24
45
  }
25
46
  return self;
26
47
  }
@@ -519,5 +540,12 @@
519
540
  return nil;
520
541
  }
521
542
 
522
- @end
543
+ - (void)dealloc {
544
+ // Clean up GCD timer
545
+ if (self.stateCheckTimer) {
546
+ dispatch_source_cancel(self.stateCheckTimer);
547
+ self.stateCheckTimer = nil;
548
+ }
549
+ }
523
550
 
551
+ @end
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.6",
9
+ "version": "2.3.8",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "https://github.com/stoprocent/noble.git"