@stoprocent/noble 2.3.5 → 2.3.7
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/common/include/ThreadSafeCallback.h +43 -4
- package/lib/mac/src/ble_manager.h +1 -0
- package/lib/mac/src/ble_manager.mm +29 -1
- package/lib/noble.js +3 -1
- package/package.json +1 -1
- 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/test/noble.test.js +19 -1
|
@@ -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
|
|
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
|
|
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
|
-
|
|
92
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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/lib/noble.js
CHANGED
|
@@ -188,10 +188,12 @@ class Noble extends EventEmitter {
|
|
|
188
188
|
const self = this;
|
|
189
189
|
const scan = (state) => {
|
|
190
190
|
if (state !== 'poweredOn') {
|
|
191
|
-
|
|
191
|
+
const boundScan = scan.bind(self);
|
|
192
|
+
self.once('stateChange', boundScan);
|
|
192
193
|
const error = new Error(`Could not start scanning, state is ${state} (not poweredOn)`);
|
|
193
194
|
|
|
194
195
|
if (typeof callback === 'function') {
|
|
196
|
+
self.removeListener('stateChange', boundScan);
|
|
195
197
|
callback(error);
|
|
196
198
|
} else {
|
|
197
199
|
throw error;
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/test/noble.test.js
CHANGED
|
@@ -184,6 +184,24 @@ describe('noble', () => {
|
|
|
184
184
|
);
|
|
185
185
|
expect(mockBindings.startScanning).not.toHaveBeenCalled();
|
|
186
186
|
});
|
|
187
|
+
|
|
188
|
+
test('should not cause MaxListenersExceededWarning warnings after repeated unauthorized errors', async () => {
|
|
189
|
+
noble._state = 'unauthorized';
|
|
190
|
+
|
|
191
|
+
const promise = noble.waitForPoweredOnAsync(0);
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i < 10; i++) {
|
|
194
|
+
try {
|
|
195
|
+
await noble.startScanningAsync([]);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const finalListenerCount = noble.listenerCount('stateChange');
|
|
201
|
+
|
|
202
|
+
await expect(promise).rejects.toThrow('Timeout waiting for Noble to be powered on');
|
|
203
|
+
expect(finalListenerCount).toBeLessThanOrEqual(1);
|
|
204
|
+
});
|
|
187
205
|
});
|
|
188
206
|
|
|
189
207
|
describe('stopScanning', () => {
|
|
@@ -421,7 +439,7 @@ describe('noble', () => {
|
|
|
421
439
|
await expect(promise).rejects.toThrow('Timeout waiting for Noble to be powered on');
|
|
422
440
|
});
|
|
423
441
|
|
|
424
|
-
test('should not cause MaxListenersExceededWarning with multiple timeout calls', async () => {
|
|
442
|
+
test('should not cause MaxListenersExceededWarning warnings with multiple timeout calls', async () => {
|
|
425
443
|
noble._state = 'poweredOff';
|
|
426
444
|
|
|
427
445
|
const promises = [];
|