diff --git a/lib/common/include/ThreadSafeCallback.h b/lib/common/include/ThreadSafeCallback.h index a7dceeba..f8f9f2e8 100644 --- a/lib/common/include/ThreadSafeCallback.h +++ b/lib/common/include/ThreadSafeCallback.h @@ -72,7 +72,9 @@ inline ThreadSafeCallback::~ThreadSafeCallback() { inline void ThreadSafeCallback::call(ArgumentFunction argFunction) { auto argFn = new ArgumentFunction(argFunction); - if (threadSafeFunction_.BlockingCall(argFn) != napi_ok) { + // Use NonBlockingCall to avoid hanging if environment is destroyed + // This will queue the callback but not block waiting for it + if (threadSafeFunction_.NonBlockingCall(argFn) != napi_ok) { delete argFn; } } @@ -83,13 +85,50 @@ inline void ThreadSafeCallback::callJsCallback( Napi::Reference* context, ArgumentFunction* argFn) { - if (argFn != nullptr) { + if (argFn == nullptr) { + return; + } + + // Check if environment and callback are valid before proceeding + if (env == nullptr || jsCallback == nullptr || context == nullptr) { + delete argFn; + return; + } + + // Check if context reference is still valid + if (context->IsEmpty()) { + delete argFn; + return; + } + + try { ArgumentVector args; (*argFn)(env, args); delete argFn; - if (env != nullptr && jsCallback != nullptr) { - jsCallback.Call(context->Value(), args); + // Get the receiver value and check if it's valid + Napi::Value receiverValue = context->Value(); + // Check if receiver value is null or undefined (invalid) + if (receiverValue.IsNull() || receiverValue.IsUndefined()) { + return; + } + + // Attempt to call the callback with error handling + // If the environment is being destroyed, this may fail + // Note: N-API errors don't throw C++ exceptions, so this won't catch + // napi_open_callback_scope failures, but it helps with other cases + try { + jsCallback.Call(receiverValue, args); + } catch (const std::exception&) { + // Silently ignore exceptions - environment might be destroyed + } catch (...) { + // Catch any other exceptions during callback execution } + } catch (const std::exception&) { + // If argument building fails, just clean up + delete argFn; + } catch (...) { + // Catch any other exceptions and clean up + delete argFn; } } \ No newline at end of file diff --git a/lib/mac/src/ble_manager.h b/lib/mac/src/ble_manager.h index 3805901d..e8e1c5c1 100644 --- a/lib/mac/src/ble_manager.h +++ b/lib/mac/src/ble_manager.h @@ -16,6 +16,7 @@ @property NSMutableDictionary *peripherals; @property NSMutableDictionary *mtus; @property NSMutableSet *discovered; +@property (strong) dispatch_source_t stateCheckTimer; - (instancetype)init: (const Napi::Value&) receiver with: (const Napi::Function&) callback; diff --git a/lib/mac/src/ble_manager.mm b/lib/mac/src/ble_manager.mm index d45a3d45..b82f1e96 100644 --- a/lib/mac/src/ble_manager.mm +++ b/lib/mac/src/ble_manager.mm @@ -21,6 +21,27 @@ - (instancetype)init: (const Napi::Value&) receiver with: (const Napi::Function& self.discovered = [NSMutableSet set]; self.peripherals = [NSMutableDictionary dictionaryWithCapacity:10]; self.mtus = [NSMutableDictionary dictionaryWithCapacity:10]; + + // Set up periodic state checking to handle sleep/wake cycles using GCD timer + // Check every 2 seconds for state changes on the same dispatch queue + self.stateCheckTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.dispatchQueue); + if (self.stateCheckTimer) { + dispatch_source_set_timer(self.stateCheckTimer, + dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), + 2.0 * NSEC_PER_SEC, + 0.1 * NSEC_PER_SEC); // 100ms leeway + __weak typeof(self) weakSelf = self; + dispatch_source_set_event_handler(self.stateCheckTimer, ^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + if (strongSelf) { + CBManagerState currentState = strongSelf.centralManager.state; + if (currentState != strongSelf.lastState) { + [strongSelf centralManagerDidUpdateState:strongSelf.centralManager]; + } + } + }); + dispatch_resume(self.stateCheckTimer); + } } return self; } @@ -519,5 +540,12 @@ -(CBDescriptor*)getDescriptor:(CBPeripheral*) peripheral ByHandle:(NSNumber*) ha return nil; } -@end +- (void)dealloc { + // Clean up GCD timer + if (self.stateCheckTimer) { + dispatch_source_cancel(self.stateCheckTimer); + self.stateCheckTimer = nil; + } +} +@end